2 /// \file ConflictDlg.cc
3 /// The dialog used during a sync, to display conflicting
4 /// changes, and let the user decide what to do.
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"
29 #include <wx/statline.h>
34 //////////////////////////////////////////////////////////////////////////////
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
)
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"))
96 , m_supported_commands(supported_commands
)
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 -
107 5 * 2 * m_changes
.size())
110 // first, parse all change data
113 // create a global set of key names that contain differing data
115 CreateDifferingKeyNameSet();
121 ConflictDlg::~ConflictDlg()
125 void ConflictDlg::ParseChanges()
129 // create an xmlmap to start with, so we only parse the
132 oss
<< m_engine
.GetEngineName() << "/xmlmap";
133 tr1::shared_ptr
<XmlNodeMap
> basemap
;
135 basemap
.reset(new XmlNodeMap(GetBaseFilename(oss
.str())) );
137 catch( std::runtime_error
&e
) {
141 for( size_t i
= 0; i
< m_changes
.size(); i
++ ) {
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()
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
);
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
);
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
);
198 if( !next
|| *first
!= *next
) {
199 m_differing_keys
.insert(*i
);
207 // +-barry-sync---------+ +-evo2-sync----------+ +-google-sync------+
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
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();
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
290 for( ; nmi
!= nme
; ++nmi
) {
291 if( !IsDifferent(*nmi
) )
294 AddMapping(box
, *nmi
, true);
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
);
313 box
->Add( new wxButton(this,
314 Dialog_Conflict_ShowButton1
+ change_index
,
316 wxDefaultPosition
, wxDefaultSize
,
318 0, wxTOP
| wxLEFT
| wxRIGHT
, 3);
319 box
->Add( new wxButton(this,
320 Dialog_Conflict_SelectButton1
+ change_index
,
322 wxDefaultPosition
, wxDefaultSize
,
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
);
344 sizer
->Add( text
, 0, wxEXPAND
, 0);
347 void ConflictDlg::AddMapping(wxSizer
*sizer
,
348 XmlNodeMapping
&mapping
,
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 ) {
372 text
->SetFont(priority_changed
);
374 text
->SetFont(priority_font
);
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
,
401 if( m_supported_commands
.find('I') != string::npos
) {
402 box
->Add( new wxButton(this, Dialog_Conflict_IgnoreButton
,
407 if( m_supported_commands
.find('N') != string::npos
) {
408 box
->Add( new wxButton(this, Dialog_Conflict_KeepNewerButton
,
413 if( m_supported_commands
.find('A') != string::npos
) {
414 box
->Add( new wxButton(this, Dialog_Conflict_AbortButton
,
419 // no abort available, so add a Kill Sync button
420 box
->Add( new wxButton(this, Dialog_Conflict_KillSyncButton
,
425 sizer
->Add(box
, 0, wxEXPAND
| wxALL
, 10);
429 //////////////////////////////////////////////////////////////////////////////
432 void ConflictDlg::clear()
434 m_command_string
.clear();
438 int ConflictDlg::ShowModal()
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
) {
451 oss
<< "S " << m_changes
[i
].id
;
452 m_command_string
= oss
.str();
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
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
) {
466 oss
<< "S " << m_changes
[i
].id
;
467 m_command_string
= oss
.str();
473 m_command_string
= m_always
.m_last_command
;
478 // ok, no "always" data that works, so ask the user
480 ret
= wxDialog::ShowModal();
481 } while( m_command_string
.size() == 0 && !m_kill_sync
);
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 );
500 box
->SetSizeHints(&dlg
);
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
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();
551 EndModal(event
.GetId());
554 void ConflictDlg::OnAlwaysCheckbox(wxCommandEvent
&event
)
556 m_always
.m_always
= event
.IsChecked();