Bumped copyright dates for 2013
[barry.git] / desktop / src / Mode_Browse.cc
blob7ff7622ba4fd264b7382e6e39aaed8c9ce4ecfc0
1 ///
2 /// \file Mode_Browse.cc
3 /// Mode derived class for database browsing
4 ///
6 /*
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"
30 #include <iostream>
31 #include <sstream>
32 #include <iomanip>
34 using namespace std;
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)
62 END_EVENT_TABLE()
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
88 // import / exports
89 if( dbname == Contact::GetDBName() ) {
91 if( file_types ) {
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";
97 return true;
100 else if( dbname == Calendar::GetDBName() ||
101 dbname == Memo::GetDBName() ||
102 dbname == Task::GetDBName() ) {
104 if( file_types ) {
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";
110 return true;
114 return false;
117 bool EditRecord(wxWindow *parent,
118 bool editable,
119 const Barry::TimeZones &zones,
120 Barry::Contact &rec)
122 ContactEditDlg edit(parent, rec, editable);
123 return edit.ShowModal() == wxID_OK;
126 bool EditRecord(wxWindow *parent,
127 bool editable,
128 const Barry::TimeZones &zones,
129 Barry::Bookmark &rec)
131 return false;
134 bool EditRecord(wxWindow *parent,
135 bool editable,
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,
144 bool editable,
145 const Barry::TimeZones &zones,
146 Barry::CalendarAll &rec)
148 return false;
151 bool EditRecord(wxWindow *parent,
152 bool editable,
153 const Barry::TimeZones &zones,
154 Barry::ContentStore &rec)
156 return false;
159 bool EditRecord(wxWindow *parent,
160 bool editable,
161 const Barry::TimeZones &zones,
162 Barry::Folder &rec)
164 return false;
167 bool EditRecord(wxWindow *parent,
168 bool editable,
169 const Barry::TimeZones &zones,
170 Barry::Memo &rec)
172 MemoEditDlg edit(parent, rec, editable);
173 return edit.ShowModal() == wxID_OK;
176 bool EditRecord(wxWindow *parent,
177 bool editable,
178 const Barry::TimeZones &zones,
179 Barry::Message &rec)
181 return false;
184 bool EditRecord(wxWindow *parent,
185 bool editable,
186 const Barry::TimeZones &zones,
187 Barry::CallLog &rec)
189 return false;
192 bool EditRecord(wxWindow *parent,
193 bool editable,
194 const Barry::TimeZones &zones,
195 Barry::PINMessage &rec)
197 return false;
200 bool EditRecord(wxWindow *parent,
201 bool editable,
202 const Barry::TimeZones &zones,
203 Barry::SavedMessage &rec)
205 return false;
208 bool EditRecord(wxWindow *parent,
209 bool editable,
210 const Barry::TimeZones &zones,
211 Barry::ServiceBook &rec)
213 return false;
216 bool EditRecord(wxWindow *parent,
217 bool editable,
218 const Barry::TimeZones &zones,
219 Barry::Sms &rec)
221 return false;
224 bool EditRecord(wxWindow *parent,
225 bool editable,
226 const Barry::TimeZones &zones,
227 Barry::Task &rec)
229 TaskEditDlg edit(parent, rec, editable, &zones);
230 return edit.ShowModal() == wxID_OK;
233 bool EditRecord(wxWindow *parent,
234 bool editable,
235 const Barry::TimeZones &zones,
236 Barry::TimeZone &rec)
238 return false;
241 bool EditRecord(wxWindow *parent,
242 bool editable,
243 const Barry::TimeZones &zones,
244 Barry::HandheldAgent &rec)
246 return false;
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 //////////////////////////////////////////////////////////////////////////////
273 // DBDataCache
275 DBDataCache::DBDataCache(DataCache::IndexType index, const Barry::DBData &raw)
276 : DataCache(index, raw.GetUniqueId())
277 , m_raw(raw)
281 bool DBDataCache::Edit(wxWindow *parent,
282 bool editable,
283 const Barry::TimeZones &zones)
285 return false;
288 std::string DBDataCache::GetDescription() const
290 return _C("raw data");
294 //////////////////////////////////////////////////////////////////////////////
295 // DBCache
297 DBCache::DBCache(ThreadableDesktop &tdesktop, const std::string &dbname)
298 : m_tdesktop(tdesktop)
299 , m_dbname(dbname)
301 // lock the desktop
302 DesktopInstancePtr dip = m_tdesktop.Get();
304 // grab the DBID
305 m_dbid = dip->Desktop().GetDBID(m_dbname);
307 // load the record state table
308 dip->Desktop().GetRecordStateTable(m_dbid, m_state);
310 // load all records
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
319 m_records.sort();
322 DBCache::~DBCache()
326 DBCache::iterator DBCache::Get(int list_offset)
328 iterator i = begin();
329 for( ; i != end() && list_offset; ++i, list_offset-- )
331 return i;
334 DBCache::const_iterator DBCache::Get(int list_offset) const
336 const_iterator i = begin();
337 for( ; i != end() && list_offset; ++i, list_offset-- )
339 return i;
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++ ) {
347 if( i == record )
348 return index;
350 return -1;
353 int DBCache::GetIndex(const_iterator record) const
355 const_iterator i = begin();
356 for( int index = 0; i != end(); ++i, index++ ) {
357 if( i == record )
358 return index;
360 return -1;
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());
367 if( !bp ) {
368 cerr << _C("DataCachePtr has no builder") << endl;
369 return end();
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);
382 try {
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);
388 return end();
391 throw;
393 Barry::Verbose(iv);
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
400 IndexType new_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
412 return begin();
415 DBCache::iterator DBCache::Add(wxWindow *parent,
416 const Barry::TimeZones &zones,
417 iterator copy_record)
419 DataCachePtr p;
421 #undef HANDLE_BUILDER
422 #define HANDLE_BUILDER(tname) \
423 if( m_dbname == Barry::tname::GetDBName() ) { \
424 Barry::tname rec; \
425 if( copy_record != end() ) { \
426 RecordCache<Barry::tname> *rc = dynamic_cast<RecordCache<Barry::tname>* > (copy_record->get()); \
427 if( rc ) { \
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
436 if( !p.get() ) {
437 return end();
440 if( p->Edit(parent, true, zones) ) {
441 return Add(parent, p);
443 else {
444 return end();
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());
452 if( !bp )
453 return false;
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);
462 try {
463 desktop.SetRecord(m_dbid, (*record)->GetStateIndex(), *bp);
464 } catch( Barry::ReturnCodeError &rce ) {
465 if( rce.IsReadOnly() ) {
466 ShowReadOnlyMsg(parent, rce);
467 return false;
470 throw;
472 Barry::Verbose(iv);
474 return true;
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());
481 if( !bp )
482 return false;
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);
491 try {
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);
497 return false;
500 throw;
502 Barry::Verbose(iv);
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
509 IndexType new_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());
517 return true;
520 bool DBCache::Edit(wxWindow *parent,
521 const Barry::TimeZones &zones,
522 iterator record)
524 if( record == end() )
525 return false;
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());
531 if( tp ) {
532 // yes, it is... the Tasks database has a bug
533 // so we need to "edit" by deleting and adding
534 // the record again
535 return DeleteAndAddRecord(parent, record);
537 else {
538 // use the normal code for all other records
539 return OverwriteRecord(parent, record);
542 else {
543 return false;
547 bool DBCache::Delete(wxWindow *parent, iterator record)
549 if( record == end() )
550 return false;
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 )
559 return false;
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);
570 return true;
573 // For Barry::AllRecordStore
574 #undef HANDLE_PARSER
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
583 // For Barry::Parser
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 //////////////////////////////////////////////////////////////////////////////
593 // DBMap
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() )
616 return i->second;
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() )
631 return i->second;
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);
640 m_map[dbname] = p;
641 return p;
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() )
650 return i->second;
652 return DBCachePtr();
655 //////////////////////////////////////////////////////////////////////////////
656 // BrowseMode
658 BrowseMode::BrowseMode(wxWindow *parent, const ProbeResult &device)
659 : m_parent(parent)
660 , m_buildable(false)
661 , m_editable(false)
662 , m_cardable(false)
663 , m_show_all(false)
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) );
672 m_con->Reconnect(2);
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();
677 m_dbdb.SortByName();
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()) );
684 else {
685 // use static time zone table from Barry library
686 m_zones.reset( new TimeZones );
689 CreateControls();
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
697 // no exceptions!
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);
705 if( ret != 0 )
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 ) {
721 m_abort_flag = true;
722 void *junk;
723 pthread_join(m_cache_thread, &junk);
727 std::string& GetDBName(Barry::DatabaseDatabase::Database &db)
729 return db.Name;
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);
742 else {
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
754 // work area
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(),
771 // &GetDBName);
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,
793 wxCHK_2STATE) );
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,
801 _T(""),
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 );
812 // bottom buttons
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());
853 // m_dbdb_list
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,
858 size * 0.80);
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
865 // m_record_list
866 wxSize record_size = m_record_list->GetClientSize();
867 m_record_list->InsertColumn(0, _W("Record Description"),
868 wxLIST_FORMAT_LEFT, record_size.GetWidth());
872 // add data
874 FillDBDBList();
877 // attempt to re-select the devices as we last saw them
878 ReselectDevices(m_device_set->String2Subset(wxGetApp().GetGlobalConfig().GetKey("SelectedDevices")));
879 UpdateButtons();
884 int BrowseMode::GUItoDBDBIndex(int gui_index)
886 if( m_show_all )
887 return 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) )
894 continue;
896 if( !gui_index )
897 return index;
899 gui_index--;
902 // error
903 return -1;
906 void BrowseMode::FillDBDBList()
908 // start fresh
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) )
916 continue;
918 // Database Name
919 wxString text(i->Name.c_str(), wxConvUTF8);
920 long item = m_dbdb_list->InsertItem(index, text);
922 // Record Count
923 ostringstream oss;
924 oss << dec << i->RecordCount;
925 text = wxString(oss.str().c_str(), wxConvUTF8);
926 m_dbdb_list->SetItem(item, 1, text);
929 UpdateButtons();
932 void BrowseMode::FillRecordList(const std::string &dbname)
934 try {
936 // start fresh
937 m_record_list->DeleteAllItems();
939 // grab our DB
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);
947 //long item =
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;
999 if( m_abort_flag )
1000 break;
1003 SendStatusEvent("");
1005 // finished
1006 m_abort_flag = true;
1009 void* BrowseMode::FillCacheThread(void *bobj)
1011 BrowseMode *bm = (BrowseMode*) bobj;
1012 bm->FillCache();
1013 return NULL;
1016 void BrowseMode::OnDBDBListSelChange(wxListEvent &event)
1018 wxBusyCursor wait;
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);
1027 UpdateButtons();
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);
1035 if( !p.get() )
1036 return;
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);
1043 UpdateButtons();
1046 void BrowseMode::OnRecordListActivated(wxListEvent &event)
1048 wxCommandEvent ce;
1049 OnEditRecord(ce);
1052 void BrowseMode::OnShowAll(wxCommandEvent &event)
1054 m_show_all = !m_show_all;
1055 FillDBDBList();
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) ) {
1064 CategoryList tlist;
1065 tlist = types;
1066 string tslist;
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);
1074 return false;
1077 return true;
1080 void BrowseMode::OnImportRecord(wxCommandEvent &event)
1082 string file_types;
1083 if( !IsCardable(m_current_dbname, &file_types) )
1084 return;
1086 // we are loading files here, so also allow *.*
1087 file_types += "|";
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(""),
1096 file_filter,
1097 wxFD_OPEN | wxFD_PREVIEW);
1098 if( dlg.ShowModal() != wxID_OK )
1099 return;
1101 // open file
1102 ifstream ifs(dlg.GetPath().utf8_str());
1103 string vrec;
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);
1109 return;
1112 DataCachePtr rp;
1113 string convert_error;
1114 try {
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) )
1120 return;
1121 Sync::vCard vcard;
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) )
1127 return;
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) )
1135 return;
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) )
1143 return;
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,
1156 m_parent);
1157 return;
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);
1163 if( !dbp.get() ) {
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);
1167 return;
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);
1178 else {
1179 wxMessageBox(_W("Internal error: cannot add record to DBCache"),
1180 _W("Internal Error"),
1181 wxOK | wxICON_ERROR, m_parent);
1182 return;
1186 void BrowseMode::OnExportRecord(wxCommandEvent &event)
1188 string file_types;
1189 if( !IsCardable(m_current_dbname, &file_types) )
1190 return;
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);
1195 if( !p.get() )
1196 return;
1198 DBCache::iterator i = p->Get(m_current_record_item);
1199 string vdata;
1200 if( (*i)->Card(m_parent, vdata) ) {
1201 MimeExportDlg dlg(m_parent, vdata, file_types);
1202 dlg.ShowModal();
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);
1211 if( !p.get() )
1212 return;
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);
1229 if( !p.get() )
1230 return;
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);
1248 if( !p.get() )
1249 return;
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);
1263 if( !p.get() )
1264 return;
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());