desktop: added Import and Export buttons to Browse mode (MIME vcard support)
[barry/progweb.git] / desktop / src / Mode_Browse.cc
blob56fbdd98184c0bea47036559ecc016e3c0c0c361
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(_T("This database is apparently read-only. If this device is connected to a BES, you cannot edit records via USB. (Error: ") + what + _T(")"),
72 _T("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 = "VCard files (*.vcf;*.vcard)|*.vcf;*.vcard";
93 return true;
96 else if( dbname == Calendar::GetDBName() ||
97 dbname == Memo::GetDBName() ||
98 dbname == Task::GetDBName() ) {
100 if( file_types )
101 *file_types = "ICalendar files (*.ical;*.ics;*.ifb;*.icalendar)|*.ical;*.ics;*.ifb;*.icalendar";
102 return true;
106 return false;
109 bool EditRecord(wxWindow *parent,
110 bool editable,
111 const Barry::TimeZones &zones,
112 Barry::Contact &rec)
114 ContactEditDlg edit(parent, rec, editable);
115 return edit.ShowModal() == wxID_OK;
118 bool EditRecord(wxWindow *parent,
119 bool editable,
120 const Barry::TimeZones &zones,
121 Barry::Bookmark &rec)
123 return false;
126 bool EditRecord(wxWindow *parent,
127 bool editable,
128 const Barry::TimeZones &zones,
129 Barry::Calendar &rec)
131 CalendarEditDlg edit(parent, rec, editable, &zones);
132 return edit.ShowModal() == wxID_OK;
135 bool EditRecord(wxWindow *parent,
136 bool editable,
137 const Barry::TimeZones &zones,
138 Barry::CalendarAll &rec)
140 return false;
143 bool EditRecord(wxWindow *parent,
144 bool editable,
145 const Barry::TimeZones &zones,
146 Barry::ContentStore &rec)
148 return false;
151 bool EditRecord(wxWindow *parent,
152 bool editable,
153 const Barry::TimeZones &zones,
154 Barry::Folder &rec)
156 return false;
159 bool EditRecord(wxWindow *parent,
160 bool editable,
161 const Barry::TimeZones &zones,
162 Barry::Memo &rec)
164 MemoEditDlg edit(parent, rec, editable);
165 return edit.ShowModal() == wxID_OK;
168 bool EditRecord(wxWindow *parent,
169 bool editable,
170 const Barry::TimeZones &zones,
171 Barry::Message &rec)
173 return false;
176 bool EditRecord(wxWindow *parent,
177 bool editable,
178 const Barry::TimeZones &zones,
179 Barry::CallLog &rec)
181 return false;
184 bool EditRecord(wxWindow *parent,
185 bool editable,
186 const Barry::TimeZones &zones,
187 Barry::PINMessage &rec)
189 return false;
192 bool EditRecord(wxWindow *parent,
193 bool editable,
194 const Barry::TimeZones &zones,
195 Barry::SavedMessage &rec)
197 return false;
200 bool EditRecord(wxWindow *parent,
201 bool editable,
202 const Barry::TimeZones &zones,
203 Barry::ServiceBook &rec)
205 return false;
208 bool EditRecord(wxWindow *parent,
209 bool editable,
210 const Barry::TimeZones &zones,
211 Barry::Sms &rec)
213 return false;
216 bool EditRecord(wxWindow *parent,
217 bool editable,
218 const Barry::TimeZones &zones,
219 Barry::Task &rec)
221 TaskEditDlg edit(parent, rec, editable, &zones);
222 return edit.ShowModal() == wxID_OK;
225 bool EditRecord(wxWindow *parent,
226 bool editable,
227 const Barry::TimeZones &zones,
228 Barry::TimeZone &rec)
230 return false;
233 bool EditRecord(wxWindow *parent,
234 bool editable,
235 const Barry::TimeZones &zones,
236 Barry::HandheldAgent &rec)
238 return false;
242 //////////////////////////////////////////////////////////////////////////////
243 // GUIDesktopConnector
245 bool GUIDesktopConnector::PasswordPrompt(const Barry::BadPassword &bp,
246 std::string &password_result)
248 // create prompt based on exception data
249 ostringstream oss;
250 oss << "Please enter device password: ("
251 << bp.remaining_tries()
252 << " tries remaining)";
253 wxString prompt(oss.str().c_str(), wxConvUTF8);
255 // ask user for device password
256 wxString pass = wxGetPasswordFromUser(prompt,
257 _T("Device Password"), _T(""), m_parent);
259 password_result = pass.utf8_str();
261 // assume that a blank password means the user wishes to quit...
262 // wxWidgets doesn't seem to handle this very well?
263 return password_result.size() > 0;
266 //////////////////////////////////////////////////////////////////////////////
267 // DBDataCache
269 DBDataCache::DBDataCache(DataCache::IndexType index, const Barry::DBData &raw)
270 : DataCache(index, raw.GetUniqueId())
271 , m_raw(raw)
275 bool DBDataCache::Edit(wxWindow *parent,
276 bool editable,
277 const Barry::TimeZones &zones)
279 return false;
282 std::string DBDataCache::GetDescription() const
284 return "raw data";
288 //////////////////////////////////////////////////////////////////////////////
289 // DBCache
291 DBCache::DBCache(ThreadableDesktop &tdesktop, const std::string &dbname)
292 : m_tdesktop(tdesktop)
293 , m_dbname(dbname)
295 // lock the desktop
296 DesktopInstancePtr dip = m_tdesktop.Get();
298 // grab the DBID
299 m_dbid = dip->Desktop().GetDBID(m_dbname);
301 // load the record state table
302 dip->Desktop().GetRecordStateTable(m_dbid, m_state);
304 // load all records
305 AllRecordParser ap(*this, *this);
306 RecordStateTable::StateMapType::iterator i = m_state.StateMap.begin();
307 for( ; i != m_state.StateMap.end(); ++i ) {
308 m_index = i->second.Index; // save for the callback
309 dip->Desktop().GetRecord(m_dbid, m_index, ap);
312 // sort the list of records by description
313 m_records.sort();
316 DBCache::~DBCache()
320 DBCache::iterator DBCache::Get(int list_offset)
322 iterator i = begin();
323 for( ; i != end() && list_offset; ++i, list_offset-- )
325 return i;
328 DBCache::const_iterator DBCache::Get(int list_offset) const
330 const_iterator i = begin();
331 for( ; i != end() && list_offset; ++i, list_offset-- )
333 return i;
336 int DBCache::GetIndex(iterator record) const
338 iterator i = const_cast<DBCache*> (this)->begin();
339 iterator e = const_cast<DBCache*> (this)->end();
340 for( int index = 0; i != e; ++i, index++ ) {
341 if( i == record )
342 return index;
344 return -1;
347 int DBCache::GetIndex(const_iterator record) const
349 const_iterator i = begin();
350 for( int index = 0; i != end(); ++i, index++ ) {
351 if( i == record )
352 return index;
354 return -1;
357 DBCache::iterator DBCache::Add(wxWindow *parent, DataCachePtr p)
359 // see if this record has a builder
360 Barry::Builder *bp = dynamic_cast<Barry::Builder*> (p.get());
361 if( !bp ) {
362 cerr << "DataCachePtr has no builder" << endl;
363 return end();
366 // give record a new UniqueID
367 uint32_t record_id = m_state.MakeNewRecordId();
368 cout << "New recordID generated: 0x" << hex << record_id << endl;
369 p->SetIds(p->GetStateIndex(), record_id);
371 // add record to device
372 DesktopInstancePtr dip = m_tdesktop.Get();
373 Barry::Mode::Desktop &desktop = dip->Desktop();
374 bool iv = Barry::IsVerbose();
375 Barry::Verbose(true);
376 try {
377 desktop.AddRecord(m_dbid, *bp);
378 } catch( Barry::ReturnCodeError &rce ) {
379 cerr << "Device exception: " << rce.what() << endl;
380 if( rce.IsReadOnly() ) {
381 ShowReadOnlyMsg(parent, rce);
382 return end();
385 throw;
387 Barry::Verbose(iv);
389 // update our copy of the record state table from device
390 desktop.GetRecordStateTable(m_dbid, m_state);
391 cout << m_state << endl;
393 // find our new record_id in list, to find the state index
394 IndexType new_index;
395 if( !m_state.GetIndex(record_id, &new_index) ) {
396 throw std::logic_error("Need to reconnect for adding a record?");
399 // update new state_index in the data cache record
400 p->SetIds(new_index, record_id);
402 // add DataCachePtr to our own cache list
403 m_records.push_front(p);
405 // return iterator pointing to new record
406 return begin();
409 DBCache::iterator DBCache::Add(wxWindow *parent,
410 const Barry::TimeZones &zones,
411 iterator copy_record)
413 DataCachePtr p;
415 #undef HANDLE_BUILDER
416 #define HANDLE_BUILDER(tname) \
417 if( m_dbname == Barry::tname::GetDBName() ) { \
418 Barry::tname rec; \
419 if( copy_record != end() ) { \
420 RecordCache<Barry::tname> *rc = dynamic_cast<RecordCache<Barry::tname>* > (copy_record->get()); \
421 if( rc ) { \
422 rec = rc->GetRecord(); \
425 p.reset( new RecordCache<Barry::tname>(0, rec) ); \
427 ALL_KNOWN_BUILDER_TYPES
429 // anything else is not addable or buildable
430 if( !p.get() ) {
431 return end();
434 if( p->Edit(parent, true, zones) ) {
435 return Add(parent, p);
437 else {
438 return end();
442 bool DBCache::OverwriteRecord(wxWindow *parent, iterator record)
444 // see if this record has a builder
445 Barry::Builder *bp = dynamic_cast<Barry::Builder*> ((*record).get());
446 if( !bp )
447 return false;
449 cout << "Changing device record with index: 0x" << hex << (*record)->GetStateIndex() << endl;
450 cout << m_state << endl;
451 // update the device with new record data
452 DesktopInstancePtr dip = m_tdesktop.Get();
453 Barry::Mode::Desktop &desktop = dip->Desktop();
454 bool iv = Barry::IsVerbose();
455 Barry::Verbose(true);
456 try {
457 desktop.SetRecord(m_dbid, (*record)->GetStateIndex(), *bp);
458 } catch( Barry::ReturnCodeError &rce ) {
459 if( rce.IsReadOnly() ) {
460 ShowReadOnlyMsg(parent, rce);
461 return false;
464 throw;
466 Barry::Verbose(iv);
468 return true;
471 bool DBCache::DeleteAndAddRecord(wxWindow *parent, iterator record)
473 // see if this record has a builder
474 Barry::Builder *bp = dynamic_cast<Barry::Builder*> ((*record).get());
475 if( !bp )
476 return false;
478 cout << "Changing device record with index: 0x" << hex << (*record)->GetStateIndex() << endl;
479 cout << m_state << endl;
480 // update the device with new record data
481 DesktopInstancePtr dip = m_tdesktop.Get();
482 Barry::Mode::Desktop &desktop = dip->Desktop();
483 bool iv = Barry::IsVerbose();
484 Barry::Verbose(true);
485 try {
486 desktop.DeleteRecord(m_dbid, (*record)->GetStateIndex());
487 desktop.AddRecord(m_dbid, *bp);
488 } catch( Barry::ReturnCodeError &rce ) {
489 if( rce.IsReadOnly() ) {
490 ShowReadOnlyMsg(parent, rce);
491 return false;
494 throw;
496 Barry::Verbose(iv);
498 // update our copy of the record state table from device
499 desktop.GetRecordStateTable(m_dbid, m_state);
500 cout << m_state << endl;
502 // find our record_id in list, to find the state index
503 IndexType new_index;
504 if( !m_state.GetIndex((*record)->GetRecordId(), &new_index) ) {
505 throw std::logic_error("DAA: Need to reconnect for adding a record?");
508 // update new state_index in the data cache record
509 (*record)->SetIds(new_index, (*record)->GetRecordId());
511 return true;
514 bool DBCache::Edit(wxWindow *parent,
515 const Barry::TimeZones &zones,
516 iterator record)
518 if( record == end() )
519 return false;
521 if( (*record)->Edit(parent, true, zones) && (*record)->IsBuildable() ) {
522 // see if this record is part of the Tasks database
523 RecordCache<Barry::Task> *tp = dynamic_cast< RecordCache<Barry::Task>*> ((*record).get());
525 if( tp ) {
526 // yes, it is... the Tasks database has a bug
527 // so we need to "edit" by deleting and adding
528 // the record again
529 return DeleteAndAddRecord(parent, record);
531 else {
532 // use the normal code for all other records
533 return OverwriteRecord(parent, record);
536 else {
537 return false;
541 bool DBCache::Delete(wxWindow *parent, iterator record)
543 if( record == end() )
544 return false;
546 // prompt user with Yes / No message
547 wxString desc((*record)->GetDescription().c_str(), wxConvUTF8);
548 int choice = wxMessageBox(_T("Delete record: ") + desc + _T("?"),
549 _T("Record Delete"), wxYES_NO | wxICON_QUESTION, parent);
551 // if no, return false
552 if( choice != wxYES )
553 return false;
555 cout << "Deleting device record with index: 0x" << hex << (*record)->GetStateIndex() << endl;
556 cout << m_state << endl;
557 // delete record from device
558 DesktopInstancePtr dip = m_tdesktop.Get();
559 Barry::Mode::Desktop &desktop = dip->Desktop();
560 desktop.DeleteRecord(m_dbid, (*record)->GetStateIndex());
562 // remove record from cache list
563 m_records.erase(record);
564 return true;
567 // For Barry::AllRecordStore
568 #undef HANDLE_PARSER
569 #define HANDLE_PARSER(tname) \
570 void DBCache::operator() (const Barry::tname &rec) \
572 DataCachePtr p( new RecordCache<Barry::tname>(m_index, rec) ); \
573 m_records.push_front(p); \
575 ALL_KNOWN_PARSER_TYPES
577 // For Barry::Parser
578 void DBCache::ParseRecord(const Barry::DBData &data,
579 const Barry::IConverter *ic)
581 DataCachePtr p( new DBDataCache(m_index, data) );
582 m_records.push_front(p);
586 //////////////////////////////////////////////////////////////////////////////
587 // DBMap
589 DBMap::DBMap(ThreadableDesktop &tdesktop)
590 : m_tdesktop(tdesktop)
592 if( pthread_mutex_init(&m_map_mutex, NULL) ) {
593 throw Barry::Error("Failed to create map mutex");
596 if( pthread_mutex_init(&m_load_mutex, NULL) ) {
597 throw Barry::Error("Failed to create load mutex");
601 DBMap::DBCachePtr DBMap::LoadDBCache(const std::string &dbname)
603 // first, check for pre-loaded data, before the load lock,
604 // to make sure we return pre-loaded data with utmost haste
606 scoped_lock map_lock(m_map_mutex);
608 MapType::iterator i = m_map.find(dbname);
609 if( i != m_map.end() )
610 return i->second;
613 // if not found, lock and load, but be careful, since we
614 // don't want to open a window here for loading a db twice
615 scoped_lock load_lock(m_load_mutex);
617 // check again for pre-loaded data, since between
618 // map.unlock and load.lock there could have been
619 // another successful load
621 scoped_lock map_lock(m_map_mutex);
623 MapType::iterator i = m_map.find(dbname);
624 if( i != m_map.end() )
625 return i->second;
628 // do the load, without map.lock, since this can take a
629 // significant amount of time
630 DBCachePtr p( new DBCache(m_tdesktop, dbname) );
632 // lock once more to update the map, and then done
633 scoped_lock map_lock(m_map_mutex);
634 m_map[dbname] = p;
635 return p;
638 DBMap::DBCachePtr DBMap::GetDBCache(const std::string &dbname)
640 scoped_lock lock(m_map_mutex);
642 MapType::iterator i = m_map.find(dbname);
643 if( i != m_map.end() )
644 return i->second;
646 return DBCachePtr();
649 //////////////////////////////////////////////////////////////////////////////
650 // BrowseMode
652 BrowseMode::BrowseMode(wxWindow *parent, const ProbeResult &device)
653 : m_parent(parent)
654 , m_buildable(false)
655 , m_editable(false)
656 , m_cardable(false)
657 , m_show_all(false)
659 // create device identifying string
660 m_device_id_str = wxString(device.GetDisplayName().c_str(), wxConvUTF8);
663 // connect to the device
665 m_con.reset( new GUIDesktopConnector(m_parent, "", "utf-8", device) );
666 m_con->Reconnect(2);
667 m_tdesktop.reset( new ThreadableDesktop(*m_con) );
669 // keep our own copy, and sort by name for later
670 m_dbdb = m_con->GetDesktop().GetDBDB();
671 m_dbdb.SortByName();
673 // store a copy of the time zone set for record editing
674 if( TimeZones::IsLoadable(m_con->GetDesktop()) ) {
675 // load time zones from device itself
676 m_zones.reset( new TimeZones(m_con->GetDesktop()) );
678 else {
679 // use static time zone table from Barry library
680 m_zones.reset( new TimeZones );
683 CreateControls();
685 // create our DBMap and give it the threadable desktop,
686 // now that we're finished doing any desktop USB work
687 m_dbmap.reset( new DBMap(*m_tdesktop) );
690 // From here down, we assume that our constructor succeeds, with
691 // no exceptions!
694 // fire off a background thread to cache database records
695 // in advance... if it fails, don't worry about it
696 m_abort_flag = false;
697 int ret = pthread_create(&m_cache_thread, NULL,
698 &BrowseMode::FillCacheThread, this);
699 if( ret != 0 )
700 m_abort_flag = true; // no need to join later
702 // connect ourselves to the parent's event handling chain
703 // do this last, so that we are guaranteed our destructor
704 // will run, in case of exceptions
705 m_parent->PushEventHandler(this);
708 BrowseMode::~BrowseMode()
710 // unhook that event handler!
711 m_parent->PopEventHandler();
713 // make sure the cache thread is finished before we destroy it :-)
714 if( !m_abort_flag ) {
715 m_abort_flag = true;
716 void *junk;
717 pthread_join(m_cache_thread, &junk);
721 std::string& GetDBName(Barry::DatabaseDatabase::Database &db)
723 return db.Name;
726 void BrowseMode::SendStatusEvent(const std::string &dbname)
728 wxCommandEvent event(BMET_LOAD_STATUS, wxID_ANY);
729 event.SetEventObject(this);
731 if( dbname.size() ) {
732 wxString msg(_T("Loading: "));
733 msg += wxString(dbname.c_str(), wxConvUTF8);
734 event.SetString(msg);
736 else {
737 event.SetString(_T(""));
740 AddPendingEvent(event);
743 void BrowseMode::CreateControls()
745 m_top_sizer.reset( new wxBoxSizer(wxVERTICAL) );
747 // make space for the main header, which is not part of our
748 // work area
749 m_top_sizer->AddSpacer(MAIN_HEADER_OFFSET);
753 // add list boxes to main area, the list_sizer
756 wxStaticBoxSizer *list_sizer = new wxStaticBoxSizer(wxHORIZONTAL,
757 m_parent, m_device_id_str);
759 // add database listctrl
760 m_dbdb_list.reset (new wxListCtrl(m_parent, BrowseMode_DBDBList,
761 wxDefaultPosition, wxDefaultSize,
762 wxLC_REPORT | wxLC_SINGLE_SEL) ); //| wxLC_VRULES
763 // int max_db_width = GetMaxWidth(m_dbdb_list.get(),
764 // m_dbdb.Databases.begin(), m_dbdb.Databases.end(),
765 // &GetDBName);
766 list_sizer->Add( m_dbdb_list.get(), 4, wxEXPAND | wxALL, 4 );
768 // add the record listctrl
769 m_record_list.reset(new wxListCtrl(m_parent, BrowseMode_RecordList,
770 wxDefaultPosition, wxDefaultSize,
771 wxLC_REPORT | wxLC_SINGLE_SEL) ); //| wxLC_VRULES
772 list_sizer->Add( m_record_list.get(), 5, wxEXPAND | wxALL, 4 );
774 // add list sizer to top sizer
775 m_top_sizer->Add( list_sizer, 1, wxEXPAND | wxALL, 4 );
778 // add "show all" checkbox and load status static textbox, inside sizer
781 wxBoxSizer *status_sizer = new wxBoxSizer(wxHORIZONTAL);
783 m_show_all_checkbox.reset( new wxCheckBox(m_parent,
784 BrowseMode_ShowAllCheckbox,
785 _T("Show All Databases"),
786 wxDefaultPosition, wxDefaultSize,
787 wxCHK_2STATE) );
788 status_sizer->Add( m_show_all_checkbox.get(), 0, wxEXPAND, 0 );
789 m_show_all_checkbox->SetValue(m_show_all);
791 status_sizer->AddStretchSpacer();
793 m_load_status_text.reset( new wxStaticText(m_parent,
794 BrowseMode_LoadStatusText,
795 _T(""),
796 wxDefaultPosition, wxSize(200, -1),
797 wxST_NO_AUTORESIZE) );
798 status_sizer->Add( m_load_status_text.get(), 0,
799 wxEXPAND | wxALIGN_CENTRE_VERTICAL, 0 );
801 m_top_sizer->Add( status_sizer, 0, wxEXPAND | wxALL, 4 );
806 // bottom buttons
809 // add bottom buttons - these go in the bottom FOOTER area
810 // so their heights must be fixed to MAIN_HEADER_OFFSET
811 // minus a border of 5px top and bottom
812 wxSize footer(75, MAIN_HEADER_OFFSET - 5 - 5);
813 wxBoxSizer *buttons = new wxBoxSizer(wxHORIZONTAL);
814 m_import_record_button.reset( new wxButton(m_parent,
815 BrowseMode_ImportRecordButton, _T("Import..."),
816 wxDefaultPosition, footer) );
817 m_export_record_button.reset( new wxButton(m_parent,
818 BrowseMode_ExportRecordButton, _T("Export..."),
819 wxDefaultPosition, footer) );
820 m_add_record_button.reset( new wxButton(m_parent,
821 BrowseMode_AddRecordButton, _T("Add..."),
822 wxDefaultPosition, footer) );
823 m_copy_record_button.reset( new wxButton(m_parent,
824 BrowseMode_CopyRecordButton, _T("Copy..."),
825 wxDefaultPosition, footer) );
826 m_edit_record_button.reset( new wxButton(m_parent,
827 BrowseMode_EditRecordButton, _T("Edit..."),
828 wxDefaultPosition, footer));
829 m_delete_record_button.reset( new wxButton(m_parent,
830 BrowseMode_DeleteRecordButton, _T("Delete..."),
831 wxDefaultPosition, footer) );
832 buttons->Add(m_import_record_button.get(), 0, wxRIGHT, 5);
833 buttons->Add(m_export_record_button.get(), 0, wxRIGHT, 5);
834 buttons->Add(m_add_record_button.get(), 0, wxRIGHT, 5);
835 buttons->Add(m_copy_record_button.get(), 0, wxRIGHT, 5);
836 buttons->Add(m_edit_record_button.get(), 0, wxRIGHT, 5);
837 buttons->Add(m_delete_record_button.get(), 0, wxRIGHT, 5);
838 m_top_sizer->Add(buttons, 0, wxALL | wxALIGN_RIGHT, 5);
841 // recalc size of children and add columns
843 wxSize client_size = m_parent->GetClientSize();
844 m_top_sizer->SetDimension(0, 0,
845 client_size.GetWidth(), client_size.GetHeight());
847 // m_dbdb_list
848 wxSize dbdb_size = m_dbdb_list->GetClientSize();
849 int scroll_width = wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
850 int size = dbdb_size.GetWidth() - scroll_width;
851 m_dbdb_list->InsertColumn(0, _T("Databases"), wxLIST_FORMAT_LEFT,
852 size * 0.80);
853 m_dbdb_list->InsertColumn(1, _T("Count"), wxLIST_FORMAT_LEFT,
854 size * 0.20 + scroll_width); // add back the scroll width
855 // so it doesn't look half-baked when
856 // there is no scroll bar
859 // m_record_list
860 wxSize record_size = m_record_list->GetClientSize();
861 m_record_list->InsertColumn(0, _T("Record Description"),
862 wxLIST_FORMAT_LEFT, record_size.GetWidth());
866 // add data
868 FillDBDBList();
871 // attempt to re-select the devices as we last saw them
872 ReselectDevices(m_device_set->String2Subset(wxGetApp().GetGlobalConfig().GetKey("SelectedDevices")));
873 UpdateButtons();
878 int BrowseMode::GUItoDBDBIndex(int gui_index)
880 if( m_show_all )
881 return gui_index;
883 DatabaseDatabase::DatabaseArrayType::const_iterator
884 i = m_dbdb.Databases.begin(), e = m_dbdb.Databases.end();
885 for( int index = 0; i != e; ++i, index++ ) {
886 // only bump index on the parsable databases
887 if( !m_show_all && !IsParsable(i->Name) )
888 continue;
890 if( !gui_index )
891 return index;
893 gui_index--;
896 // error
897 return -1;
900 void BrowseMode::FillDBDBList()
902 // start fresh
903 m_dbdb_list->DeleteAllItems();
905 DatabaseDatabase::DatabaseArrayType::const_iterator
906 i = m_dbdb.Databases.begin(), e = m_dbdb.Databases.end();
907 for( int index = 0; i != e; ++i, index++ ) {
908 // Only show parsable databases, depending on GUI
909 if( !m_show_all && !IsParsable(i->Name) )
910 continue;
912 // Database Name
913 wxString text(i->Name.c_str(), wxConvUTF8);
914 long item = m_dbdb_list->InsertItem(index, text);
916 // Record Count
917 ostringstream oss;
918 oss << dec << i->RecordCount;
919 text = wxString(oss.str().c_str(), wxConvUTF8);
920 m_dbdb_list->SetItem(item, 1, text);
923 UpdateButtons();
926 void BrowseMode::FillRecordList(const std::string &dbname)
928 try {
930 // start fresh
931 m_record_list->DeleteAllItems();
933 // grab our DB
934 DBMap::DBCachePtr db = m_dbmap->LoadDBCache(dbname);
936 // cycle through the cache, and insert the descriptions
937 // given for each record
938 DBCache::const_iterator b = db->begin(), e = db->end();
939 for( int index = 0; b != e; ++b, index++ ) {
940 wxString text((*b)->GetDescription().c_str(), wxConvUTF8);
941 //long item =
942 m_record_list->InsertItem(index, text);
945 } catch( Barry::Error &be ) {
946 cerr << be.what() << endl;
950 void BrowseMode::UpdateButtons()
952 int selected_count = m_record_list->GetSelectedItemCount();
954 // can only import if this is a cardable DB
955 m_import_record_button->Enable(m_cardable);
957 // can only export if this is a cardable DB and only 1 selected
958 m_export_record_button->Enable(m_cardable && selected_count == 1);
960 // can only add if we have a builder and dialog for this record type
961 m_add_record_button->Enable(m_buildable && m_editable);
963 // can only copy or edit if we have a builder, a dialog, and
964 // only 1 is selected
965 m_copy_record_button->Enable(
966 m_buildable && m_editable && selected_count == 1);
967 m_edit_record_button->Enable(
968 m_buildable && m_editable && selected_count == 1);
970 // can only delete if something is selected
971 m_delete_record_button->Enable(selected_count > 0);
974 void BrowseMode::FillCache()
976 // create a copy of the dbdb, and sort in ascending order of
977 // record count, so the last db loaded is the longest...
978 // hopefully this makes the UI more user-friendly and responsive
979 DatabaseDatabase dbdb = m_dbdb;
980 dbdb.SortByRecordCount();
982 // cycle through the dbdb and load all Parsable databases
983 DatabaseDatabase::DatabaseArrayType::const_iterator
984 i = dbdb.Databases.begin(), e = dbdb.Databases.end();
985 for( ; i != e; ++i ) {
986 if( IsParsable(i->Name) ) try {
987 SendStatusEvent(i->Name);
988 m_dbmap->LoadDBCache(i->Name);
989 } catch( Barry::Error &be ) {
990 cerr << be.what() << endl;
993 if( m_abort_flag )
994 break;
997 SendStatusEvent("");
999 // finished
1000 m_abort_flag = true;
1003 void* BrowseMode::FillCacheThread(void *bobj)
1005 BrowseMode *bm = (BrowseMode*) bobj;
1006 bm->FillCache();
1007 return NULL;
1010 void BrowseMode::OnDBDBListSelChange(wxListEvent &event)
1012 wxBusyCursor wait;
1013 int index = GUItoDBDBIndex(event.GetIndex());
1014 m_current_dbname = m_dbdb.Databases.at(index).Name;
1015 m_buildable = ::IsBuildable(m_current_dbname);
1016 m_editable = ::IsEditable(m_current_dbname);
1017 m_cardable = ::IsCardable(m_current_dbname);
1018 m_current_record_item = -1;
1020 FillRecordList(m_current_dbname);
1021 UpdateButtons();
1024 void BrowseMode::OnRecordListSelChange(wxListEvent &event)
1026 // grab the cache for the current database... Get is ok here,
1027 // since the cache is already loaded by the main db list
1028 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1029 if( !p.get() )
1030 return;
1032 // grab the record list index
1033 m_current_record_item = event.GetIndex();
1034 // m_current_record_item = m_record_list->GetNextItem(
1035 // m_current_record_item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1037 UpdateButtons();
1040 void BrowseMode::OnRecordListActivated(wxListEvent &event)
1042 wxCommandEvent ce;
1043 OnEditRecord(ce);
1046 void BrowseMode::OnShowAll(wxCommandEvent &event)
1048 m_show_all = !m_show_all;
1049 FillDBDBList();
1052 template <class SyncT>
1053 bool CheckTypes(wxWindow *parent,
1054 const string &dbname,
1055 const vector<string> &types)
1057 if( !MimeBuilder::IsMember(SyncT::GetVName(), types) ) {
1058 CategoryList tlist;
1059 tlist = types;
1060 string tslist;
1061 tlist.CategoryList2Str(tslist);
1063 wxString msg = wxString::Format(_T("Card file type (%s) does not match record you are trying to add (%s)."),
1064 wxString(tslist.c_str(), wxConvUTF8).c_str(),
1065 wxString(dbname.c_str(), wxConvUTF8).c_str());
1066 wxMessageBox(msg, _T("Invalid Card Type"),
1067 wxOK | wxICON_INFORMATION, parent);
1068 return false;
1071 return true;
1074 void BrowseMode::OnImportRecord(wxCommandEvent &event)
1076 string file_types;
1077 if( !IsCardable(m_current_dbname, &file_types) )
1078 return;
1080 // we are loading files here, so also allow *.*
1081 file_types += "|All files (*.*)|*.*";
1083 wxString file_filter(file_types.c_str(), wxConvUTF8);
1085 wxFileDialog dlg(m_parent, _T("Load Record..."), _T(""), _T(""),
1086 file_filter,
1087 wxFD_OPEN | wxFD_PREVIEW);
1088 if( dlg.ShowModal() != wxID_OK )
1089 return;
1091 // open file
1092 ifstream ifs(dlg.GetPath().utf8_str());
1093 string vrec;
1094 vector<string> types;
1095 if( !MimeBuilder::ReadMimeRecord(ifs, vrec, types) ) {
1096 wxMessageBox(_T("No card data found in: ") + dlg.GetPath(),
1097 _T("Import Read Error"),
1098 wxOK | wxICON_ERROR, m_parent);
1099 return;
1102 DataCachePtr rp;
1103 string convert_error;
1104 try {
1105 const string &dn = m_current_dbname;
1107 // and read per record type:
1108 if( m_current_dbname == Contact::GetDBName() ) {
1109 if( !CheckTypes<Sync::vCard>(m_parent, dn, types) )
1110 return;
1111 Sync::vCard vcard;
1112 Contact rec = vcard.ToBarry(vrec.c_str(), 0);
1113 rp.reset( new RecordCache<Contact>(0, rec) );
1115 else if( m_current_dbname == Calendar::GetDBName() ) {
1116 if( !CheckTypes<Sync::vCalendar>(m_parent, dn, types) )
1117 return;
1118 Sync::vTimeConverter vtc;
1119 Sync::vCalendar vcal(vtc);
1120 Calendar rec = vcal.ToBarry(vrec.c_str(), 0);
1121 rp.reset( new RecordCache<Calendar>(0, rec) );
1123 else if( m_current_dbname == Memo::GetDBName() ) {
1124 if( !CheckTypes<Sync::vJournal>(m_parent, dn, types) )
1125 return;
1126 Sync::vTimeConverter vtc;
1127 Sync::vJournal vjournal(vtc);
1128 Memo rec = vjournal.ToBarry(vrec.c_str(), 0);
1129 rp.reset( new RecordCache<Memo>(0, rec) );
1131 else if( m_current_dbname == Task::GetDBName() ) {
1132 if( !CheckTypes<Sync::vTodo>(m_parent, dn, types) )
1133 return;
1134 Sync::vTimeConverter vtc;
1135 Sync::vTodo vtodo(vtc);
1136 Task rec = vtodo.ToBarry(vrec.c_str(), 0);
1137 rp.reset( new RecordCache<Task>(0, rec) );
1139 } catch( Barry::ConvertError &ce ) {
1140 convert_error = ce.what();
1143 if( convert_error.size() ) {
1144 wxString msg = wxString::Format(_T("Unable to import selected file: %s"), wxString(convert_error.c_str(), wxConvUTF8).c_str());
1145 wxMessageBox(msg, _T("Import Error"), wxOK | wxICON_ERROR,
1146 m_parent);
1147 return;
1150 // grab the cache for the current database... Get is ok here,
1151 // since the cache is already loaded by the main db list
1152 DBMap::DBCachePtr dbp = m_dbmap->GetDBCache(m_current_dbname);
1153 if( !dbp.get() ) {
1154 wxMessageBox(_T("Internal pointer error: cannot find DBCachePtr for: ") + wxString(m_current_dbname.c_str(), wxConvUTF8),
1155 _T("Internal Error"),
1156 wxOK | wxICON_ERROR, m_parent);
1157 return;
1160 DBCache::iterator i = dbp->Add(m_parent, rp);
1161 if( i != dbp->end() ) {
1162 wxString text((*i)->GetDescription().c_str(), wxConvUTF8);
1164 // insert new record in same spot as DBCache has it
1165 m_current_record_item = dbp->GetIndex(i);
1166 m_record_list->InsertItem(m_current_record_item, text);
1168 else {
1169 wxMessageBox(_T("Internal error: cannot add record to DBCache"),
1170 _T("Internal Error"),
1171 wxOK | wxICON_ERROR, m_parent);
1172 return;
1176 void BrowseMode::OnExportRecord(wxCommandEvent &event)
1178 string file_types;
1179 if( !IsCardable(m_current_dbname, &file_types) )
1180 return;
1182 // grab the cache for the current database... Get is ok here,
1183 // since the cache is already loaded by the main db list
1184 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1185 if( !p.get() )
1186 return;
1188 DBCache::iterator i = p->Get(m_current_record_item);
1189 string vdata;
1190 if( (*i)->Card(m_parent, vdata) ) {
1191 MimeExportDlg dlg(m_parent, vdata, file_types);
1192 dlg.ShowModal();
1196 void BrowseMode::OnAddRecord(wxCommandEvent &event)
1198 // grab the cache for the current database... Get is ok here,
1199 // since the cache is already loaded by the main db list
1200 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1201 if( !p.get() )
1202 return;
1204 DBCache::iterator i = p->Add(m_parent, *m_zones, p->end());
1205 if( i != p->end() ) {
1206 wxString text((*i)->GetDescription().c_str(), wxConvUTF8);
1208 // insert new record in same spot as DBCache has it
1209 m_current_record_item = p->GetIndex(i);
1210 m_record_list->InsertItem(m_current_record_item, text);
1214 void BrowseMode::OnCopyRecord(wxCommandEvent &event)
1216 // grab the cache for the current database... Get is ok here,
1217 // since the cache is already loaded by the main db list
1218 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1219 if( !p.get() )
1220 return;
1222 DBCache::iterator source = p->Get(m_current_record_item);
1223 DBCache::iterator i = p->Add(m_parent, *m_zones, source);
1224 if( i != p->end() ) {
1225 wxString text((*i)->GetDescription().c_str(), wxConvUTF8);
1227 // insert new record in same spot as DBCache has it
1228 m_current_record_item = p->GetIndex(i);
1229 m_record_list->InsertItem(m_current_record_item, text);
1233 void BrowseMode::OnEditRecord(wxCommandEvent &event)
1235 // grab the cache for the current database... Get is ok here,
1236 // since the cache is already loaded by the main db list
1237 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1238 if( !p.get() )
1239 return;
1241 DBCache::iterator i = p->Get(m_current_record_item);
1242 if( p->Edit(m_parent, *m_zones, i) ) {
1243 wxString text((*i)->GetDescription().c_str(), wxConvUTF8);
1244 m_record_list->SetItem(m_current_record_item, 0, text);
1248 void BrowseMode::OnDeleteRecord(wxCommandEvent &event)
1250 // grab the cache for the current database... Get is ok here,
1251 // since the cache is already loaded by the main db list
1252 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1253 if( !p.get() )
1254 return;
1256 DBCache::iterator i = p->Get(m_current_record_item);
1257 if( p->Delete(m_parent, i) ) {
1258 m_record_list->DeleteItem(m_current_record_item);
1262 void BrowseMode::OnStatusEvent(wxCommandEvent &event)
1264 m_load_status_text->SetLabel(event.GetString());