desktop: string conversion to support i18n
[barry.git] / desktop / src / Mode_Browse.cc
blob0cb68d457d30d365a143279e1e424e803e5d2132
1 ///
2 /// \file Mode_Browse.cc
3 /// Mode derived class for database browsing
4 ///
6 /*
7 Copyright (C) 2011-2012, 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 *file_types = _C("VCard files");
93 *file_types += " (*.vcf;*.vcard)|*.vcf;*.vcard";
95 return true;
98 else if( dbname == Calendar::GetDBName() ||
99 dbname == Memo::GetDBName() ||
100 dbname == Task::GetDBName() ) {
102 if( file_types ) {
103 *file_types = _C("ICalendar files");
104 *file_types += " (*.ical;*.ics;*.ifb;*.icalendar)|*.ical;*.ics;*.ifb;*.icalendar";
106 return true;
110 return false;
113 bool EditRecord(wxWindow *parent,
114 bool editable,
115 const Barry::TimeZones &zones,
116 Barry::Contact &rec)
118 ContactEditDlg edit(parent, rec, editable);
119 return edit.ShowModal() == wxID_OK;
122 bool EditRecord(wxWindow *parent,
123 bool editable,
124 const Barry::TimeZones &zones,
125 Barry::Bookmark &rec)
127 return false;
130 bool EditRecord(wxWindow *parent,
131 bool editable,
132 const Barry::TimeZones &zones,
133 Barry::Calendar &rec)
135 CalendarEditDlg edit(parent, rec, editable, &zones);
136 return edit.ShowModal() == wxID_OK;
139 bool EditRecord(wxWindow *parent,
140 bool editable,
141 const Barry::TimeZones &zones,
142 Barry::CalendarAll &rec)
144 return false;
147 bool EditRecord(wxWindow *parent,
148 bool editable,
149 const Barry::TimeZones &zones,
150 Barry::ContentStore &rec)
152 return false;
155 bool EditRecord(wxWindow *parent,
156 bool editable,
157 const Barry::TimeZones &zones,
158 Barry::Folder &rec)
160 return false;
163 bool EditRecord(wxWindow *parent,
164 bool editable,
165 const Barry::TimeZones &zones,
166 Barry::Memo &rec)
168 MemoEditDlg edit(parent, rec, editable);
169 return edit.ShowModal() == wxID_OK;
172 bool EditRecord(wxWindow *parent,
173 bool editable,
174 const Barry::TimeZones &zones,
175 Barry::Message &rec)
177 return false;
180 bool EditRecord(wxWindow *parent,
181 bool editable,
182 const Barry::TimeZones &zones,
183 Barry::CallLog &rec)
185 return false;
188 bool EditRecord(wxWindow *parent,
189 bool editable,
190 const Barry::TimeZones &zones,
191 Barry::PINMessage &rec)
193 return false;
196 bool EditRecord(wxWindow *parent,
197 bool editable,
198 const Barry::TimeZones &zones,
199 Barry::SavedMessage &rec)
201 return false;
204 bool EditRecord(wxWindow *parent,
205 bool editable,
206 const Barry::TimeZones &zones,
207 Barry::ServiceBook &rec)
209 return false;
212 bool EditRecord(wxWindow *parent,
213 bool editable,
214 const Barry::TimeZones &zones,
215 Barry::Sms &rec)
217 return false;
220 bool EditRecord(wxWindow *parent,
221 bool editable,
222 const Barry::TimeZones &zones,
223 Barry::Task &rec)
225 TaskEditDlg edit(parent, rec, editable, &zones);
226 return edit.ShowModal() == wxID_OK;
229 bool EditRecord(wxWindow *parent,
230 bool editable,
231 const Barry::TimeZones &zones,
232 Barry::TimeZone &rec)
234 return false;
237 bool EditRecord(wxWindow *parent,
238 bool editable,
239 const Barry::TimeZones &zones,
240 Barry::HandheldAgent &rec)
242 return false;
246 //////////////////////////////////////////////////////////////////////////////
247 // GUIDesktopConnector
249 bool GUIDesktopConnector::PasswordPrompt(const Barry::BadPassword &bp,
250 std::string &password_result)
252 // create prompt based on exception data
253 ostringstream oss;
254 oss << _C("Please enter device password: ")
255 << "(" << bp.remaining_tries() << _C(" tries remaining") << ")";
256 wxString prompt(oss.str().c_str(), wxConvUTF8);
258 // ask user for device password
259 wxString pass = wxGetPasswordFromUser(prompt,
260 _W("Device Password"), _T(""), m_parent);
262 password_result = pass.utf8_str();
264 // assume that a blank password means the user wishes to quit...
265 // wxWidgets doesn't seem to handle this very well?
266 return password_result.size() > 0;
269 //////////////////////////////////////////////////////////////////////////////
270 // DBDataCache
272 DBDataCache::DBDataCache(DataCache::IndexType index, const Barry::DBData &raw)
273 : DataCache(index, raw.GetUniqueId())
274 , m_raw(raw)
278 bool DBDataCache::Edit(wxWindow *parent,
279 bool editable,
280 const Barry::TimeZones &zones)
282 return false;
285 std::string DBDataCache::GetDescription() const
287 return _C("raw data");
291 //////////////////////////////////////////////////////////////////////////////
292 // DBCache
294 DBCache::DBCache(ThreadableDesktop &tdesktop, const std::string &dbname)
295 : m_tdesktop(tdesktop)
296 , m_dbname(dbname)
298 // lock the desktop
299 DesktopInstancePtr dip = m_tdesktop.Get();
301 // grab the DBID
302 m_dbid = dip->Desktop().GetDBID(m_dbname);
304 // load the record state table
305 dip->Desktop().GetRecordStateTable(m_dbid, m_state);
307 // load all records
308 AllRecordParser ap(*this, *this);
309 RecordStateTable::StateMapType::iterator i = m_state.StateMap.begin();
310 for( ; i != m_state.StateMap.end(); ++i ) {
311 m_index = i->second.Index; // save for the callback
312 dip->Desktop().GetRecord(m_dbid, m_index, ap);
315 // sort the list of records by description
316 m_records.sort();
319 DBCache::~DBCache()
323 DBCache::iterator DBCache::Get(int list_offset)
325 iterator i = begin();
326 for( ; i != end() && list_offset; ++i, list_offset-- )
328 return i;
331 DBCache::const_iterator DBCache::Get(int list_offset) const
333 const_iterator i = begin();
334 for( ; i != end() && list_offset; ++i, list_offset-- )
336 return i;
339 int DBCache::GetIndex(iterator record) const
341 iterator i = const_cast<DBCache*> (this)->begin();
342 iterator e = const_cast<DBCache*> (this)->end();
343 for( int index = 0; i != e; ++i, index++ ) {
344 if( i == record )
345 return index;
347 return -1;
350 int DBCache::GetIndex(const_iterator record) const
352 const_iterator i = begin();
353 for( int index = 0; i != end(); ++i, index++ ) {
354 if( i == record )
355 return index;
357 return -1;
360 DBCache::iterator DBCache::Add(wxWindow *parent, DataCachePtr p)
362 // see if this record has a builder
363 Barry::Builder *bp = dynamic_cast<Barry::Builder*> (p.get());
364 if( !bp ) {
365 cerr << _C("DataCachePtr has no builder") << endl;
366 return end();
369 // give record a new UniqueID
370 uint32_t record_id = m_state.MakeNewRecordId();
371 cout << "New recordID generated: 0x" << hex << record_id << endl;
372 p->SetIds(p->GetStateIndex(), record_id);
374 // add record to device
375 DesktopInstancePtr dip = m_tdesktop.Get();
376 Barry::Mode::Desktop &desktop = dip->Desktop();
377 bool iv = Barry::IsVerbose();
378 Barry::Verbose(true);
379 try {
380 desktop.AddRecord(m_dbid, *bp);
381 } catch( Barry::ReturnCodeError &rce ) {
382 cerr << _C("Device exception: ") << rce.what() << endl;
383 if( rce.IsReadOnly() ) {
384 ShowReadOnlyMsg(parent, rce);
385 return end();
388 throw;
390 Barry::Verbose(iv);
392 // update our copy of the record state table from device
393 desktop.GetRecordStateTable(m_dbid, m_state);
394 cout << m_state << endl;
396 // find our new record_id in list, to find the state index
397 IndexType new_index;
398 if( !m_state.GetIndex(record_id, &new_index) ) {
399 throw std::logic_error("Need to reconnect for adding a record?");
402 // update new state_index in the data cache record
403 p->SetIds(new_index, record_id);
405 // add DataCachePtr to our own cache list
406 m_records.push_front(p);
408 // return iterator pointing to new record
409 return begin();
412 DBCache::iterator DBCache::Add(wxWindow *parent,
413 const Barry::TimeZones &zones,
414 iterator copy_record)
416 DataCachePtr p;
418 #undef HANDLE_BUILDER
419 #define HANDLE_BUILDER(tname) \
420 if( m_dbname == Barry::tname::GetDBName() ) { \
421 Barry::tname rec; \
422 if( copy_record != end() ) { \
423 RecordCache<Barry::tname> *rc = dynamic_cast<RecordCache<Barry::tname>* > (copy_record->get()); \
424 if( rc ) { \
425 rec = rc->GetRecord(); \
428 p.reset( new RecordCache<Barry::tname>(0, rec) ); \
430 ALL_KNOWN_BUILDER_TYPES
432 // anything else is not addable or buildable
433 if( !p.get() ) {
434 return end();
437 if( p->Edit(parent, true, zones) ) {
438 return Add(parent, p);
440 else {
441 return end();
445 bool DBCache::OverwriteRecord(wxWindow *parent, iterator record)
447 // see if this record has a builder
448 Barry::Builder *bp = dynamic_cast<Barry::Builder*> ((*record).get());
449 if( !bp )
450 return false;
452 cout << "Changing device record with index: 0x" << hex << (*record)->GetStateIndex() << endl;
453 cout << m_state << endl;
454 // update the device with new record data
455 DesktopInstancePtr dip = m_tdesktop.Get();
456 Barry::Mode::Desktop &desktop = dip->Desktop();
457 bool iv = Barry::IsVerbose();
458 Barry::Verbose(true);
459 try {
460 desktop.SetRecord(m_dbid, (*record)->GetStateIndex(), *bp);
461 } catch( Barry::ReturnCodeError &rce ) {
462 if( rce.IsReadOnly() ) {
463 ShowReadOnlyMsg(parent, rce);
464 return false;
467 throw;
469 Barry::Verbose(iv);
471 return true;
474 bool DBCache::DeleteAndAddRecord(wxWindow *parent, iterator record)
476 // see if this record has a builder
477 Barry::Builder *bp = dynamic_cast<Barry::Builder*> ((*record).get());
478 if( !bp )
479 return false;
481 cout << "Changing device record with index: 0x" << hex << (*record)->GetStateIndex() << endl;
482 cout << m_state << endl;
483 // update the device with new record data
484 DesktopInstancePtr dip = m_tdesktop.Get();
485 Barry::Mode::Desktop &desktop = dip->Desktop();
486 bool iv = Barry::IsVerbose();
487 Barry::Verbose(true);
488 try {
489 desktop.DeleteRecord(m_dbid, (*record)->GetStateIndex());
490 desktop.AddRecord(m_dbid, *bp);
491 } catch( Barry::ReturnCodeError &rce ) {
492 if( rce.IsReadOnly() ) {
493 ShowReadOnlyMsg(parent, rce);
494 return false;
497 throw;
499 Barry::Verbose(iv);
501 // update our copy of the record state table from device
502 desktop.GetRecordStateTable(m_dbid, m_state);
503 cout << m_state << endl;
505 // find our record_id in list, to find the state index
506 IndexType new_index;
507 if( !m_state.GetIndex((*record)->GetRecordId(), &new_index) ) {
508 throw std::logic_error("DAA: Need to reconnect for adding a record?");
511 // update new state_index in the data cache record
512 (*record)->SetIds(new_index, (*record)->GetRecordId());
514 return true;
517 bool DBCache::Edit(wxWindow *parent,
518 const Barry::TimeZones &zones,
519 iterator record)
521 if( record == end() )
522 return false;
524 if( (*record)->Edit(parent, true, zones) && (*record)->IsBuildable() ) {
525 // see if this record is part of the Tasks database
526 RecordCache<Barry::Task> *tp = dynamic_cast< RecordCache<Barry::Task>*> ((*record).get());
528 if( tp ) {
529 // yes, it is... the Tasks database has a bug
530 // so we need to "edit" by deleting and adding
531 // the record again
532 return DeleteAndAddRecord(parent, record);
534 else {
535 // use the normal code for all other records
536 return OverwriteRecord(parent, record);
539 else {
540 return false;
544 bool DBCache::Delete(wxWindow *parent, iterator record)
546 if( record == end() )
547 return false;
549 // prompt user with Yes / No message
550 wxString desc((*record)->GetDescription().c_str(), wxConvUTF8);
551 int choice = wxMessageBox(_W("Delete this record?\n ") + desc,
552 _W("Record Delete"), wxYES_NO | wxICON_QUESTION, parent);
554 // if no, return false
555 if( choice != wxYES )
556 return false;
558 cout << "Deleting device record with index: 0x" << hex << (*record)->GetStateIndex() << endl;
559 cout << m_state << endl;
560 // delete record from device
561 DesktopInstancePtr dip = m_tdesktop.Get();
562 Barry::Mode::Desktop &desktop = dip->Desktop();
563 desktop.DeleteRecord(m_dbid, (*record)->GetStateIndex());
565 // remove record from cache list
566 m_records.erase(record);
567 return true;
570 // For Barry::AllRecordStore
571 #undef HANDLE_PARSER
572 #define HANDLE_PARSER(tname) \
573 void DBCache::operator() (const Barry::tname &rec) \
575 DataCachePtr p( new RecordCache<Barry::tname>(m_index, rec) ); \
576 m_records.push_front(p); \
578 ALL_KNOWN_PARSER_TYPES
580 // For Barry::Parser
581 void DBCache::ParseRecord(const Barry::DBData &data,
582 const Barry::IConverter *ic)
584 DataCachePtr p( new DBDataCache(m_index, data) );
585 m_records.push_front(p);
589 //////////////////////////////////////////////////////////////////////////////
590 // DBMap
592 DBMap::DBMap(ThreadableDesktop &tdesktop)
593 : m_tdesktop(tdesktop)
595 if( pthread_mutex_init(&m_map_mutex, NULL) ) {
596 throw Barry::Error(_C("Failed to create map mutex"));
599 if( pthread_mutex_init(&m_load_mutex, NULL) ) {
600 throw Barry::Error(_C("Failed to create load mutex"));
604 DBMap::DBCachePtr DBMap::LoadDBCache(const std::string &dbname)
606 // first, check for pre-loaded data, before the load lock,
607 // to make sure we return pre-loaded data with utmost haste
609 scoped_lock map_lock(m_map_mutex);
611 MapType::iterator i = m_map.find(dbname);
612 if( i != m_map.end() )
613 return i->second;
616 // if not found, lock and load, but be careful, since we
617 // don't want to open a window here for loading a db twice
618 scoped_lock load_lock(m_load_mutex);
620 // check again for pre-loaded data, since between
621 // map.unlock and load.lock there could have been
622 // another successful load
624 scoped_lock map_lock(m_map_mutex);
626 MapType::iterator i = m_map.find(dbname);
627 if( i != m_map.end() )
628 return i->second;
631 // do the load, without map.lock, since this can take a
632 // significant amount of time
633 DBCachePtr p( new DBCache(m_tdesktop, dbname) );
635 // lock once more to update the map, and then done
636 scoped_lock map_lock(m_map_mutex);
637 m_map[dbname] = p;
638 return p;
641 DBMap::DBCachePtr DBMap::GetDBCache(const std::string &dbname)
643 scoped_lock lock(m_map_mutex);
645 MapType::iterator i = m_map.find(dbname);
646 if( i != m_map.end() )
647 return i->second;
649 return DBCachePtr();
652 //////////////////////////////////////////////////////////////////////////////
653 // BrowseMode
655 BrowseMode::BrowseMode(wxWindow *parent, const ProbeResult &device)
656 : m_parent(parent)
657 , m_buildable(false)
658 , m_editable(false)
659 , m_cardable(false)
660 , m_show_all(false)
662 // create device identifying string
663 m_device_id_str = wxString(device.GetDisplayName().c_str(), wxConvUTF8);
666 // connect to the device
668 m_con.reset( new GUIDesktopConnector(m_parent, "", "utf-8", device) );
669 m_con->Reconnect(2);
670 m_tdesktop.reset( new ThreadableDesktop(*m_con) );
672 // keep our own copy, and sort by name for later
673 m_dbdb = m_con->GetDesktop().GetDBDB();
674 m_dbdb.SortByName();
676 // store a copy of the time zone set for record editing
677 if( TimeZones::IsLoadable(m_con->GetDesktop()) ) {
678 // load time zones from device itself
679 m_zones.reset( new TimeZones(m_con->GetDesktop()) );
681 else {
682 // use static time zone table from Barry library
683 m_zones.reset( new TimeZones );
686 CreateControls();
688 // create our DBMap and give it the threadable desktop,
689 // now that we're finished doing any desktop USB work
690 m_dbmap.reset( new DBMap(*m_tdesktop) );
693 // From here down, we assume that our constructor succeeds, with
694 // no exceptions!
697 // fire off a background thread to cache database records
698 // in advance... if it fails, don't worry about it
699 m_abort_flag = false;
700 int ret = pthread_create(&m_cache_thread, NULL,
701 &BrowseMode::FillCacheThread, this);
702 if( ret != 0 )
703 m_abort_flag = true; // no need to join later
705 // connect ourselves to the parent's event handling chain
706 // do this last, so that we are guaranteed our destructor
707 // will run, in case of exceptions
708 m_parent->PushEventHandler(this);
711 BrowseMode::~BrowseMode()
713 // unhook that event handler!
714 m_parent->PopEventHandler();
716 // make sure the cache thread is finished before we destroy it :-)
717 if( !m_abort_flag ) {
718 m_abort_flag = true;
719 void *junk;
720 pthread_join(m_cache_thread, &junk);
724 std::string& GetDBName(Barry::DatabaseDatabase::Database &db)
726 return db.Name;
729 void BrowseMode::SendStatusEvent(const std::string &dbname)
731 wxCommandEvent event(BMET_LOAD_STATUS, wxID_ANY);
732 event.SetEventObject(this);
734 if( dbname.size() ) {
735 wxString msg(_W("Loading: "));
736 msg += wxString(dbname.c_str(), wxConvUTF8);
737 event.SetString(msg);
739 else {
740 event.SetString(_T(""));
743 AddPendingEvent(event);
746 void BrowseMode::CreateControls()
748 m_top_sizer.reset( new wxBoxSizer(wxVERTICAL) );
750 // make space for the main header, which is not part of our
751 // work area
752 m_top_sizer->AddSpacer(MAIN_HEADER_OFFSET);
756 // add list boxes to main area, the list_sizer
759 wxStaticBoxSizer *list_sizer = new wxStaticBoxSizer(wxHORIZONTAL,
760 m_parent, m_device_id_str);
762 // add database listctrl
763 m_dbdb_list.reset (new wxListCtrl(m_parent, BrowseMode_DBDBList,
764 wxDefaultPosition, wxDefaultSize,
765 wxLC_REPORT | wxLC_SINGLE_SEL) ); //| wxLC_VRULES
766 // int max_db_width = GetMaxWidth(m_dbdb_list.get(),
767 // m_dbdb.Databases.begin(), m_dbdb.Databases.end(),
768 // &GetDBName);
769 list_sizer->Add( m_dbdb_list.get(), 4, wxEXPAND | wxALL, 4 );
771 // add the record listctrl
772 m_record_list.reset(new wxListCtrl(m_parent, BrowseMode_RecordList,
773 wxDefaultPosition, wxDefaultSize,
774 wxLC_REPORT | wxLC_SINGLE_SEL) ); //| wxLC_VRULES
775 list_sizer->Add( m_record_list.get(), 5, wxEXPAND | wxALL, 4 );
777 // add list sizer to top sizer
778 m_top_sizer->Add( list_sizer, 1, wxEXPAND | wxALL, 4 );
781 // add "show all" checkbox and load status static textbox, inside sizer
784 wxBoxSizer *status_sizer = new wxBoxSizer(wxHORIZONTAL);
786 m_show_all_checkbox.reset( new wxCheckBox(m_parent,
787 BrowseMode_ShowAllCheckbox,
788 _W("Show All Databases"),
789 wxDefaultPosition, wxDefaultSize,
790 wxCHK_2STATE) );
791 status_sizer->Add( m_show_all_checkbox.get(), 0, wxEXPAND, 0 );
792 m_show_all_checkbox->SetValue(m_show_all);
794 status_sizer->AddStretchSpacer();
796 m_load_status_text.reset( new wxStaticText(m_parent,
797 BrowseMode_LoadStatusText,
798 _T(""),
799 wxDefaultPosition, wxSize(200, -1),
800 wxST_NO_AUTORESIZE) );
801 status_sizer->Add( m_load_status_text.get(), 0,
802 wxEXPAND | wxALIGN_CENTRE_VERTICAL, 0 );
804 m_top_sizer->Add( status_sizer, 0, wxEXPAND | wxALL, 4 );
809 // bottom buttons
812 // add bottom buttons - these go in the bottom FOOTER area
813 // so their heights must be fixed to MAIN_HEADER_OFFSET
814 // minus a border of 5px top and bottom
815 wxSize footer(75, MAIN_HEADER_OFFSET - 5 - 5);
816 wxBoxSizer *buttons = new wxBoxSizer(wxHORIZONTAL);
817 m_import_record_button.reset( new wxButton(m_parent,
818 BrowseMode_ImportRecordButton, _W("Import..."),
819 wxDefaultPosition, footer) );
820 m_export_record_button.reset( new wxButton(m_parent,
821 BrowseMode_ExportRecordButton, _W("Export..."),
822 wxDefaultPosition, footer) );
823 m_add_record_button.reset( new wxButton(m_parent,
824 BrowseMode_AddRecordButton, _W("Add..."),
825 wxDefaultPosition, footer) );
826 m_copy_record_button.reset( new wxButton(m_parent,
827 BrowseMode_CopyRecordButton, _W("Copy..."),
828 wxDefaultPosition, footer) );
829 m_edit_record_button.reset( new wxButton(m_parent,
830 BrowseMode_EditRecordButton, _W("Edit..."),
831 wxDefaultPosition, footer));
832 m_delete_record_button.reset( new wxButton(m_parent,
833 BrowseMode_DeleteRecordButton, _W("Delete..."),
834 wxDefaultPosition, footer) );
835 buttons->Add(m_import_record_button.get(), 0, wxRIGHT, 5);
836 buttons->Add(m_export_record_button.get(), 0, wxRIGHT, 5);
837 buttons->Add(m_add_record_button.get(), 0, wxRIGHT, 5);
838 buttons->Add(m_copy_record_button.get(), 0, wxRIGHT, 5);
839 buttons->Add(m_edit_record_button.get(), 0, wxRIGHT, 5);
840 buttons->Add(m_delete_record_button.get(), 0, wxRIGHT, 5);
841 m_top_sizer->Add(buttons, 0, wxALL | wxALIGN_RIGHT, 5);
844 // recalc size of children and add columns
846 wxSize client_size = m_parent->GetClientSize();
847 m_top_sizer->SetDimension(0, 0,
848 client_size.GetWidth(), client_size.GetHeight());
850 // m_dbdb_list
851 wxSize dbdb_size = m_dbdb_list->GetClientSize();
852 int scroll_width = wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
853 int size = dbdb_size.GetWidth() - scroll_width;
854 m_dbdb_list->InsertColumn(0, _W("Databases"), wxLIST_FORMAT_LEFT,
855 size * 0.80);
856 m_dbdb_list->InsertColumn(1, _W("Count"), wxLIST_FORMAT_LEFT,
857 size * 0.20 + scroll_width); // add back the scroll width
858 // so it doesn't look half-baked when
859 // there is no scroll bar
862 // m_record_list
863 wxSize record_size = m_record_list->GetClientSize();
864 m_record_list->InsertColumn(0, _W("Record Description"),
865 wxLIST_FORMAT_LEFT, record_size.GetWidth());
869 // add data
871 FillDBDBList();
874 // attempt to re-select the devices as we last saw them
875 ReselectDevices(m_device_set->String2Subset(wxGetApp().GetGlobalConfig().GetKey("SelectedDevices")));
876 UpdateButtons();
881 int BrowseMode::GUItoDBDBIndex(int gui_index)
883 if( m_show_all )
884 return gui_index;
886 DatabaseDatabase::DatabaseArrayType::const_iterator
887 i = m_dbdb.Databases.begin(), e = m_dbdb.Databases.end();
888 for( int index = 0; i != e; ++i, index++ ) {
889 // only bump index on the parsable databases
890 if( !m_show_all && !IsParsable(i->Name) )
891 continue;
893 if( !gui_index )
894 return index;
896 gui_index--;
899 // error
900 return -1;
903 void BrowseMode::FillDBDBList()
905 // start fresh
906 m_dbdb_list->DeleteAllItems();
908 DatabaseDatabase::DatabaseArrayType::const_iterator
909 i = m_dbdb.Databases.begin(), e = m_dbdb.Databases.end();
910 for( int index = 0; i != e; ++i, index++ ) {
911 // Only show parsable databases, depending on GUI
912 if( !m_show_all && !IsParsable(i->Name) )
913 continue;
915 // Database Name
916 wxString text(i->Name.c_str(), wxConvUTF8);
917 long item = m_dbdb_list->InsertItem(index, text);
919 // Record Count
920 ostringstream oss;
921 oss << dec << i->RecordCount;
922 text = wxString(oss.str().c_str(), wxConvUTF8);
923 m_dbdb_list->SetItem(item, 1, text);
926 UpdateButtons();
929 void BrowseMode::FillRecordList(const std::string &dbname)
931 try {
933 // start fresh
934 m_record_list->DeleteAllItems();
936 // grab our DB
937 DBMap::DBCachePtr db = m_dbmap->LoadDBCache(dbname);
939 // cycle through the cache, and insert the descriptions
940 // given for each record
941 DBCache::const_iterator b = db->begin(), e = db->end();
942 for( int index = 0; b != e; ++b, index++ ) {
943 wxString text((*b)->GetDescription().c_str(), wxConvUTF8);
944 //long item =
945 m_record_list->InsertItem(index, text);
948 } catch( Barry::Error &be ) {
949 cerr << be.what() << endl;
953 void BrowseMode::UpdateButtons()
955 int selected_count = m_record_list->GetSelectedItemCount();
957 // can only import if this is a cardable DB
958 m_import_record_button->Enable(m_cardable);
960 // can only export if this is a cardable DB and only 1 selected
961 m_export_record_button->Enable(m_cardable && selected_count == 1);
963 // can only add if we have a builder and dialog for this record type
964 m_add_record_button->Enable(m_buildable && m_editable);
966 // can only copy or edit if we have a builder, a dialog, and
967 // only 1 is selected
968 m_copy_record_button->Enable(
969 m_buildable && m_editable && selected_count == 1);
970 m_edit_record_button->Enable(
971 m_buildable && m_editable && selected_count == 1);
973 // can only delete if something is selected
974 m_delete_record_button->Enable(selected_count > 0);
977 void BrowseMode::FillCache()
979 // create a copy of the dbdb, and sort in ascending order of
980 // record count, so the last db loaded is the longest...
981 // hopefully this makes the UI more user-friendly and responsive
982 DatabaseDatabase dbdb = m_dbdb;
983 dbdb.SortByRecordCount();
985 // cycle through the dbdb and load all Parsable databases
986 DatabaseDatabase::DatabaseArrayType::const_iterator
987 i = dbdb.Databases.begin(), e = dbdb.Databases.end();
988 for( ; i != e; ++i ) {
989 if( IsParsable(i->Name) ) try {
990 SendStatusEvent(i->Name);
991 m_dbmap->LoadDBCache(i->Name);
992 } catch( Barry::Error &be ) {
993 cerr << be.what() << endl;
996 if( m_abort_flag )
997 break;
1000 SendStatusEvent("");
1002 // finished
1003 m_abort_flag = true;
1006 void* BrowseMode::FillCacheThread(void *bobj)
1008 BrowseMode *bm = (BrowseMode*) bobj;
1009 bm->FillCache();
1010 return NULL;
1013 void BrowseMode::OnDBDBListSelChange(wxListEvent &event)
1015 wxBusyCursor wait;
1016 int index = GUItoDBDBIndex(event.GetIndex());
1017 m_current_dbname = m_dbdb.Databases.at(index).Name;
1018 m_buildable = ::IsBuildable(m_current_dbname);
1019 m_editable = ::IsEditable(m_current_dbname);
1020 m_cardable = ::IsCardable(m_current_dbname);
1021 m_current_record_item = -1;
1023 FillRecordList(m_current_dbname);
1024 UpdateButtons();
1027 void BrowseMode::OnRecordListSelChange(wxListEvent &event)
1029 // grab the cache for the current database... Get is ok here,
1030 // since the cache is already loaded by the main db list
1031 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1032 if( !p.get() )
1033 return;
1035 // grab the record list index
1036 m_current_record_item = event.GetIndex();
1037 // m_current_record_item = m_record_list->GetNextItem(
1038 // m_current_record_item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1040 UpdateButtons();
1043 void BrowseMode::OnRecordListActivated(wxListEvent &event)
1045 wxCommandEvent ce;
1046 OnEditRecord(ce);
1049 void BrowseMode::OnShowAll(wxCommandEvent &event)
1051 m_show_all = !m_show_all;
1052 FillDBDBList();
1055 template <class SyncT>
1056 bool CheckTypes(wxWindow *parent,
1057 const string &dbname,
1058 const vector<string> &types)
1060 if( !MimeBuilder::IsMember(SyncT::GetVName(), types) ) {
1061 CategoryList tlist;
1062 tlist = types;
1063 string tslist;
1064 tlist.CategoryList2Str(tslist);
1066 wxString msg = wxString::Format(_W("Card file type (%s) does not match record you are trying to add (%s)."),
1067 wxString(tslist.c_str(), wxConvUTF8).c_str(),
1068 wxString(dbname.c_str(), wxConvUTF8).c_str());
1069 wxMessageBox(msg, _W("Invalid Card Type"),
1070 wxOK | wxICON_INFORMATION, parent);
1071 return false;
1074 return true;
1077 void BrowseMode::OnImportRecord(wxCommandEvent &event)
1079 string file_types;
1080 if( !IsCardable(m_current_dbname, &file_types) )
1081 return;
1083 // we are loading files here, so also allow *.*
1084 file_types += "|";
1085 file_types += _C("All files");
1086 file_types += " (*.*)|*.*";
1088 wxString file_filter(file_types.c_str(), wxConvUTF8);
1090 wxFileDialog dlg(m_parent, _W("Load Record..."), _T(""), _T(""),
1091 file_filter,
1092 wxFD_OPEN | wxFD_PREVIEW);
1093 if( dlg.ShowModal() != wxID_OK )
1094 return;
1096 // open file
1097 ifstream ifs(dlg.GetPath().utf8_str());
1098 string vrec;
1099 vector<string> types;
1100 if( !MimeBuilder::ReadMimeRecord(ifs, vrec, types) ) {
1101 wxMessageBox(_W("No card data found in: ") + dlg.GetPath(),
1102 _W("Import Read Error"),
1103 wxOK | wxICON_ERROR, m_parent);
1104 return;
1107 DataCachePtr rp;
1108 string convert_error;
1109 try {
1110 const string &dn = m_current_dbname;
1112 // and read per record type:
1113 if( m_current_dbname == Contact::GetDBName() ) {
1114 if( !CheckTypes<Sync::vCard>(m_parent, dn, types) )
1115 return;
1116 Sync::vCard vcard;
1117 Contact rec = vcard.ToBarry(vrec.c_str(), 0);
1118 rp.reset( new RecordCache<Contact>(0, rec) );
1120 else if( m_current_dbname == Calendar::GetDBName() ) {
1121 if( !CheckTypes<Sync::vCalendar>(m_parent, dn, types) )
1122 return;
1123 Sync::vTimeConverter vtc;
1124 Sync::vCalendar vcal(vtc);
1125 Calendar rec = vcal.ToBarry(vrec.c_str(), 0);
1126 rp.reset( new RecordCache<Calendar>(0, rec) );
1128 else if( m_current_dbname == Memo::GetDBName() ) {
1129 if( !CheckTypes<Sync::vJournal>(m_parent, dn, types) )
1130 return;
1131 Sync::vTimeConverter vtc;
1132 Sync::vJournal vjournal(vtc);
1133 Memo rec = vjournal.ToBarry(vrec.c_str(), 0);
1134 rp.reset( new RecordCache<Memo>(0, rec) );
1136 else if( m_current_dbname == Task::GetDBName() ) {
1137 if( !CheckTypes<Sync::vTodo>(m_parent, dn, types) )
1138 return;
1139 Sync::vTimeConverter vtc;
1140 Sync::vTodo vtodo(vtc);
1141 Task rec = vtodo.ToBarry(vrec.c_str(), 0);
1142 rp.reset( new RecordCache<Task>(0, rec) );
1144 } catch( Barry::ConvertError &ce ) {
1145 convert_error = ce.what();
1148 if( convert_error.size() ) {
1149 wxString msg = wxString::Format(_W("Unable to import selected file: %s"), wxString(convert_error.c_str(), wxConvUTF8).c_str());
1150 wxMessageBox(msg, _W("Import Error"), wxOK | wxICON_ERROR,
1151 m_parent);
1152 return;
1155 // grab the cache for the current database... Get is ok here,
1156 // since the cache is already loaded by the main db list
1157 DBMap::DBCachePtr dbp = m_dbmap->GetDBCache(m_current_dbname);
1158 if( !dbp.get() ) {
1159 wxMessageBox(_W("Internal pointer error: cannot find DBCachePtr for: ") + wxString(m_current_dbname.c_str(), wxConvUTF8),
1160 _W("Internal Error"),
1161 wxOK | wxICON_ERROR, m_parent);
1162 return;
1165 DBCache::iterator i = dbp->Add(m_parent, rp);
1166 if( i != dbp->end() ) {
1167 wxString text((*i)->GetDescription().c_str(), wxConvUTF8);
1169 // insert new record in same spot as DBCache has it
1170 m_current_record_item = dbp->GetIndex(i);
1171 m_record_list->InsertItem(m_current_record_item, text);
1173 else {
1174 wxMessageBox(_W("Internal error: cannot add record to DBCache"),
1175 _W("Internal Error"),
1176 wxOK | wxICON_ERROR, m_parent);
1177 return;
1181 void BrowseMode::OnExportRecord(wxCommandEvent &event)
1183 string file_types;
1184 if( !IsCardable(m_current_dbname, &file_types) )
1185 return;
1187 // grab the cache for the current database... Get is ok here,
1188 // since the cache is already loaded by the main db list
1189 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1190 if( !p.get() )
1191 return;
1193 DBCache::iterator i = p->Get(m_current_record_item);
1194 string vdata;
1195 if( (*i)->Card(m_parent, vdata) ) {
1196 MimeExportDlg dlg(m_parent, vdata, file_types);
1197 dlg.ShowModal();
1201 void BrowseMode::OnAddRecord(wxCommandEvent &event)
1203 // grab the cache for the current database... Get is ok here,
1204 // since the cache is already loaded by the main db list
1205 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1206 if( !p.get() )
1207 return;
1209 DBCache::iterator i = p->Add(m_parent, *m_zones, p->end());
1210 if( i != p->end() ) {
1211 wxString text((*i)->GetDescription().c_str(), wxConvUTF8);
1213 // insert new record in same spot as DBCache has it
1214 m_current_record_item = p->GetIndex(i);
1215 m_record_list->InsertItem(m_current_record_item, text);
1219 void BrowseMode::OnCopyRecord(wxCommandEvent &event)
1221 // grab the cache for the current database... Get is ok here,
1222 // since the cache is already loaded by the main db list
1223 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1224 if( !p.get() )
1225 return;
1227 DBCache::iterator source = p->Get(m_current_record_item);
1228 DBCache::iterator i = p->Add(m_parent, *m_zones, source);
1229 if( i != p->end() ) {
1230 wxString text((*i)->GetDescription().c_str(), wxConvUTF8);
1232 // insert new record in same spot as DBCache has it
1233 m_current_record_item = p->GetIndex(i);
1234 m_record_list->InsertItem(m_current_record_item, text);
1238 void BrowseMode::OnEditRecord(wxCommandEvent &event)
1240 // grab the cache for the current database... Get is ok here,
1241 // since the cache is already loaded by the main db list
1242 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1243 if( !p.get() )
1244 return;
1246 DBCache::iterator i = p->Get(m_current_record_item);
1247 if( p->Edit(m_parent, *m_zones, i) ) {
1248 wxString text((*i)->GetDescription().c_str(), wxConvUTF8);
1249 m_record_list->SetItem(m_current_record_item, 0, text);
1253 void BrowseMode::OnDeleteRecord(wxCommandEvent &event)
1255 // grab the cache for the current database... Get is ok here,
1256 // since the cache is already loaded by the main db list
1257 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1258 if( !p.get() )
1259 return;
1261 DBCache::iterator i = p->Get(m_current_record_item);
1262 if( p->Delete(m_parent, i) ) {
1263 m_record_list->DeleteItem(m_current_record_item);
1267 void BrowseMode::OnStatusEvent(wxCommandEvent &event)
1269 m_load_status_text->SetLabel(event.GetString());