2 /// \file Mode_Browse.cc
3 /// Mode derived class for database browsing
7 Copyright (C) 2011-2013, Net Direct Inc. (http://www.netdirect.ca/)
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 See the GNU General Public License in the COPYING file at the
19 root directory of this project for more details.
22 #include "Mode_Browse.h"
23 #include "BaseFrame.h"
24 #include "ContactEditDlg.h"
25 #include "CalendarEditDlg.h"
26 #include "MemoEditDlg.h"
27 #include "TaskEditDlg.h"
28 #include "MimeExportDlg.h"
29 #include "windowids.h"
35 using namespace Barry
;
37 DEFINE_EVENT_TYPE(BMET_LOAD_STATUS
)
39 BEGIN_EVENT_TABLE(BrowseMode
, wxEvtHandler
)
40 EVT_LIST_ITEM_SELECTED(BrowseMode_DBDBList
,
41 BrowseMode::OnDBDBListSelChange
)
42 EVT_LIST_ITEM_SELECTED(BrowseMode_RecordList
,
43 BrowseMode::OnRecordListSelChange
)
44 EVT_LIST_ITEM_ACTIVATED(BrowseMode_RecordList
,
45 BrowseMode::OnRecordListActivated
)
46 EVT_CHECKBOX (BrowseMode_ShowAllCheckbox
,
47 BrowseMode::OnShowAll
)
48 EVT_BUTTON (BrowseMode_ImportRecordButton
,
49 BrowseMode::OnImportRecord
)
50 EVT_BUTTON (BrowseMode_ExportRecordButton
,
51 BrowseMode::OnExportRecord
)
52 EVT_BUTTON (BrowseMode_AddRecordButton
,
53 BrowseMode::OnAddRecord
)
54 EVT_BUTTON (BrowseMode_CopyRecordButton
,
55 BrowseMode::OnCopyRecord
)
56 EVT_BUTTON (BrowseMode_EditRecordButton
,
57 BrowseMode::OnEditRecord
)
58 EVT_BUTTON (BrowseMode_DeleteRecordButton
,
59 BrowseMode::OnDeleteRecord
)
60 EVT_COMMAND (wxID_ANY
, BMET_LOAD_STATUS
,
61 BrowseMode::OnStatusEvent
)
65 //////////////////////////////////////////////////////////////////////////////
66 // Standalone functions
68 void ShowReadOnlyMsg(wxWindow
*parent
, const Barry::ReturnCodeError
&rce
)
70 wxString
what(rce
.what(), wxConvUTF8
);
71 wxMessageBox(_W("This database is apparently read-only. If this device is connected to a BES, you cannot edit records via USB. (Error: ") + what
+ _T(")"),
72 _W("Device Error"), wxOK
| wxICON_ERROR
, parent
);
75 bool IsEditable(const std::string
&dbname
)
77 // add entry here for each edit dialog available
78 return dbname
== Contact::GetDBName() ||
79 dbname
== Calendar::GetDBName() ||
80 dbname
== Memo::GetDBName() ||
81 dbname
== Task::GetDBName();
84 bool IsCardable(const std::string
&dbname
, std::string
*file_types
= 0);
85 bool IsCardable(const std::string
&dbname
, std::string
*file_types
)
87 // add entry here for each record that can handle MIME style
89 if( dbname
== Contact::GetDBName() ) {
92 // TRANSLATORS: this is a file selector string,
93 // see "Image files" for more info.
94 *file_types
= _C("VCard files");
95 *file_types
+= " (*.vcf;*.vcard)|*.vcf;*.vcard";
100 else if( dbname
== Calendar::GetDBName() ||
101 dbname
== Memo::GetDBName() ||
102 dbname
== Task::GetDBName() ) {
105 // TRANSLATORS: this is a file selector string,
106 // see "Image files" for more info.
107 *file_types
= _C("ICalendar files");
108 *file_types
+= " (*.ical;*.ics;*.ifb;*.icalendar)|*.ical;*.ics;*.ifb;*.icalendar";
117 bool EditRecord(wxWindow
*parent
,
119 const Barry::TimeZones
&zones
,
122 ContactEditDlg
edit(parent
, rec
, editable
);
123 return edit
.ShowModal() == wxID_OK
;
126 bool EditRecord(wxWindow
*parent
,
128 const Barry::TimeZones
&zones
,
129 Barry::Bookmark
&rec
)
134 bool EditRecord(wxWindow
*parent
,
136 const Barry::TimeZones
&zones
,
137 Barry::Calendar
&rec
)
139 CalendarEditDlg
edit(parent
, rec
, editable
, &zones
);
140 return edit
.ShowModal() == wxID_OK
;
143 bool EditRecord(wxWindow
*parent
,
145 const Barry::TimeZones
&zones
,
146 Barry::CalendarAll
&rec
)
151 bool EditRecord(wxWindow
*parent
,
153 const Barry::TimeZones
&zones
,
154 Barry::ContentStore
&rec
)
159 bool EditRecord(wxWindow
*parent
,
161 const Barry::TimeZones
&zones
,
167 bool EditRecord(wxWindow
*parent
,
169 const Barry::TimeZones
&zones
,
172 MemoEditDlg
edit(parent
, rec
, editable
);
173 return edit
.ShowModal() == wxID_OK
;
176 bool EditRecord(wxWindow
*parent
,
178 const Barry::TimeZones
&zones
,
184 bool EditRecord(wxWindow
*parent
,
186 const Barry::TimeZones
&zones
,
192 bool EditRecord(wxWindow
*parent
,
194 const Barry::TimeZones
&zones
,
195 Barry::PINMessage
&rec
)
200 bool EditRecord(wxWindow
*parent
,
202 const Barry::TimeZones
&zones
,
203 Barry::SavedMessage
&rec
)
208 bool EditRecord(wxWindow
*parent
,
210 const Barry::TimeZones
&zones
,
211 Barry::ServiceBook
&rec
)
216 bool EditRecord(wxWindow
*parent
,
218 const Barry::TimeZones
&zones
,
224 bool EditRecord(wxWindow
*parent
,
226 const Barry::TimeZones
&zones
,
229 TaskEditDlg
edit(parent
, rec
, editable
, &zones
);
230 return edit
.ShowModal() == wxID_OK
;
233 bool EditRecord(wxWindow
*parent
,
235 const Barry::TimeZones
&zones
,
236 Barry::TimeZone
&rec
)
241 bool EditRecord(wxWindow
*parent
,
243 const Barry::TimeZones
&zones
,
244 Barry::HandheldAgent
&rec
)
250 //////////////////////////////////////////////////////////////////////////////
251 // GUIDesktopConnector
253 bool GUIDesktopConnector::PasswordPrompt(const Barry::BadPassword
&bp
,
254 std::string
&password_result
)
256 // create prompt based on exception data
257 wxString prompt
= wxString::Format(
258 _W("Please enter device password: (%d tries remaining)"),
259 bp
.remaining_tries());
261 // ask user for device password
262 wxString pass
= wxGetPasswordFromUser(prompt
,
263 _W("Device Password"), _T(""), m_parent
);
265 password_result
= pass
.utf8_str();
267 // assume that a blank password means the user wishes to quit...
268 // wxWidgets doesn't seem to handle this very well?
269 return password_result
.size() > 0;
272 //////////////////////////////////////////////////////////////////////////////
275 DBDataCache::DBDataCache(DataCache::IndexType index
, const Barry::DBData
&raw
)
276 : DataCache(index
, raw
.GetUniqueId())
281 bool DBDataCache::Edit(wxWindow
*parent
,
283 const Barry::TimeZones
&zones
)
288 std::string
DBDataCache::GetDescription() const
290 return _C("raw data");
294 //////////////////////////////////////////////////////////////////////////////
297 DBCache::DBCache(ThreadableDesktop
&tdesktop
, const std::string
&dbname
)
298 : m_tdesktop(tdesktop
)
302 DesktopInstancePtr dip
= m_tdesktop
.Get();
305 m_dbid
= dip
->Desktop().GetDBID(m_dbname
);
307 // load the record state table
308 dip
->Desktop().GetRecordStateTable(m_dbid
, m_state
);
311 AllRecordParser
ap(*this, *this);
312 RecordStateTable::StateMapType::iterator i
= m_state
.StateMap
.begin();
313 for( ; i
!= m_state
.StateMap
.end(); ++i
) {
314 m_index
= i
->second
.Index
; // save for the callback
315 dip
->Desktop().GetRecord(m_dbid
, m_index
, ap
);
318 // sort the list of records by description
326 DBCache::iterator
DBCache::Get(int list_offset
)
328 iterator i
= begin();
329 for( ; i
!= end() && list_offset
; ++i
, list_offset
-- )
334 DBCache::const_iterator
DBCache::Get(int list_offset
) const
336 const_iterator i
= begin();
337 for( ; i
!= end() && list_offset
; ++i
, list_offset
-- )
342 int DBCache::GetIndex(iterator record
) const
344 iterator i
= const_cast<DBCache
*> (this)->begin();
345 iterator e
= const_cast<DBCache
*> (this)->end();
346 for( int index
= 0; i
!= e
; ++i
, index
++ ) {
353 int DBCache::GetIndex(const_iterator record
) const
355 const_iterator i
= begin();
356 for( int index
= 0; i
!= end(); ++i
, index
++ ) {
363 DBCache::iterator
DBCache::Add(wxWindow
*parent
, DataCachePtr p
)
365 // see if this record has a builder
366 Barry::Builder
*bp
= dynamic_cast<Barry::Builder
*> (p
.get());
368 cerr
<< _C("DataCachePtr has no builder") << endl
;
372 // give record a new UniqueID
373 uint32_t record_id
= m_state
.MakeNewRecordId();
374 cout
<< "New recordID generated: 0x" << hex
<< record_id
<< endl
;
375 p
->SetIds(p
->GetStateIndex(), record_id
);
377 // add record to device
378 DesktopInstancePtr dip
= m_tdesktop
.Get();
379 Barry::Mode::Desktop
&desktop
= dip
->Desktop();
380 bool iv
= Barry::IsVerbose();
381 Barry::Verbose(true);
383 desktop
.AddRecord(m_dbid
, *bp
);
384 } catch( Barry::ReturnCodeError
&rce
) {
385 cerr
<< _C("Device exception: ") << rce
.what() << endl
;
386 if( rce
.IsReadOnly() ) {
387 ShowReadOnlyMsg(parent
, rce
);
395 // update our copy of the record state table from device
396 desktop
.GetRecordStateTable(m_dbid
, m_state
);
397 cout
<< m_state
<< endl
;
399 // find our new record_id in list, to find the state index
401 if( !m_state
.GetIndex(record_id
, &new_index
) ) {
402 throw std::logic_error("Need to reconnect for adding a record?");
405 // update new state_index in the data cache record
406 p
->SetIds(new_index
, record_id
);
408 // add DataCachePtr to our own cache list
409 m_records
.push_front(p
);
411 // return iterator pointing to new record
415 DBCache::iterator
DBCache::Add(wxWindow
*parent
,
416 const Barry::TimeZones
&zones
,
417 iterator copy_record
)
421 #undef HANDLE_BUILDER
422 #define HANDLE_BUILDER(tname) \
423 if( m_dbname == Barry::tname::GetDBName() ) { \
425 if( copy_record != end() ) { \
426 RecordCache<Barry::tname> *rc = dynamic_cast<RecordCache<Barry::tname>* > (copy_record->get()); \
428 rec = rc->GetRecord(); \
431 p.reset( new RecordCache<Barry::tname>(0, rec) ); \
433 ALL_KNOWN_BUILDER_TYPES
435 // anything else is not addable or buildable
440 if( p
->Edit(parent
, true, zones
) ) {
441 return Add(parent
, p
);
448 bool DBCache::OverwriteRecord(wxWindow
*parent
, iterator record
)
450 // see if this record has a builder
451 Barry::Builder
*bp
= dynamic_cast<Barry::Builder
*> ((*record
).get());
455 cout
<< "Changing device record with index: 0x" << hex
<< (*record
)->GetStateIndex() << endl
;
456 cout
<< m_state
<< endl
;
457 // update the device with new record data
458 DesktopInstancePtr dip
= m_tdesktop
.Get();
459 Barry::Mode::Desktop
&desktop
= dip
->Desktop();
460 bool iv
= Barry::IsVerbose();
461 Barry::Verbose(true);
463 desktop
.SetRecord(m_dbid
, (*record
)->GetStateIndex(), *bp
);
464 } catch( Barry::ReturnCodeError
&rce
) {
465 if( rce
.IsReadOnly() ) {
466 ShowReadOnlyMsg(parent
, rce
);
477 bool DBCache::DeleteAndAddRecord(wxWindow
*parent
, iterator record
)
479 // see if this record has a builder
480 Barry::Builder
*bp
= dynamic_cast<Barry::Builder
*> ((*record
).get());
484 cout
<< "Changing device record with index: 0x" << hex
<< (*record
)->GetStateIndex() << endl
;
485 cout
<< m_state
<< endl
;
486 // update the device with new record data
487 DesktopInstancePtr dip
= m_tdesktop
.Get();
488 Barry::Mode::Desktop
&desktop
= dip
->Desktop();
489 bool iv
= Barry::IsVerbose();
490 Barry::Verbose(true);
492 desktop
.DeleteRecord(m_dbid
, (*record
)->GetStateIndex());
493 desktop
.AddRecord(m_dbid
, *bp
);
494 } catch( Barry::ReturnCodeError
&rce
) {
495 if( rce
.IsReadOnly() ) {
496 ShowReadOnlyMsg(parent
, rce
);
504 // update our copy of the record state table from device
505 desktop
.GetRecordStateTable(m_dbid
, m_state
);
506 cout
<< m_state
<< endl
;
508 // find our record_id in list, to find the state index
510 if( !m_state
.GetIndex((*record
)->GetRecordId(), &new_index
) ) {
511 throw std::logic_error("DAA: Need to reconnect for adding a record?");
514 // update new state_index in the data cache record
515 (*record
)->SetIds(new_index
, (*record
)->GetRecordId());
520 bool DBCache::Edit(wxWindow
*parent
,
521 const Barry::TimeZones
&zones
,
524 if( record
== end() )
527 if( (*record
)->Edit(parent
, true, zones
) && (*record
)->IsBuildable() ) {
528 // see if this record is part of the Tasks database
529 RecordCache
<Barry::Task
> *tp
= dynamic_cast< RecordCache
<Barry::Task
>*> ((*record
).get());
532 // yes, it is... the Tasks database has a bug
533 // so we need to "edit" by deleting and adding
535 return DeleteAndAddRecord(parent
, record
);
538 // use the normal code for all other records
539 return OverwriteRecord(parent
, record
);
547 bool DBCache::Delete(wxWindow
*parent
, iterator record
)
549 if( record
== end() )
552 // prompt user with Yes / No message
553 wxString
desc((*record
)->GetDescription().c_str(), wxConvUTF8
);
554 int choice
= wxMessageBox(_W("Delete this record?\n ") + desc
,
555 _W("Record Delete"), wxYES_NO
| wxICON_QUESTION
, parent
);
557 // if no, return false
558 if( choice
!= wxYES
)
561 cout
<< "Deleting device record with index: 0x" << hex
<< (*record
)->GetStateIndex() << endl
;
562 cout
<< m_state
<< endl
;
563 // delete record from device
564 DesktopInstancePtr dip
= m_tdesktop
.Get();
565 Barry::Mode::Desktop
&desktop
= dip
->Desktop();
566 desktop
.DeleteRecord(m_dbid
, (*record
)->GetStateIndex());
568 // remove record from cache list
569 m_records
.erase(record
);
573 // For Barry::AllRecordStore
575 #define HANDLE_PARSER(tname) \
576 void DBCache::operator() (const Barry::tname &rec) \
578 DataCachePtr p( new RecordCache<Barry::tname>(m_index, rec) ); \
579 m_records.push_front(p); \
581 ALL_KNOWN_PARSER_TYPES
584 void DBCache::ParseRecord(const Barry::DBData
&data
,
585 const Barry::IConverter
*ic
)
587 DataCachePtr
p( new DBDataCache(m_index
, data
) );
588 m_records
.push_front(p
);
592 //////////////////////////////////////////////////////////////////////////////
595 DBMap::DBMap(ThreadableDesktop
&tdesktop
)
596 : m_tdesktop(tdesktop
)
598 if( pthread_mutex_init(&m_map_mutex
, NULL
) ) {
599 throw Barry::Error(_C("Failed to create map mutex"));
602 if( pthread_mutex_init(&m_load_mutex
, NULL
) ) {
603 throw Barry::Error(_C("Failed to create load mutex"));
607 DBMap::DBCachePtr
DBMap::LoadDBCache(const std::string
&dbname
)
609 // first, check for pre-loaded data, before the load lock,
610 // to make sure we return pre-loaded data with utmost haste
612 scoped_lock
map_lock(m_map_mutex
);
614 MapType::iterator i
= m_map
.find(dbname
);
615 if( i
!= m_map
.end() )
619 // if not found, lock and load, but be careful, since we
620 // don't want to open a window here for loading a db twice
621 scoped_lock
load_lock(m_load_mutex
);
623 // check again for pre-loaded data, since between
624 // map.unlock and load.lock there could have been
625 // another successful load
627 scoped_lock
map_lock(m_map_mutex
);
629 MapType::iterator i
= m_map
.find(dbname
);
630 if( i
!= m_map
.end() )
634 // do the load, without map.lock, since this can take a
635 // significant amount of time
636 DBCachePtr
p( new DBCache(m_tdesktop
, dbname
) );
638 // lock once more to update the map, and then done
639 scoped_lock
map_lock(m_map_mutex
);
644 DBMap::DBCachePtr
DBMap::GetDBCache(const std::string
&dbname
)
646 scoped_lock
lock(m_map_mutex
);
648 MapType::iterator i
= m_map
.find(dbname
);
649 if( i
!= m_map
.end() )
655 //////////////////////////////////////////////////////////////////////////////
658 BrowseMode::BrowseMode(wxWindow
*parent
, const ProbeResult
&device
)
665 // create device identifying string
666 m_device_id_str
= wxString(device
.GetDisplayName().c_str(), wxConvUTF8
);
669 // connect to the device
671 m_con
.reset( new GUIDesktopConnector(m_parent
, "", "utf-8", device
) );
673 m_tdesktop
.reset( new ThreadableDesktop(*m_con
) );
675 // keep our own copy, and sort by name for later
676 m_dbdb
= m_con
->GetDesktop().GetDBDB();
679 // store a copy of the time zone set for record editing
680 if( TimeZones::IsLoadable(m_con
->GetDesktop()) ) {
681 // load time zones from device itself
682 m_zones
.reset( new TimeZones(m_con
->GetDesktop()) );
685 // use static time zone table from Barry library
686 m_zones
.reset( new TimeZones
);
691 // create our DBMap and give it the threadable desktop,
692 // now that we're finished doing any desktop USB work
693 m_dbmap
.reset( new DBMap(*m_tdesktop
) );
696 // From here down, we assume that our constructor succeeds, with
700 // fire off a background thread to cache database records
701 // in advance... if it fails, don't worry about it
702 m_abort_flag
= false;
703 int ret
= pthread_create(&m_cache_thread
, NULL
,
704 &BrowseMode::FillCacheThread
, this);
706 m_abort_flag
= true; // no need to join later
708 // connect ourselves to the parent's event handling chain
709 // do this last, so that we are guaranteed our destructor
710 // will run, in case of exceptions
711 m_parent
->PushEventHandler(this);
714 BrowseMode::~BrowseMode()
716 // unhook that event handler!
717 m_parent
->PopEventHandler();
719 // make sure the cache thread is finished before we destroy it :-)
720 if( !m_abort_flag
) {
723 pthread_join(m_cache_thread
, &junk
);
727 std::string
& GetDBName(Barry::DatabaseDatabase::Database
&db
)
732 void BrowseMode::SendStatusEvent(const std::string
&dbname
)
734 wxCommandEvent
event(BMET_LOAD_STATUS
, wxID_ANY
);
735 event
.SetEventObject(this);
737 if( dbname
.size() ) {
738 wxString
msg(_W("Loading: "));
739 msg
+= wxString(dbname
.c_str(), wxConvUTF8
);
740 event
.SetString(msg
);
743 event
.SetString(_T(""));
746 AddPendingEvent(event
);
749 void BrowseMode::CreateControls()
751 m_top_sizer
.reset( new wxBoxSizer(wxVERTICAL
) );
753 // make space for the main header, which is not part of our
755 m_top_sizer
->AddSpacer(MAIN_HEADER_OFFSET
);
759 // add list boxes to main area, the list_sizer
762 wxStaticBoxSizer
*list_sizer
= new wxStaticBoxSizer(wxHORIZONTAL
,
763 m_parent
, m_device_id_str
);
765 // add database listctrl
766 m_dbdb_list
.reset (new wxListCtrl(m_parent
, BrowseMode_DBDBList
,
767 wxDefaultPosition
, wxDefaultSize
,
768 wxLC_REPORT
| wxLC_SINGLE_SEL
) ); //| wxLC_VRULES
769 // int max_db_width = GetMaxWidth(m_dbdb_list.get(),
770 // m_dbdb.Databases.begin(), m_dbdb.Databases.end(),
772 list_sizer
->Add( m_dbdb_list
.get(), 4, wxEXPAND
| wxALL
, 4 );
774 // add the record listctrl
775 m_record_list
.reset(new wxListCtrl(m_parent
, BrowseMode_RecordList
,
776 wxDefaultPosition
, wxDefaultSize
,
777 wxLC_REPORT
| wxLC_SINGLE_SEL
) ); //| wxLC_VRULES
778 list_sizer
->Add( m_record_list
.get(), 5, wxEXPAND
| wxALL
, 4 );
780 // add list sizer to top sizer
781 m_top_sizer
->Add( list_sizer
, 1, wxEXPAND
| wxALL
, 4 );
784 // add "show all" checkbox and load status static textbox, inside sizer
787 wxBoxSizer
*status_sizer
= new wxBoxSizer(wxHORIZONTAL
);
789 m_show_all_checkbox
.reset( new wxCheckBox(m_parent
,
790 BrowseMode_ShowAllCheckbox
,
791 _W("Show All Databases"),
792 wxDefaultPosition
, wxDefaultSize
,
794 status_sizer
->Add( m_show_all_checkbox
.get(), 0, wxEXPAND
, 0 );
795 m_show_all_checkbox
->SetValue(m_show_all
);
797 status_sizer
->AddStretchSpacer();
799 m_load_status_text
.reset( new wxStaticText(m_parent
,
800 BrowseMode_LoadStatusText
,
802 wxDefaultPosition
, wxSize(200, -1),
803 wxST_NO_AUTORESIZE
) );
804 status_sizer
->Add( m_load_status_text
.get(), 0,
805 wxEXPAND
| wxALIGN_CENTRE_VERTICAL
, 0 );
807 m_top_sizer
->Add( status_sizer
, 0, wxEXPAND
| wxALL
, 4 );
815 // add bottom buttons - these go in the bottom FOOTER area
816 // so their heights must be fixed to MAIN_HEADER_OFFSET
817 // minus a border of 5px top and bottom
818 wxSize
footer(75, MAIN_HEADER_OFFSET
- 5 - 5);
819 wxBoxSizer
*buttons
= new wxBoxSizer(wxHORIZONTAL
);
820 m_import_record_button
.reset( new wxButton(m_parent
,
821 BrowseMode_ImportRecordButton
, _W("Import..."),
822 wxDefaultPosition
, footer
) );
823 m_export_record_button
.reset( new wxButton(m_parent
,
824 BrowseMode_ExportRecordButton
, _W("Export..."),
825 wxDefaultPosition
, footer
) );
826 m_add_record_button
.reset( new wxButton(m_parent
,
827 BrowseMode_AddRecordButton
, _W("Add..."),
828 wxDefaultPosition
, footer
) );
829 m_copy_record_button
.reset( new wxButton(m_parent
,
830 BrowseMode_CopyRecordButton
, _W("Copy..."),
831 wxDefaultPosition
, footer
) );
832 m_edit_record_button
.reset( new wxButton(m_parent
,
833 BrowseMode_EditRecordButton
, _W("Edit..."),
834 wxDefaultPosition
, footer
));
835 m_delete_record_button
.reset( new wxButton(m_parent
,
836 BrowseMode_DeleteRecordButton
, _W("Delete..."),
837 wxDefaultPosition
, footer
) );
838 buttons
->Add(m_import_record_button
.get(), 0, wxRIGHT
, 5);
839 buttons
->Add(m_export_record_button
.get(), 0, wxRIGHT
, 5);
840 buttons
->Add(m_add_record_button
.get(), 0, wxRIGHT
, 5);
841 buttons
->Add(m_copy_record_button
.get(), 0, wxRIGHT
, 5);
842 buttons
->Add(m_edit_record_button
.get(), 0, wxRIGHT
, 5);
843 buttons
->Add(m_delete_record_button
.get(), 0, wxRIGHT
, 5);
844 m_top_sizer
->Add(buttons
, 0, wxALL
| wxALIGN_RIGHT
, 5);
847 // recalc size of children and add columns
849 wxSize client_size
= m_parent
->GetClientSize();
850 m_top_sizer
->SetDimension(0, 0,
851 client_size
.GetWidth(), client_size
.GetHeight());
854 wxSize dbdb_size
= m_dbdb_list
->GetClientSize();
855 int scroll_width
= wxSystemSettings::GetMetric(wxSYS_VSCROLL_X
);
856 int size
= dbdb_size
.GetWidth() - scroll_width
;
857 m_dbdb_list
->InsertColumn(0, _W("Databases"), wxLIST_FORMAT_LEFT
,
859 m_dbdb_list
->InsertColumn(1, _W("Count"), wxLIST_FORMAT_LEFT
,
860 size
* 0.20 + scroll_width
); // add back the scroll width
861 // so it doesn't look half-baked when
862 // there is no scroll bar
866 wxSize record_size
= m_record_list
->GetClientSize();
867 m_record_list
->InsertColumn(0, _W("Record Description"),
868 wxLIST_FORMAT_LEFT
, record_size
.GetWidth());
877 // attempt to re-select the devices as we last saw them
878 ReselectDevices(m_device_set->String2Subset(wxGetApp().GetGlobalConfig().GetKey("SelectedDevices")));
884 int BrowseMode::GUItoDBDBIndex(int gui_index
)
889 DatabaseDatabase::DatabaseArrayType::const_iterator
890 i
= m_dbdb
.Databases
.begin(), e
= m_dbdb
.Databases
.end();
891 for( int index
= 0; i
!= e
; ++i
, index
++ ) {
892 // only bump index on the parsable databases
893 if( !m_show_all
&& !IsParsable(i
->Name
) )
906 void BrowseMode::FillDBDBList()
909 m_dbdb_list
->DeleteAllItems();
911 DatabaseDatabase::DatabaseArrayType::const_iterator
912 i
= m_dbdb
.Databases
.begin(), e
= m_dbdb
.Databases
.end();
913 for( int index
= 0; i
!= e
; ++i
, index
++ ) {
914 // Only show parsable databases, depending on GUI
915 if( !m_show_all
&& !IsParsable(i
->Name
) )
919 wxString
text(i
->Name
.c_str(), wxConvUTF8
);
920 long item
= m_dbdb_list
->InsertItem(index
, text
);
924 oss
<< dec
<< i
->RecordCount
;
925 text
= wxString(oss
.str().c_str(), wxConvUTF8
);
926 m_dbdb_list
->SetItem(item
, 1, text
);
932 void BrowseMode::FillRecordList(const std::string
&dbname
)
937 m_record_list
->DeleteAllItems();
940 DBMap::DBCachePtr db
= m_dbmap
->LoadDBCache(dbname
);
942 // cycle through the cache, and insert the descriptions
943 // given for each record
944 DBCache::const_iterator b
= db
->begin(), e
= db
->end();
945 for( int index
= 0; b
!= e
; ++b
, index
++ ) {
946 wxString
text((*b
)->GetDescription().c_str(), wxConvUTF8
);
948 m_record_list
->InsertItem(index
, text
);
951 } catch( Barry::Error
&be
) {
952 cerr
<< be
.what() << endl
;
956 void BrowseMode::UpdateButtons()
958 int selected_count
= m_record_list
->GetSelectedItemCount();
960 // can only import if this is a cardable DB
961 m_import_record_button
->Enable(m_cardable
);
963 // can only export if this is a cardable DB and only 1 selected
964 m_export_record_button
->Enable(m_cardable
&& selected_count
== 1);
966 // can only add if we have a builder and dialog for this record type
967 m_add_record_button
->Enable(m_buildable
&& m_editable
);
969 // can only copy or edit if we have a builder, a dialog, and
970 // only 1 is selected
971 m_copy_record_button
->Enable(
972 m_buildable
&& m_editable
&& selected_count
== 1);
973 m_edit_record_button
->Enable(
974 m_buildable
&& m_editable
&& selected_count
== 1);
976 // can only delete if something is selected
977 m_delete_record_button
->Enable(selected_count
> 0);
980 void BrowseMode::FillCache()
982 // create a copy of the dbdb, and sort in ascending order of
983 // record count, so the last db loaded is the longest...
984 // hopefully this makes the UI more user-friendly and responsive
985 DatabaseDatabase dbdb
= m_dbdb
;
986 dbdb
.SortByRecordCount();
988 // cycle through the dbdb and load all Parsable databases
989 DatabaseDatabase::DatabaseArrayType::const_iterator
990 i
= dbdb
.Databases
.begin(), e
= dbdb
.Databases
.end();
991 for( ; i
!= e
; ++i
) {
992 if( IsParsable(i
->Name
) ) try {
993 SendStatusEvent(i
->Name
);
994 m_dbmap
->LoadDBCache(i
->Name
);
995 } catch( Barry::Error
&be
) {
996 cerr
<< be
.what() << endl
;
1003 SendStatusEvent("");
1006 m_abort_flag
= true;
1009 void* BrowseMode::FillCacheThread(void *bobj
)
1011 BrowseMode
*bm
= (BrowseMode
*) bobj
;
1016 void BrowseMode::OnDBDBListSelChange(wxListEvent
&event
)
1019 int index
= GUItoDBDBIndex(event
.GetIndex());
1020 m_current_dbname
= m_dbdb
.Databases
.at(index
).Name
;
1021 m_buildable
= ::IsBuildable(m_current_dbname
);
1022 m_editable
= ::IsEditable(m_current_dbname
);
1023 m_cardable
= ::IsCardable(m_current_dbname
);
1024 m_current_record_item
= -1;
1026 FillRecordList(m_current_dbname
);
1030 void BrowseMode::OnRecordListSelChange(wxListEvent
&event
)
1032 // grab the cache for the current database... Get is ok here,
1033 // since the cache is already loaded by the main db list
1034 DBMap::DBCachePtr p
= m_dbmap
->GetDBCache(m_current_dbname
);
1038 // grab the record list index
1039 m_current_record_item
= event
.GetIndex();
1040 // m_current_record_item = m_record_list->GetNextItem(
1041 // m_current_record_item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1046 void BrowseMode::OnRecordListActivated(wxListEvent
&event
)
1052 void BrowseMode::OnShowAll(wxCommandEvent
&event
)
1054 m_show_all
= !m_show_all
;
1058 template <class SyncT
>
1059 bool CheckTypes(wxWindow
*parent
,
1060 const string
&dbname
,
1061 const vector
<string
> &types
)
1063 if( !MimeBuilder::IsMember(SyncT::GetVName(), types
) ) {
1067 tlist
.CategoryList2Str(tslist
);
1069 wxString msg
= wxString::Format(_W("Card file type (%s) does not match record you are trying to add (%s)."),
1070 wxString(tslist
.c_str(), wxConvUTF8
).c_str(),
1071 wxString(dbname
.c_str(), wxConvUTF8
).c_str());
1072 wxMessageBox(msg
, _W("Invalid Card Type"),
1073 wxOK
| wxICON_INFORMATION
, parent
);
1080 void BrowseMode::OnImportRecord(wxCommandEvent
&event
)
1083 if( !IsCardable(m_current_dbname
, &file_types
) )
1086 // we are loading files here, so also allow *.*
1088 // TRANSLATORS: this is a file selector string,
1089 // see "Image files" for more info.
1090 file_types
+= _C("All files");
1091 file_types
+= " (*.*)|*.*";
1093 wxString
file_filter(file_types
.c_str(), wxConvUTF8
);
1095 wxFileDialog
dlg(m_parent
, _W("Load Record..."), _T(""), _T(""),
1097 wxFD_OPEN
| wxFD_PREVIEW
);
1098 if( dlg
.ShowModal() != wxID_OK
)
1102 ifstream
ifs(dlg
.GetPath().utf8_str());
1104 vector
<string
> types
;
1105 if( !MimeBuilder::ReadMimeRecord(ifs
, vrec
, types
) ) {
1106 wxMessageBox(_W("No card data found in: ") + dlg
.GetPath(),
1107 _W("Import Read Error"),
1108 wxOK
| wxICON_ERROR
, m_parent
);
1113 string convert_error
;
1115 const string
&dn
= m_current_dbname
;
1117 // and read per record type:
1118 if( m_current_dbname
== Contact::GetDBName() ) {
1119 if( !CheckTypes
<Sync::vCard
>(m_parent
, dn
, types
) )
1122 Contact rec
= vcard
.ToBarry(vrec
.c_str(), 0);
1123 rp
.reset( new RecordCache
<Contact
>(0, rec
) );
1125 else if( m_current_dbname
== Calendar::GetDBName() ) {
1126 if( !CheckTypes
<Sync::vCalendar
>(m_parent
, dn
, types
) )
1128 Sync::vTimeConverter vtc
;
1129 Sync::vCalendar
vcal(vtc
);
1130 Calendar rec
= vcal
.ToBarry(vrec
.c_str(), 0);
1131 rp
.reset( new RecordCache
<Calendar
>(0, rec
) );
1133 else if( m_current_dbname
== Memo::GetDBName() ) {
1134 if( !CheckTypes
<Sync::vJournal
>(m_parent
, dn
, types
) )
1136 Sync::vTimeConverter vtc
;
1137 Sync::vJournal
vjournal(vtc
);
1138 Memo rec
= vjournal
.ToBarry(vrec
.c_str(), 0);
1139 rp
.reset( new RecordCache
<Memo
>(0, rec
) );
1141 else if( m_current_dbname
== Task::GetDBName() ) {
1142 if( !CheckTypes
<Sync::vTodo
>(m_parent
, dn
, types
) )
1144 Sync::vTimeConverter vtc
;
1145 Sync::vTodo
vtodo(vtc
);
1146 Task rec
= vtodo
.ToBarry(vrec
.c_str(), 0);
1147 rp
.reset( new RecordCache
<Task
>(0, rec
) );
1149 } catch( Barry::ConvertError
&ce
) {
1150 convert_error
= ce
.what();
1153 if( convert_error
.size() ) {
1154 wxString msg
= wxString::Format(_W("Unable to import selected file: %s"), wxString(convert_error
.c_str(), wxConvUTF8
).c_str());
1155 wxMessageBox(msg
, _W("Import Error"), wxOK
| wxICON_ERROR
,
1160 // grab the cache for the current database... Get is ok here,
1161 // since the cache is already loaded by the main db list
1162 DBMap::DBCachePtr dbp
= m_dbmap
->GetDBCache(m_current_dbname
);
1164 wxMessageBox(_W("Internal pointer error: cannot find DBCachePtr for: ") + wxString(m_current_dbname
.c_str(), wxConvUTF8
),
1165 _W("Internal Error"),
1166 wxOK
| wxICON_ERROR
, m_parent
);
1170 DBCache::iterator i
= dbp
->Add(m_parent
, rp
);
1171 if( i
!= dbp
->end() ) {
1172 wxString
text((*i
)->GetDescription().c_str(), wxConvUTF8
);
1174 // insert new record in same spot as DBCache has it
1175 m_current_record_item
= dbp
->GetIndex(i
);
1176 m_record_list
->InsertItem(m_current_record_item
, text
);
1179 wxMessageBox(_W("Internal error: cannot add record to DBCache"),
1180 _W("Internal Error"),
1181 wxOK
| wxICON_ERROR
, m_parent
);
1186 void BrowseMode::OnExportRecord(wxCommandEvent
&event
)
1189 if( !IsCardable(m_current_dbname
, &file_types
) )
1192 // grab the cache for the current database... Get is ok here,
1193 // since the cache is already loaded by the main db list
1194 DBMap::DBCachePtr p
= m_dbmap
->GetDBCache(m_current_dbname
);
1198 DBCache::iterator i
= p
->Get(m_current_record_item
);
1200 if( (*i
)->Card(m_parent
, vdata
) ) {
1201 MimeExportDlg
dlg(m_parent
, vdata
, file_types
);
1206 void BrowseMode::OnAddRecord(wxCommandEvent
&event
)
1208 // grab the cache for the current database... Get is ok here,
1209 // since the cache is already loaded by the main db list
1210 DBMap::DBCachePtr p
= m_dbmap
->GetDBCache(m_current_dbname
);
1214 DBCache::iterator i
= p
->Add(m_parent
, *m_zones
, p
->end());
1215 if( i
!= p
->end() ) {
1216 wxString
text((*i
)->GetDescription().c_str(), wxConvUTF8
);
1218 // insert new record in same spot as DBCache has it
1219 m_current_record_item
= p
->GetIndex(i
);
1220 m_record_list
->InsertItem(m_current_record_item
, text
);
1224 void BrowseMode::OnCopyRecord(wxCommandEvent
&event
)
1226 // grab the cache for the current database... Get is ok here,
1227 // since the cache is already loaded by the main db list
1228 DBMap::DBCachePtr p
= m_dbmap
->GetDBCache(m_current_dbname
);
1232 DBCache::iterator source
= p
->Get(m_current_record_item
);
1233 DBCache::iterator i
= p
->Add(m_parent
, *m_zones
, source
);
1234 if( i
!= p
->end() ) {
1235 wxString
text((*i
)->GetDescription().c_str(), wxConvUTF8
);
1237 // insert new record in same spot as DBCache has it
1238 m_current_record_item
= p
->GetIndex(i
);
1239 m_record_list
->InsertItem(m_current_record_item
, text
);
1243 void BrowseMode::OnEditRecord(wxCommandEvent
&event
)
1245 // grab the cache for the current database... Get is ok here,
1246 // since the cache is already loaded by the main db list
1247 DBMap::DBCachePtr p
= m_dbmap
->GetDBCache(m_current_dbname
);
1251 DBCache::iterator i
= p
->Get(m_current_record_item
);
1252 if( p
->Edit(m_parent
, *m_zones
, i
) ) {
1253 wxString
text((*i
)->GetDescription().c_str(), wxConvUTF8
);
1254 m_record_list
->SetItem(m_current_record_item
, 0, text
);
1258 void BrowseMode::OnDeleteRecord(wxCommandEvent
&event
)
1260 // grab the cache for the current database... Get is ok here,
1261 // since the cache is already loaded by the main db list
1262 DBMap::DBCachePtr p
= m_dbmap
->GetDBCache(m_current_dbname
);
1266 DBCache::iterator i
= p
->Get(m_current_record_item
);
1267 if( p
->Delete(m_parent
, i
) ) {
1268 m_record_list
->DeleteItem(m_current_record_item
);
1272 void BrowseMode::OnStatusEvent(wxCommandEvent
&event
)
1274 m_load_status_text
->SetLabel(event
.GetString());