Bumped copyright dates for 2013
[barry.git] / desktop / src / ConflictDlg.cc
blob790c2d94fbc3157f3076f81ce0a1b6328c83d0ca
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-2013, 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 #include "wxi18n.h"
32 using namespace std;
34 //////////////////////////////////////////////////////////////////////////////
35 // ConflictDlg class
37 BEGIN_EVENT_TABLE(ConflictDlg, wxDialog)
38 EVT_BUTTON (Dialog_Conflict_ShowButton1,
39 ConflictDlg::OnShowButton)
40 EVT_BUTTON (Dialog_Conflict_ShowButton2,
41 ConflictDlg::OnShowButton)
42 EVT_BUTTON (Dialog_Conflict_ShowButton3,
43 ConflictDlg::OnShowButton)
44 EVT_BUTTON (Dialog_Conflict_ShowButton4,
45 ConflictDlg::OnShowButton)
46 EVT_BUTTON (Dialog_Conflict_ShowButton5,
47 ConflictDlg::OnShowButton)
48 EVT_BUTTON (Dialog_Conflict_ShowButton6,
49 ConflictDlg::OnShowButton)
50 EVT_BUTTON (Dialog_Conflict_ShowButton7,
51 ConflictDlg::OnShowButton)
52 EVT_BUTTON (Dialog_Conflict_ShowButton8,
53 ConflictDlg::OnShowButton)
54 EVT_BUTTON (Dialog_Conflict_ShowButton9,
55 ConflictDlg::OnShowButton)
56 EVT_BUTTON (Dialog_Conflict_SelectButton1,
57 ConflictDlg::OnSelectButton)
58 EVT_BUTTON (Dialog_Conflict_SelectButton2,
59 ConflictDlg::OnSelectButton)
60 EVT_BUTTON (Dialog_Conflict_SelectButton3,
61 ConflictDlg::OnSelectButton)
62 EVT_BUTTON (Dialog_Conflict_SelectButton4,
63 ConflictDlg::OnSelectButton)
64 EVT_BUTTON (Dialog_Conflict_SelectButton5,
65 ConflictDlg::OnSelectButton)
66 EVT_BUTTON (Dialog_Conflict_SelectButton6,
67 ConflictDlg::OnSelectButton)
68 EVT_BUTTON (Dialog_Conflict_SelectButton7,
69 ConflictDlg::OnSelectButton)
70 EVT_BUTTON (Dialog_Conflict_SelectButton8,
71 ConflictDlg::OnSelectButton)
72 EVT_BUTTON (Dialog_Conflict_SelectButton9,
73 ConflictDlg::OnSelectButton)
74 EVT_BUTTON (Dialog_Conflict_DuplicateButton,
75 ConflictDlg::OnDuplicateButton)
76 EVT_BUTTON (Dialog_Conflict_AbortButton,
77 ConflictDlg::OnAbortButton)
78 EVT_BUTTON (Dialog_Conflict_IgnoreButton,
79 ConflictDlg::OnIgnoreButton)
80 EVT_BUTTON (Dialog_Conflict_KeepNewerButton,
81 ConflictDlg::OnKeepNewerButton)
82 EVT_BUTTON (Dialog_Conflict_KillSyncButton,
83 ConflictDlg::OnKillSyncButton)
84 EVT_CHECKBOX (Dialog_Conflict_AlwaysCheckbox,
85 ConflictDlg::OnAlwaysCheckbox)
86 END_EVENT_TABLE()
88 ConflictDlg::ConflictDlg(wxWindow *parent,
89 const OpenSync::API &engine,
90 const std::string &supported_commands,
91 const std::vector<OpenSync::SyncChange> &changes,
92 ConflictDlg::AlwaysMemoryBlock &always)
93 : wxDialog(parent, Dialog_Conflict, _W("Sync Conflict"))
94 , m_engine(engine)
95 , m_changes(changes)
96 , m_supported_commands(supported_commands)
97 , m_always(always)
98 , m_kill_sync(false)
99 , m_topsizer(0)
101 // the max text width is the maximum width that any line of text
102 // in a conflict summary can use... it is calculated with
103 // (screen_width - window_border_width*2) / change_count
104 m_max_text_width = (wxSystemSettings::GetMetric(wxSYS_SCREEN_X) -
105 wxSystemSettings::GetMetric(wxSYS_BORDER_X) * 2 -
106 10 * 2 -
107 5 * 2 * m_changes.size())
108 / m_changes.size();
110 // first, parse all change data
111 ParseChanges();
113 // create a global set of key names that contain differing data
114 // between changes
115 CreateDifferingKeyNameSet();
117 // setup the raw GUI
118 CreateLayout();
121 ConflictDlg::~ConflictDlg()
125 void ConflictDlg::ParseChanges()
127 m_maps.clear();
129 // create an xmlmap to start with, so we only parse the
130 // map file once
131 ostringstream oss;
132 oss << m_engine.GetEngineName() << "/xmlmap";
133 tr1::shared_ptr<XmlNodeMap> basemap;
134 try {
135 basemap.reset(new XmlNodeMap(GetBaseFilename(oss.str())) );
137 catch( std::runtime_error &e ) {
138 basemap.reset();
141 for( size_t i = 0; i < m_changes.size(); i++ ) {
142 XmlPair xp;
143 xp.dom.reset( new xmlpp::DomParser );
144 xp.dom->parse_memory_raw(
145 (const unsigned char*) m_changes[i].printable_data.data(),
146 m_changes[i].printable_data.size());
148 if( basemap.get() ) {
149 xp.map.reset( new XmlNodeMap(*basemap) );
150 xp.map->ImportNodes(xp.dom->get_document()->get_root_node());
151 xp.map->PurgeEmpties();
152 xp.map->SortBySummary();
155 m_maps.push_back(xp);
159 void ConflictDlg::CreateDifferingKeyNameSet()
161 // start fresh
162 m_differing_keys.clear();
164 if( !m_maps.size() || !m_maps[0].map.get() )
165 return; // nothing to do
167 // find a list of all available key names
168 key_set all_keys; // set of all keys names from all xmlmaps
169 for( size_t i = 0; i < m_maps.size(); i++ ) {
170 XmlNodeMap::iterator mi = m_maps[i].map->begin(),
171 me = m_maps[i].map->end();
173 for( ; mi != me; ++mi ) {
174 all_keys.insert(mi->KeyName());
179 // cycle through all keys and find ones that have changes
180 for( key_set::iterator i = all_keys.begin(); i!=all_keys.end(); ++i ) {
182 // find the mapping from the first nodemap
183 XmlNodeMapping *first = m_maps[0].map->Find(*i);
184 if( !first ) {
185 // if a key does not exist in this map, then
186 // it does in another, and therefore is "differing"
187 m_differing_keys.insert(*i);
188 continue;
191 // cycle through all remaining nodemaps, find the mapping that
192 // matches the keyname, and compare... if any do not
193 // exist, or do not match, add to the differing keys set
194 for( size_t j = 1; j < m_maps.size(); j++ ) {
195 XmlNodeMapping *next = m_maps[j].map->Find(*i);
197 // compare!
198 if( !next || *first != *next ) {
199 m_differing_keys.insert(*i);
200 break;
207 // +-barry-sync---------+ +-evo2-sync----------+ +-google-sync------+
208 // | | | | | |
209 // | Adam Brandee | | Adam Brandee | | Adam Brandee |
210 // | IBM Canada | | | | IBM Canada | < red
211 // | 1-519-555-1212 | | 1-519-555-1212 | | 1-519-555-1111 | < red
212 // | abramble@ibm.com | | abramble@ibm.com | | abramble@ibm.com |
213 // | ---- | | ---- | | ---- |
214 // | 123 Big Street | | 123 Big Street | | 123 Long St. | < red
215 // | | | | | Canada | < red
216 // +--------------------+ +--------------------+ +------------------+
218 // [See XML] [Select] [See XML] [Select] [See XML] [Select]
221 // At the bottom of the dialog, display the rest of the optional buttons,
222 // like Duplicate, Abort, etc. Also include a checkbox for "always"...
223 // figure out the best way to handle "always" selections... always
224 // change #1?
226 // If converting an XML change's data to a hash map throws an exception
227 // somewhere in the xmlpp::DomParser, then that change will have to
228 // be handled in a raw manner. I don't think any changes can be
229 // displayed in a table like above, but each should get a scrolling
230 // edit control with the raw data included.
232 // If possible, take the default table font and reduce it by 20% or
233 // something, because this table will likely hold a lot of data.
235 void ConflictDlg::CreateLayout()
237 m_topsizer = new wxBoxSizer(wxVERTICAL);
238 CreateSummaries(m_topsizer);
239 CreateAlternateButtons(m_topsizer);
241 SetSizer(m_topsizer);
242 m_topsizer->SetSizeHints(this);
243 m_topsizer->Layout();
246 void ConflictDlg::CreateSummaries(wxSizer *sizer)
248 // this sizer is the horizontal box containing one
249 // "map summary" each
250 wxBoxSizer *box = new wxBoxSizer(wxHORIZONTAL);
252 for( size_t i = 0; i < m_maps.size(); i++ ) {
253 CreateSummary(box, i);
256 sizer->Add(box, 0, wxEXPAND | wxALL, 5);
259 void ConflictDlg::CreateSummary(wxSizer *sizer, size_t change_index)
261 // this sizer contains the summary box in the top and the
262 // buttons in the bottom
263 wxBoxSizer *box = new wxBoxSizer(wxVERTICAL);
265 CreateSummaryGroup(box, change_index);
267 sizer->Add(box, 1, wxEXPAND | wxLEFT | wxRIGHT, 5);
270 void ConflictDlg::CreateSummaryGroup(wxSizer *sizer, size_t change_index)
272 wxString name(m_changes[change_index].plugin_name.c_str(), wxConvUTF8);
273 wxStaticBoxSizer *box = new wxStaticBoxSizer(wxVERTICAL, this, name);
275 XmlNodeMap *xml = m_maps[change_index].map.get();
276 if( xml ) {
277 // add all priority 0 mappings
278 XmlNodeMap::iterator nmi = xml->begin(), nme = xml->priority_end();
279 for( ; nmi != nme; ++nmi ) {
280 AddMapping(box, *nmi, IsDifferent(*nmi));
283 // add small separator (line?)
284 box->Add( new wxStaticLine(this), 0, wxEXPAND | wxALL, 5);
286 // add all differing mappings, in the map order, to preserve
287 // the map file's user-friendly display order
288 nmi = nme; // start at priority_end() to skip priority 0
289 nme = xml->end();
290 for( ; nmi != nme; ++nmi ) {
291 if( !IsDifferent(*nmi) )
292 continue;
294 AddMapping(box, *nmi, true);
297 else {
298 AddEmptyNotice(box);
301 box->Add(0, 10, 0);
302 box->Add(0, 0, 1);
303 CreateSummaryButtons(box, change_index);
305 sizer->Add(box, 1, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 0);
308 void ConflictDlg::CreateSummaryButtons(wxSizer *sizer, size_t change_index)
310 wxBoxSizer *box = new wxBoxSizer(wxHORIZONTAL);
312 box->Add(0, 0, 1);
313 box->Add( new wxButton(this,
314 Dialog_Conflict_ShowButton1 + change_index,
315 _W("XML..."),
316 wxDefaultPosition, wxDefaultSize,
317 wxBU_EXACTFIT),
318 0, wxTOP | wxLEFT | wxRIGHT, 3);
319 box->Add( new wxButton(this,
320 Dialog_Conflict_SelectButton1 + change_index,
321 _W("Select"),
322 wxDefaultPosition, wxDefaultSize,
323 wxBU_EXACTFIT),
324 0, wxTOP | wxLEFT | wxRIGHT, 3);
326 sizer->Add(box, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 0);
329 bool ConflictDlg::IsDifferent(const XmlNodeMapping &mapping) const
331 return m_differing_keys.find(mapping.KeyName()) != m_differing_keys.end();
334 void ConflictDlg::AddEmptyNotice(wxSizer *sizer)
336 wxFont font = GetFont();
337 font.SetStyle( wxFONTSTYLE_ITALIC );
339 wxStaticText *text = new wxStaticText(this, wxID_ANY,
340 _W("No XML map found."),
341 wxDefaultPosition, wxDefaultSize,
342 wxALIGN_CENTRE | wxST_NO_AUTORESIZE);
343 text->SetFont(font);
344 sizer->Add( text, 0, wxEXPAND, 0);
347 void ConflictDlg::AddMapping(wxSizer *sizer,
348 XmlNodeMapping &mapping,
349 bool differing)
351 // display differing text in red? Not sure how to do that... using italic
353 // use a big bold font for the high priority items
354 wxFont priority_font = GetFont();
355 priority_font.SetWeight( wxFONTWEIGHT_BOLD );
357 // italic for changed items
358 wxFont priority_changed = priority_font;
359 priority_changed.SetStyle( wxFONTSTYLE_ITALIC );
361 wxFont changed = GetFont();
362 changed.SetStyle( wxFONTSTYLE_ITALIC );
364 for( size_t i = 0; i < mapping.size(); i++ ) {
365 wxString data(mapping[i].Summary().raw().c_str(), wxConvUTF8);
367 wxStaticText *text = new wxStaticText(this, wxID_ANY, data,
368 wxDefaultPosition, wxDefaultSize,
369 wxALIGN_CENTRE | wxST_NO_AUTORESIZE);
370 if( mapping.Priority() == 0 ) {
371 if( differing )
372 text->SetFont(priority_changed);
373 else
374 text->SetFont(priority_font);
376 else {
377 if( differing )
378 text->SetFont(changed);
380 text->Wrap(m_max_text_width);
382 sizer->Add( text, 0, wxEXPAND, 0);
386 void ConflictDlg::CreateAlternateButtons(wxSizer *sizer)
388 wxBoxSizer *box = new wxBoxSizer(wxHORIZONTAL);
390 box->Add( new wxCheckBox(this, Dialog_Conflict_AlwaysCheckbox,
391 _W("Always use this choice")),
392 0, wxALIGN_CENTRE, 0);
393 box->Add( -1, -1, 1 );
395 if( m_supported_commands.find('D') != string::npos ) {
396 box->Add( new wxButton(this, Dialog_Conflict_DuplicateButton,
397 _W("Duplicate")),
398 0, wxLEFT , 5);
401 if( m_supported_commands.find('I') != string::npos ) {
402 box->Add( new wxButton(this, Dialog_Conflict_IgnoreButton,
403 _W("Ignore")),
404 0, wxLEFT, 5);
407 if( m_supported_commands.find('N') != string::npos ) {
408 box->Add( new wxButton(this, Dialog_Conflict_KeepNewerButton,
409 _W("Keep Newer")),
410 0, wxLEFT, 5);
413 if( m_supported_commands.find('A') != string::npos ) {
414 box->Add( new wxButton(this, Dialog_Conflict_AbortButton,
415 _W("Abort")),
416 0, wxLEFT, 5);
418 else {
419 // no abort available, so add a Kill Sync button
420 box->Add( new wxButton(this, Dialog_Conflict_KillSyncButton,
421 _W("Kill Sync")),
422 0, wxLEFT, 5);
425 sizer->Add(box, 0, wxEXPAND | wxALL, 10);
429 //////////////////////////////////////////////////////////////////////////////
430 // public members
432 void ConflictDlg::clear()
434 m_command_string.clear();
435 m_kill_sync = false;
438 int ConflictDlg::ShowModal()
440 int ret = 0;
442 // start fresh
443 clear();
445 // is there a favoured plugin name?
446 if( m_always.m_favour_plugin_name.size() ) {
447 // find the matching plugin name
448 for( size_t i = 0; i < m_changes.size(); i++ ) {
449 if( m_changes[i].plugin_name == m_always.m_favour_plugin_name ) {
450 ostringstream oss;
451 oss << "S " << m_changes[i].id;
452 m_command_string = oss.str();
453 return ret;
458 // is there an "always" answer we can use?
459 if( m_always.m_always ) {
460 if( m_always.m_last_command == "S" ) {
461 // find the change that has a matching member_id
462 // and plugin_name
463 for( size_t i = 0; i < m_changes.size(); i++ ) {
464 if( m_changes[i].member_id == m_always.m_member_id && m_changes[i].plugin_name == m_always.m_plugin_name ) {
465 ostringstream oss;
466 oss << "S " << m_changes[i].id;
467 m_command_string = oss.str();
468 return ret;
472 else {
473 m_command_string = m_always.m_last_command;
474 return ret; // done
478 // ok, no "always" data that works, so ask the user
479 do {
480 ret = wxDialog::ShowModal();
481 } while( m_command_string.size() == 0 && !m_kill_sync );
483 return ret;
486 void ConflictDlg::OnShowButton(wxCommandEvent &event)
488 int index = event.GetId() - Dialog_Conflict_ShowButton1;
489 wxString xmldata(m_changes[index].printable_data.c_str(), wxConvUTF8);
491 // let's try this the quick manual way...
492 wxDialog dlg(this, wxID_ANY, _W("Raw Change Data"));
493 wxBoxSizer *box = new wxBoxSizer(wxVERTICAL);
494 box->Add( new wxTextCtrl(&dlg, wxID_ANY, xmldata,
495 wxDefaultPosition, wxSize(400, 400),
496 wxTE_MULTILINE | wxTE_READONLY),
497 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
498 box->Add( dlg.CreateButtonSizer(wxOK), 0, wxEXPAND | wxALL, 10 );
499 dlg.SetSizer(box);
500 box->SetSizeHints(&dlg);
501 box->Layout();
502 dlg.ShowModal();
505 void ConflictDlg::OnSelectButton(wxCommandEvent &event)
507 int index = event.GetId() - Dialog_Conflict_SelectButton1;
509 // store command in m_always
510 m_always.m_member_id = m_changes[index].member_id;
511 m_always.m_plugin_name = m_changes[index].plugin_name;
512 m_always.m_last_command = "S";
514 // build actual command
515 ostringstream oss;
516 oss << "S " << m_changes[index].id;
517 m_command_string = oss.str();
519 EndModal(event.GetId());
522 void ConflictDlg::OnDuplicateButton(wxCommandEvent &event)
524 m_command_string = m_always.m_last_command = "D";
525 EndModal(event.GetId());
528 void ConflictDlg::OnAbortButton(wxCommandEvent &event)
530 m_command_string = m_always.m_last_command = "A";
531 EndModal(event.GetId());
534 void ConflictDlg::OnIgnoreButton(wxCommandEvent &event)
536 m_command_string = m_always.m_last_command = "I";
537 EndModal(event.GetId());
540 void ConflictDlg::OnKeepNewerButton(wxCommandEvent &event)
542 m_command_string = m_always.m_last_command = "N";
543 EndModal(event.GetId());
546 void ConflictDlg::OnKillSyncButton(wxCommandEvent &event)
548 m_command_string.clear();
549 m_always.m_last_command.clear();
550 m_kill_sync = true;
551 EndModal(event.GetId());
554 void ConflictDlg::OnAlwaysCheckbox(wxCommandEvent &event)
556 m_always.m_always = event.IsChecked();