lib: added implicit ctor converter from DatabaseDatabase to DBListType
[barry/progweb.git] / desktop / src / ConflictDlg.cc
blob17adf892f7b122a0e3b91e7813ad811e7e91b4d1
1 ///
2 /// \file ConflictDlg.cc
3 /// The dialog used during a sync, to display conflicting
4 /// changes, and let the user decide what to do.
5 ///
7 /*
8 Copyright (C) 2010-2012, Net Direct Inc. (http://www.netdirect.ca/)
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19 See the GNU General Public License in the COPYING file at the
20 root directory of this project for more details.
23 #include "ConflictDlg.h"
24 #include "windowids.h"
25 #include "util.h"
26 #include <iostream>
27 #include <sstream>
28 #include <stdexcept>
29 #include <wx/statline.h>
30 using namespace std;
32 //////////////////////////////////////////////////////////////////////////////
33 // ConflictDlg class
35 BEGIN_EVENT_TABLE(ConflictDlg, wxDialog)
36 EVT_BUTTON (Dialog_Conflict_ShowButton1,
37 ConflictDlg::OnShowButton)
38 EVT_BUTTON (Dialog_Conflict_ShowButton2,
39 ConflictDlg::OnShowButton)
40 EVT_BUTTON (Dialog_Conflict_ShowButton3,
41 ConflictDlg::OnShowButton)
42 EVT_BUTTON (Dialog_Conflict_ShowButton4,
43 ConflictDlg::OnShowButton)
44 EVT_BUTTON (Dialog_Conflict_ShowButton5,
45 ConflictDlg::OnShowButton)
46 EVT_BUTTON (Dialog_Conflict_ShowButton6,
47 ConflictDlg::OnShowButton)
48 EVT_BUTTON (Dialog_Conflict_ShowButton7,
49 ConflictDlg::OnShowButton)
50 EVT_BUTTON (Dialog_Conflict_ShowButton8,
51 ConflictDlg::OnShowButton)
52 EVT_BUTTON (Dialog_Conflict_ShowButton9,
53 ConflictDlg::OnShowButton)
54 EVT_BUTTON (Dialog_Conflict_SelectButton1,
55 ConflictDlg::OnSelectButton)
56 EVT_BUTTON (Dialog_Conflict_SelectButton2,
57 ConflictDlg::OnSelectButton)
58 EVT_BUTTON (Dialog_Conflict_SelectButton3,
59 ConflictDlg::OnSelectButton)
60 EVT_BUTTON (Dialog_Conflict_SelectButton4,
61 ConflictDlg::OnSelectButton)
62 EVT_BUTTON (Dialog_Conflict_SelectButton5,
63 ConflictDlg::OnSelectButton)
64 EVT_BUTTON (Dialog_Conflict_SelectButton6,
65 ConflictDlg::OnSelectButton)
66 EVT_BUTTON (Dialog_Conflict_SelectButton7,
67 ConflictDlg::OnSelectButton)
68 EVT_BUTTON (Dialog_Conflict_SelectButton8,
69 ConflictDlg::OnSelectButton)
70 EVT_BUTTON (Dialog_Conflict_SelectButton9,
71 ConflictDlg::OnSelectButton)
72 EVT_BUTTON (Dialog_Conflict_DuplicateButton,
73 ConflictDlg::OnDuplicateButton)
74 EVT_BUTTON (Dialog_Conflict_AbortButton,
75 ConflictDlg::OnAbortButton)
76 EVT_BUTTON (Dialog_Conflict_IgnoreButton,
77 ConflictDlg::OnIgnoreButton)
78 EVT_BUTTON (Dialog_Conflict_KeepNewerButton,
79 ConflictDlg::OnKeepNewerButton)
80 EVT_BUTTON (Dialog_Conflict_KillSyncButton,
81 ConflictDlg::OnKillSyncButton)
82 EVT_CHECKBOX (Dialog_Conflict_AlwaysCheckbox,
83 ConflictDlg::OnAlwaysCheckbox)
84 END_EVENT_TABLE()
86 ConflictDlg::ConflictDlg(wxWindow *parent,
87 const OpenSync::API &engine,
88 const std::string &supported_commands,
89 const std::vector<OpenSync::SyncChange> &changes,
90 ConflictDlg::AlwaysMemoryBlock &always)
91 : wxDialog(parent, Dialog_Conflict, _T("Sync Conflict"))
92 , m_engine(engine)
93 , m_changes(changes)
94 , m_supported_commands(supported_commands)
95 , m_always(always)
96 , m_kill_sync(false)
97 , m_topsizer(0)
99 // the max text width is the maximum width that any line of text
100 // in a conflict summary can use... it is calculated with
101 // (screen_width - window_border_width*2) / change_count
102 m_max_text_width = (wxSystemSettings::GetMetric(wxSYS_SCREEN_X) -
103 wxSystemSettings::GetMetric(wxSYS_BORDER_X) * 2 -
104 10 * 2 -
105 5 * 2 * m_changes.size())
106 / m_changes.size();
108 // first, parse all change data
109 ParseChanges();
111 // create a global set of key names that contain differing data
112 // between changes
113 CreateDifferingKeyNameSet();
115 // setup the raw GUI
116 CreateLayout();
119 ConflictDlg::~ConflictDlg()
123 void ConflictDlg::ParseChanges()
125 m_maps.clear();
127 // create an xmlmap to start with, so we only parse the
128 // map file once
129 ostringstream oss;
130 oss << m_engine.GetEngineName() << "/xmlmap";
131 tr1::shared_ptr<XmlNodeMap> basemap;
132 try {
133 basemap.reset(new XmlNodeMap(GetBaseFilename(oss.str())) );
135 catch( std::runtime_error &e ) {
136 basemap.reset();
139 for( size_t i = 0; i < m_changes.size(); i++ ) {
140 XmlPair xp;
141 xp.dom.reset( new xmlpp::DomParser );
142 xp.dom->parse_memory_raw(
143 (const unsigned char*) m_changes[i].printable_data.data(),
144 m_changes[i].printable_data.size());
146 if( basemap.get() ) {
147 xp.map.reset( new XmlNodeMap(*basemap) );
148 xp.map->ImportNodes(xp.dom->get_document()->get_root_node());
149 xp.map->PurgeEmpties();
150 xp.map->SortBySummary();
153 m_maps.push_back(xp);
157 void ConflictDlg::CreateDifferingKeyNameSet()
159 // start fresh
160 m_differing_keys.clear();
162 if( !m_maps.size() || !m_maps[0].map.get() )
163 return; // nothing to do
165 // find a list of all available key names
166 key_set all_keys; // set of all keys names from all xmlmaps
167 for( size_t i = 0; i < m_maps.size(); i++ ) {
168 XmlNodeMap::iterator mi = m_maps[i].map->begin(),
169 me = m_maps[i].map->end();
171 for( ; mi != me; ++mi ) {
172 all_keys.insert(mi->KeyName());
177 // cycle through all keys and find ones that have changes
178 for( key_set::iterator i = all_keys.begin(); i!=all_keys.end(); ++i ) {
180 // find the mapping from the first nodemap
181 XmlNodeMapping *first = m_maps[0].map->Find(*i);
182 if( !first ) {
183 // if a key does not exist in this map, then
184 // it does in another, and therefore is "differing"
185 m_differing_keys.insert(*i);
186 continue;
189 // cycle through all remaining nodemaps, find the mapping that
190 // matches the keyname, and compare... if any do not
191 // exist, or do not match, add to the differing keys set
192 for( size_t j = 1; j < m_maps.size(); j++ ) {
193 XmlNodeMapping *next = m_maps[j].map->Find(*i);
195 // compare!
196 if( !next || *first != *next ) {
197 m_differing_keys.insert(*i);
198 break;
205 // +-barry-sync---------+ +-evo2-sync----------+ +-google-sync------+
206 // | | | | | |
207 // | Adam Brandee | | Adam Brandee | | Adam Brandee |
208 // | IBM Canada | | | | IBM Canada | < red
209 // | 1-519-555-1212 | | 1-519-555-1212 | | 1-519-555-1111 | < red
210 // | abramble@ibm.com | | abramble@ibm.com | | abramble@ibm.com |
211 // | ---- | | ---- | | ---- |
212 // | 123 Big Street | | 123 Big Street | | 123 Long St. | < red
213 // | | | | | Canada | < red
214 // +--------------------+ +--------------------+ +------------------+
216 // [See XML] [Select] [See XML] [Select] [See XML] [Select]
219 // At the bottom of the dialog, display the rest of the optional buttons,
220 // like Duplicate, Abort, etc. Also include a checkbox for "always"...
221 // figure out the best way to handle "always" selections... always
222 // change #1?
224 // If converting an XML change's data to a hash map throws an exception
225 // somewhere in the xmlpp::DomParser, then that change will have to
226 // be handled in a raw manner. I don't think any changes can be
227 // displayed in a table like above, but each should get a scrolling
228 // edit control with the raw data included.
230 // If possible, take the default table font and reduce it by 20% or
231 // something, because this table will likely hold a lot of data.
233 void ConflictDlg::CreateLayout()
235 m_topsizer = new wxBoxSizer(wxVERTICAL);
236 CreateSummaries(m_topsizer);
237 CreateAlternateButtons(m_topsizer);
239 SetSizer(m_topsizer);
240 m_topsizer->SetSizeHints(this);
241 m_topsizer->Layout();
244 void ConflictDlg::CreateSummaries(wxSizer *sizer)
246 // this sizer is the horizontal box containing one
247 // "map summary" each
248 wxBoxSizer *box = new wxBoxSizer(wxHORIZONTAL);
250 for( size_t i = 0; i < m_maps.size(); i++ ) {
251 CreateSummary(box, i);
254 sizer->Add(box, 0, wxEXPAND | wxALL, 5);
257 void ConflictDlg::CreateSummary(wxSizer *sizer, size_t change_index)
259 // this sizer contains the summary box in the top and the
260 // buttons in the bottom
261 wxBoxSizer *box = new wxBoxSizer(wxVERTICAL);
263 CreateSummaryGroup(box, change_index);
265 sizer->Add(box, 1, wxEXPAND | wxLEFT | wxRIGHT, 5);
268 void ConflictDlg::CreateSummaryGroup(wxSizer *sizer, size_t change_index)
270 wxString name(m_changes[change_index].plugin_name.c_str(), wxConvUTF8);
271 wxStaticBoxSizer *box = new wxStaticBoxSizer(wxVERTICAL, this, name);
273 XmlNodeMap *xml = m_maps[change_index].map.get();
274 if( xml ) {
275 // add all priority 0 mappings
276 XmlNodeMap::iterator nmi = xml->begin(), nme = xml->priority_end();
277 for( ; nmi != nme; ++nmi ) {
278 AddMapping(box, *nmi, IsDifferent(*nmi));
281 // add small separator (line?)
282 box->Add( new wxStaticLine(this), 0, wxEXPAND | wxALL, 5);
284 // add all differing mappings, in the map order, to preserve
285 // the map file's user-friendly display order
286 nmi = nme; // start at priority_end() to skip priority 0
287 nme = xml->end();
288 for( ; nmi != nme; ++nmi ) {
289 if( !IsDifferent(*nmi) )
290 continue;
292 AddMapping(box, *nmi, true);
295 else {
296 AddEmptyNotice(box);
299 box->Add(0, 10, 0);
300 box->Add(0, 0, 1);
301 CreateSummaryButtons(box, change_index);
303 sizer->Add(box, 1, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 0);
306 void ConflictDlg::CreateSummaryButtons(wxSizer *sizer, size_t change_index)
308 wxBoxSizer *box = new wxBoxSizer(wxHORIZONTAL);
310 box->Add(0, 0, 1);
311 box->Add( new wxButton(this,
312 Dialog_Conflict_ShowButton1 + change_index,
313 _T("XML..."),
314 wxDefaultPosition, wxDefaultSize,
315 wxBU_EXACTFIT),
316 0, wxTOP | wxLEFT | wxRIGHT, 3);
317 box->Add( new wxButton(this,
318 Dialog_Conflict_SelectButton1 + change_index,
319 _T("Select"),
320 wxDefaultPosition, wxDefaultSize,
321 wxBU_EXACTFIT),
322 0, wxTOP | wxLEFT | wxRIGHT, 3);
324 sizer->Add(box, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 0);
327 bool ConflictDlg::IsDifferent(const XmlNodeMapping &mapping) const
329 return m_differing_keys.find(mapping.KeyName()) != m_differing_keys.end();
332 void ConflictDlg::AddEmptyNotice(wxSizer *sizer)
334 wxFont font = GetFont();
335 font.SetStyle( wxFONTSTYLE_ITALIC );
337 wxStaticText *text = new wxStaticText(this, wxID_ANY,
338 _T("No XML map found."),
339 wxDefaultPosition, wxDefaultSize,
340 wxALIGN_CENTRE | wxST_NO_AUTORESIZE);
341 text->SetFont(font);
342 sizer->Add( text, 0, wxEXPAND, 0);
345 void ConflictDlg::AddMapping(wxSizer *sizer,
346 XmlNodeMapping &mapping,
347 bool differing)
349 // display differing text in red? Not sure how to do that... using italic
351 // use a big bold font for the high priority items
352 wxFont priority_font = GetFont();
353 priority_font.SetWeight( wxFONTWEIGHT_BOLD );
355 // italic for changed items
356 wxFont priority_changed = priority_font;
357 priority_changed.SetStyle( wxFONTSTYLE_ITALIC );
359 wxFont changed = GetFont();
360 changed.SetStyle( wxFONTSTYLE_ITALIC );
362 for( size_t i = 0; i < mapping.size(); i++ ) {
363 wxString data(mapping[i].Summary().raw().c_str(), wxConvUTF8);
365 wxStaticText *text = new wxStaticText(this, wxID_ANY, data,
366 wxDefaultPosition, wxDefaultSize,
367 wxALIGN_CENTRE | wxST_NO_AUTORESIZE);
368 if( mapping.Priority() == 0 ) {
369 if( differing )
370 text->SetFont(priority_changed);
371 else
372 text->SetFont(priority_font);
374 else {
375 if( differing )
376 text->SetFont(changed);
378 text->Wrap(m_max_text_width);
380 sizer->Add( text, 0, wxEXPAND, 0);
384 void ConflictDlg::CreateAlternateButtons(wxSizer *sizer)
386 wxBoxSizer *box = new wxBoxSizer(wxHORIZONTAL);
388 box->Add( new wxCheckBox(this, Dialog_Conflict_AlwaysCheckbox,
389 _T("Always use this choice")),
390 0, wxALIGN_CENTRE, 0);
391 box->Add( -1, -1, 1 );
393 if( m_supported_commands.find('D') != string::npos ) {
394 box->Add( new wxButton(this, Dialog_Conflict_DuplicateButton,
395 _T("Duplicate")),
396 0, wxLEFT , 5);
399 if( m_supported_commands.find('I') != string::npos ) {
400 box->Add( new wxButton(this, Dialog_Conflict_IgnoreButton,
401 _T("Ignore")),
402 0, wxLEFT, 5);
405 if( m_supported_commands.find('N') != string::npos ) {
406 box->Add( new wxButton(this, Dialog_Conflict_KeepNewerButton,
407 _T("Keep Newer")),
408 0, wxLEFT, 5);
411 if( m_supported_commands.find('A') != string::npos ) {
412 box->Add( new wxButton(this, Dialog_Conflict_AbortButton,
413 _T("Abort")),
414 0, wxLEFT, 5);
416 else {
417 // no abort available, so add a Kill Sync button
418 box->Add( new wxButton(this, Dialog_Conflict_KillSyncButton,
419 _T("Kill Sync")),
420 0, wxLEFT, 5);
423 sizer->Add(box, 0, wxEXPAND | wxALL, 10);
427 //////////////////////////////////////////////////////////////////////////////
428 // public members
430 void ConflictDlg::clear()
432 m_command_string.clear();
433 m_kill_sync = false;
436 int ConflictDlg::ShowModal()
438 int ret = 0;
440 // start fresh
441 clear();
443 // is there a favoured plugin name?
444 if( m_always.m_favour_plugin_name.size() ) {
445 // find the matching plugin name
446 for( size_t i = 0; i < m_changes.size(); i++ ) {
447 if( m_changes[i].plugin_name == m_always.m_favour_plugin_name ) {
448 ostringstream oss;
449 oss << "S " << m_changes[i].id;
450 m_command_string = oss.str();
451 return ret;
456 // is there an "always" answer we can use?
457 if( m_always.m_always ) {
458 if( m_always.m_last_command == "S" ) {
459 // find the change that has a matching member_id
460 // and plugin_name
461 for( size_t i = 0; i < m_changes.size(); i++ ) {
462 if( m_changes[i].member_id == m_always.m_member_id && m_changes[i].plugin_name == m_always.m_plugin_name ) {
463 ostringstream oss;
464 oss << "S " << m_changes[i].id;
465 m_command_string = oss.str();
466 return ret;
470 else {
471 m_command_string = m_always.m_last_command;
472 return ret; // done
476 // ok, no "always" data that works, so ask the user
477 do {
478 ret = wxDialog::ShowModal();
479 } while( m_command_string.size() == 0 && !m_kill_sync );
481 return ret;
484 void ConflictDlg::OnShowButton(wxCommandEvent &event)
486 int index = event.GetId() - Dialog_Conflict_ShowButton1;
487 wxString xmldata(m_changes[index].printable_data.c_str(), wxConvUTF8);
489 // let's try this the quick manual way...
490 wxDialog dlg(this, wxID_ANY, _T("Raw Change Data"));
491 wxBoxSizer *box = new wxBoxSizer(wxVERTICAL);
492 box->Add( new wxTextCtrl(&dlg, wxID_ANY, xmldata,
493 wxDefaultPosition, wxSize(400, 400),
494 wxTE_MULTILINE | wxTE_READONLY),
495 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
496 box->Add( dlg.CreateButtonSizer(wxOK), 0, wxEXPAND | wxALL, 10 );
497 dlg.SetSizer(box);
498 box->SetSizeHints(&dlg);
499 box->Layout();
500 dlg.ShowModal();
503 void ConflictDlg::OnSelectButton(wxCommandEvent &event)
505 int index = event.GetId() - Dialog_Conflict_SelectButton1;
507 // store command in m_always
508 m_always.m_member_id = m_changes[index].member_id;
509 m_always.m_plugin_name = m_changes[index].plugin_name;
510 m_always.m_last_command = "S";
512 // build actual command
513 ostringstream oss;
514 oss << "S " << m_changes[index].id;
515 m_command_string = oss.str();
517 EndModal(event.GetId());
520 void ConflictDlg::OnDuplicateButton(wxCommandEvent &event)
522 m_command_string = m_always.m_last_command = "D";
523 EndModal(event.GetId());
526 void ConflictDlg::OnAbortButton(wxCommandEvent &event)
528 m_command_string = m_always.m_last_command = "A";
529 EndModal(event.GetId());
532 void ConflictDlg::OnIgnoreButton(wxCommandEvent &event)
534 m_command_string = m_always.m_last_command = "I";
535 EndModal(event.GetId());
538 void ConflictDlg::OnKeepNewerButton(wxCommandEvent &event)
540 m_command_string = m_always.m_last_command = "N";
541 EndModal(event.GetId());
544 void ConflictDlg::OnKillSyncButton(wxCommandEvent &event)
546 m_command_string.clear();
547 m_always.m_last_command.clear();
548 m_kill_sync = true;
549 EndModal(event.GetId());
552 void ConflictDlg::OnAlwaysCheckbox(wxCommandEvent &event)
554 m_always.m_always = event.IsChecked();