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-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"
29 #include <wx/statline.h>
32 //////////////////////////////////////////////////////////////////////////////
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
)
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"))
94 , m_supported_commands(supported_commands
)
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 -
105 5 * 2 * m_changes
.size())
108 // first, parse all change data
111 // create a global set of key names that contain differing data
113 CreateDifferingKeyNameSet();
119 ConflictDlg::~ConflictDlg()
123 void ConflictDlg::ParseChanges()
127 // create an xmlmap to start with, so we only parse the
130 oss
<< m_engine
.GetEngineName() << "/xmlmap";
131 tr1::shared_ptr
<XmlNodeMap
> basemap
;
133 basemap
.reset(new XmlNodeMap(GetBaseFilename(oss
.str())) );
135 catch( std::runtime_error
&e
) {
139 for( size_t i
= 0; i
< m_changes
.size(); i
++ ) {
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()
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
);
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
);
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
);
196 if( !next
|| *first
!= *next
) {
197 m_differing_keys
.insert(*i
);
205 // +-barry-sync---------+ +-evo2-sync----------+ +-google-sync------+
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
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();
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
288 for( ; nmi
!= nme
; ++nmi
) {
289 if( !IsDifferent(*nmi
) )
292 AddMapping(box
, *nmi
, true);
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
);
311 box
->Add( new wxButton(this,
312 Dialog_Conflict_ShowButton1
+ change_index
,
314 wxDefaultPosition
, wxDefaultSize
,
316 0, wxTOP
| wxLEFT
| wxRIGHT
, 3);
317 box
->Add( new wxButton(this,
318 Dialog_Conflict_SelectButton1
+ change_index
,
320 wxDefaultPosition
, wxDefaultSize
,
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
);
342 sizer
->Add( text
, 0, wxEXPAND
, 0);
345 void ConflictDlg::AddMapping(wxSizer
*sizer
,
346 XmlNodeMapping
&mapping
,
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 ) {
370 text
->SetFont(priority_changed
);
372 text
->SetFont(priority_font
);
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
,
399 if( m_supported_commands
.find('I') != string::npos
) {
400 box
->Add( new wxButton(this, Dialog_Conflict_IgnoreButton
,
405 if( m_supported_commands
.find('N') != string::npos
) {
406 box
->Add( new wxButton(this, Dialog_Conflict_KeepNewerButton
,
411 if( m_supported_commands
.find('A') != string::npos
) {
412 box
->Add( new wxButton(this, Dialog_Conflict_AbortButton
,
417 // no abort available, so add a Kill Sync button
418 box
->Add( new wxButton(this, Dialog_Conflict_KillSyncButton
,
423 sizer
->Add(box
, 0, wxEXPAND
| wxALL
, 10);
427 //////////////////////////////////////////////////////////////////////////////
430 void ConflictDlg::clear()
432 m_command_string
.clear();
436 int ConflictDlg::ShowModal()
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
) {
449 oss
<< "S " << m_changes
[i
].id
;
450 m_command_string
= oss
.str();
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
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
) {
464 oss
<< "S " << m_changes
[i
].id
;
465 m_command_string
= oss
.str();
471 m_command_string
= m_always
.m_last_command
;
476 // ok, no "always" data that works, so ask the user
478 ret
= wxDialog::ShowModal();
479 } while( m_command_string
.size() == 0 && !m_kill_sync
);
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 );
498 box
->SetSizeHints(&dlg
);
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
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();
549 EndModal(event
.GetId());
552 void ConflictDlg::OnAlwaysCheckbox(wxCommandEvent
&event
)
554 m_always
.m_always
= event
.IsChecked();