Added workaround to Desktop for Tasks database corruption
[barry.git] / desktop / src / Mode_Browse.cc
blob8b4b0732eb5fed0e27718149396bc6720c09f9e4
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 "windowids.h"
29 #include <iostream>
30 #include <sstream>
31 #include <iomanip>
33 using namespace std;
34 using namespace Barry;
36 DEFINE_EVENT_TYPE(BMET_LOAD_STATUS)
38 BEGIN_EVENT_TABLE(BrowseMode, wxEvtHandler)
39 EVT_LIST_ITEM_SELECTED(BrowseMode_DBDBList,
40 BrowseMode::OnDBDBListSelChange)
41 EVT_LIST_ITEM_SELECTED(BrowseMode_RecordList,
42 BrowseMode::OnRecordListSelChange)
43 EVT_LIST_ITEM_ACTIVATED(BrowseMode_RecordList,
44 BrowseMode::OnRecordListActivated)
45 EVT_CHECKBOX (BrowseMode_ShowAllCheckbox,
46 BrowseMode::OnShowAll)
47 EVT_BUTTON (BrowseMode_AddRecordButton,
48 BrowseMode::OnAddRecord)
49 EVT_BUTTON (BrowseMode_CopyRecordButton,
50 BrowseMode::OnCopyRecord)
51 EVT_BUTTON (BrowseMode_EditRecordButton,
52 BrowseMode::OnEditRecord)
53 EVT_BUTTON (BrowseMode_DeleteRecordButton,
54 BrowseMode::OnDeleteRecord)
55 EVT_COMMAND (wxID_ANY, BMET_LOAD_STATUS,
56 BrowseMode::OnStatusEvent)
57 END_EVENT_TABLE()
60 //////////////////////////////////////////////////////////////////////////////
61 // Standalone functions
63 void ShowReadOnlyMsg(wxWindow *parent, const Barry::ReturnCodeError &rce)
65 wxString what(rce.what(), wxConvUTF8);
66 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(")"),
67 _T("Device Error"), wxOK | wxICON_ERROR, parent);
70 bool IsEditable(const std::string &dbname)
72 // add entry here for each edit dialog available
73 return dbname == Contact::GetDBName() ||
74 dbname == Calendar::GetDBName() ||
75 dbname == Memo::GetDBName() ||
76 dbname == Task::GetDBName();
79 bool EditRecord(wxWindow *parent,
80 bool editable,
81 const Barry::TimeZones &zones,
82 Barry::Contact &rec)
84 ContactEditDlg edit(parent, rec, editable);
85 return edit.ShowModal() == wxID_OK;
88 bool EditRecord(wxWindow *parent,
89 bool editable,
90 const Barry::TimeZones &zones,
91 Barry::Bookmark &rec)
93 return false;
96 bool EditRecord(wxWindow *parent,
97 bool editable,
98 const Barry::TimeZones &zones,
99 Barry::Calendar &rec)
101 CalendarEditDlg edit(parent, rec, editable, &zones);
102 return edit.ShowModal() == wxID_OK;
105 bool EditRecord(wxWindow *parent,
106 bool editable,
107 const Barry::TimeZones &zones,
108 Barry::CalendarAll &rec)
110 return false;
113 bool EditRecord(wxWindow *parent,
114 bool editable,
115 const Barry::TimeZones &zones,
116 Barry::ContentStore &rec)
118 return false;
121 bool EditRecord(wxWindow *parent,
122 bool editable,
123 const Barry::TimeZones &zones,
124 Barry::Folder &rec)
126 return false;
129 bool EditRecord(wxWindow *parent,
130 bool editable,
131 const Barry::TimeZones &zones,
132 Barry::Memo &rec)
134 MemoEditDlg edit(parent, rec, editable);
135 return edit.ShowModal() == wxID_OK;
138 bool EditRecord(wxWindow *parent,
139 bool editable,
140 const Barry::TimeZones &zones,
141 Barry::Message &rec)
143 return false;
146 bool EditRecord(wxWindow *parent,
147 bool editable,
148 const Barry::TimeZones &zones,
149 Barry::CallLog &rec)
151 return false;
154 bool EditRecord(wxWindow *parent,
155 bool editable,
156 const Barry::TimeZones &zones,
157 Barry::PINMessage &rec)
159 return false;
162 bool EditRecord(wxWindow *parent,
163 bool editable,
164 const Barry::TimeZones &zones,
165 Barry::SavedMessage &rec)
167 return false;
170 bool EditRecord(wxWindow *parent,
171 bool editable,
172 const Barry::TimeZones &zones,
173 Barry::ServiceBook &rec)
175 return false;
178 bool EditRecord(wxWindow *parent,
179 bool editable,
180 const Barry::TimeZones &zones,
181 Barry::Sms &rec)
183 return false;
186 bool EditRecord(wxWindow *parent,
187 bool editable,
188 const Barry::TimeZones &zones,
189 Barry::Task &rec)
191 TaskEditDlg edit(parent, rec, editable, &zones);
192 return edit.ShowModal() == wxID_OK;
195 bool EditRecord(wxWindow *parent,
196 bool editable,
197 const Barry::TimeZones &zones,
198 Barry::TimeZone &rec)
200 return false;
203 bool EditRecord(wxWindow *parent,
204 bool editable,
205 const Barry::TimeZones &zones,
206 Barry::HandheldAgent &rec)
208 return false;
212 //////////////////////////////////////////////////////////////////////////////
213 // GUIDesktopConnector
215 bool GUIDesktopConnector::PasswordPrompt(const Barry::BadPassword &bp,
216 std::string &password_result)
218 // create prompt based on exception data
219 ostringstream oss;
220 oss << "Please enter device password: ("
221 << bp.remaining_tries()
222 << " tries remaining)";
223 wxString prompt(oss.str().c_str(), wxConvUTF8);
225 // ask user for device password
226 wxString pass = wxGetPasswordFromUser(prompt,
227 _T("Device Password"), _T(""), m_parent);
229 password_result = pass.utf8_str();
231 // assume that a blank password means the user wishes to quit...
232 // wxWidgets doesn't seem to handle this very well?
233 return password_result.size() > 0;
236 //////////////////////////////////////////////////////////////////////////////
237 // DBDataCache
239 DBDataCache::DBDataCache(DataCache::IndexType index, const Barry::DBData &raw)
240 : DataCache(index, raw.GetUniqueId())
241 , m_raw(raw)
245 bool DBDataCache::Edit(wxWindow *parent,
246 bool editable,
247 const Barry::TimeZones &zones)
249 return false;
252 std::string DBDataCache::GetDescription() const
254 return "raw data";
258 //////////////////////////////////////////////////////////////////////////////
259 // DBCache
261 DBCache::DBCache(ThreadableDesktop &tdesktop, const std::string &dbname)
262 : m_tdesktop(tdesktop)
263 , m_dbname(dbname)
265 // lock the desktop
266 DesktopInstancePtr dip = m_tdesktop.Get();
268 // grab the DBID
269 m_dbid = dip->Desktop().GetDBID(m_dbname);
271 // load the record state table
272 dip->Desktop().GetRecordStateTable(m_dbid, m_state);
274 // load all records
275 AllRecordParser ap(*this, *this);
276 RecordStateTable::StateMapType::iterator i = m_state.StateMap.begin();
277 for( ; i != m_state.StateMap.end(); ++i ) {
278 m_index = i->second.Index; // save for the callback
279 dip->Desktop().GetRecord(m_dbid, m_index, ap);
282 // sort the list of records by description
283 m_records.sort();
286 DBCache::~DBCache()
290 DBCache::iterator DBCache::Get(int list_offset)
292 iterator i = begin();
293 for( ; i != end() && list_offset; ++i, list_offset-- )
295 return i;
298 DBCache::const_iterator DBCache::Get(int list_offset) const
300 const_iterator i = begin();
301 for( ; i != end() && list_offset; ++i, list_offset-- )
303 return i;
306 int DBCache::GetIndex(iterator record) const
308 iterator i = const_cast<DBCache*> (this)->begin();
309 iterator e = const_cast<DBCache*> (this)->end();
310 for( int index = 0; i != e; ++i, index++ ) {
311 if( i == record )
312 return index;
314 return -1;
317 int DBCache::GetIndex(const_iterator record) const
319 const_iterator i = begin();
320 for( int index = 0; i != end(); ++i, index++ ) {
321 if( i == record )
322 return index;
324 return -1;
327 DBCache::iterator DBCache::Add(wxWindow *parent,
328 const Barry::TimeZones &zones,
329 iterator copy_record)
331 DataCachePtr p;
333 #undef HANDLE_BUILDER
334 #define HANDLE_BUILDER(tname) \
335 if( m_dbname == Barry::tname::GetDBName() ) { \
336 Barry::tname rec; \
337 if( copy_record != end() ) { \
338 RecordCache<Barry::tname> *rc = dynamic_cast<RecordCache<Barry::tname>* > (copy_record->get()); \
339 if( rc ) { \
340 rec = rc->GetRecord(); \
343 p.reset( new RecordCache<Barry::tname>(0, rec) ); \
345 ALL_KNOWN_BUILDER_TYPES
347 // anything else is not addable or buildable
348 if( !p.get() ) {
349 return end();
352 if( p->Edit(parent, true, zones) ) {
353 // see if this record has a builder
354 Barry::Builder *bp = dynamic_cast<Barry::Builder*> (p.get());
355 if( !bp ) {
356 return end();
359 // give record a new UniqueID
360 uint32_t record_id = m_state.MakeNewRecordId();
361 cout << "New recordID generated: 0x" << hex << record_id << endl;
362 p->SetIds(p->GetStateIndex(), record_id);
364 // add record to device
365 DesktopInstancePtr dip = m_tdesktop.Get();
366 Barry::Mode::Desktop &desktop = dip->Desktop();
367 bool iv = Barry::IsVerbose();
368 Barry::Verbose(true);
369 try {
370 desktop.AddRecord(m_dbid, *bp);
371 } catch( Barry::ReturnCodeError &rce ) {
372 if( rce.IsReadOnly() ) {
373 ShowReadOnlyMsg(parent, rce);
374 return end();
377 throw;
379 Barry::Verbose(iv);
381 // update our copy of the record state table from device
382 desktop.GetRecordStateTable(m_dbid, m_state);
383 cout << m_state << endl;
385 // find our new record_id in list, to find the state index
386 IndexType new_index;
387 if( !m_state.GetIndex(record_id, &new_index) ) {
388 throw std::logic_error("Need to reconnect for adding a record?");
391 // update new state_index in the data cache record
392 p->SetIds(new_index, record_id);
394 // add DataCachePtr to our own cache list
395 m_records.push_front(p);
397 // return iterator pointing to new record
398 return begin();
400 else {
401 return end();
405 bool DBCache::OverwriteRecord(wxWindow *parent, iterator record)
407 // see if this record has a builder
408 Barry::Builder *bp = dynamic_cast<Barry::Builder*> ((*record).get());
409 if( !bp )
410 return false;
412 cout << "Changing device record with index: 0x" << hex << (*record)->GetStateIndex() << endl;
413 cout << m_state << endl;
414 // update the device with new record data
415 DesktopInstancePtr dip = m_tdesktop.Get();
416 Barry::Mode::Desktop &desktop = dip->Desktop();
417 bool iv = Barry::IsVerbose();
418 Barry::Verbose(true);
419 try {
420 desktop.SetRecord(m_dbid, (*record)->GetStateIndex(), *bp);
421 } catch( Barry::ReturnCodeError &rce ) {
422 if( rce.IsReadOnly() ) {
423 ShowReadOnlyMsg(parent, rce);
424 return false;
427 throw;
429 Barry::Verbose(iv);
431 return true;
434 bool DBCache::DeleteAndAddRecord(wxWindow *parent, iterator record)
436 // see if this record has a builder
437 Barry::Builder *bp = dynamic_cast<Barry::Builder*> ((*record).get());
438 if( !bp )
439 return false;
441 cout << "Changing device record with index: 0x" << hex << (*record)->GetStateIndex() << endl;
442 cout << m_state << endl;
443 // update the device with new record data
444 DesktopInstancePtr dip = m_tdesktop.Get();
445 Barry::Mode::Desktop &desktop = dip->Desktop();
446 bool iv = Barry::IsVerbose();
447 Barry::Verbose(true);
448 try {
449 desktop.DeleteRecord(m_dbid, (*record)->GetStateIndex());
450 desktop.AddRecord(m_dbid, *bp);
451 } catch( Barry::ReturnCodeError &rce ) {
452 if( rce.IsReadOnly() ) {
453 ShowReadOnlyMsg(parent, rce);
454 return false;
457 throw;
459 Barry::Verbose(iv);
461 // update our copy of the record state table from device
462 desktop.GetRecordStateTable(m_dbid, m_state);
463 cout << m_state << endl;
465 // find our record_id in list, to find the state index
466 IndexType new_index;
467 if( !m_state.GetIndex((*record)->GetRecordId(), &new_index) ) {
468 throw std::logic_error("DAA: Need to reconnect for adding a record?");
471 // update new state_index in the data cache record
472 (*record)->SetIds(new_index, (*record)->GetRecordId());
474 return true;
477 bool DBCache::Edit(wxWindow *parent,
478 const Barry::TimeZones &zones,
479 iterator record)
481 if( record == end() )
482 return false;
484 if( (*record)->Edit(parent, true, zones) && (*record)->IsBuildable() ) {
485 // see if this record is part of the Tasks database
486 RecordCache<Barry::Task> *tp = dynamic_cast< RecordCache<Barry::Task>*> ((*record).get());
488 if( tp ) {
489 // yes, it is... the Tasks database has a bug
490 // so we need to "edit" by deleting and adding
491 // the record again
492 return DeleteAndAddRecord(parent, record);
494 else {
495 // use the normal code for all other records
496 return OverwriteRecord(parent, record);
499 else {
500 return false;
504 bool DBCache::Delete(wxWindow *parent, iterator record)
506 if( record == end() )
507 return false;
509 // prompt user with Yes / No message
510 wxString desc((*record)->GetDescription().c_str(), wxConvUTF8);
511 int choice = wxMessageBox(_T("Delete record: ") + desc + _T("?"),
512 _T("Record Delete"), wxYES_NO | wxICON_QUESTION, parent);
514 // if no, return false
515 if( choice != wxYES )
516 return false;
518 cout << "Deleting device record with index: 0x" << hex << (*record)->GetStateIndex() << endl;
519 cout << m_state << endl;
520 // delete record from device
521 DesktopInstancePtr dip = m_tdesktop.Get();
522 Barry::Mode::Desktop &desktop = dip->Desktop();
523 desktop.DeleteRecord(m_dbid, (*record)->GetStateIndex());
525 // remove record from cache list
526 m_records.erase(record);
527 return true;
530 // For Barry::AllRecordStore
531 #undef HANDLE_PARSER
532 #define HANDLE_PARSER(tname) \
533 void DBCache::operator() (const Barry::tname &rec) \
535 DataCachePtr p( new RecordCache<Barry::tname>(m_index, rec) ); \
536 m_records.push_front(p); \
538 ALL_KNOWN_PARSER_TYPES
540 // For Barry::Parser
541 void DBCache::ParseRecord(const Barry::DBData &data,
542 const Barry::IConverter *ic)
544 DataCachePtr p( new DBDataCache(m_index, data) );
545 m_records.push_front(p);
549 //////////////////////////////////////////////////////////////////////////////
550 // DBMap
552 DBMap::DBMap(ThreadableDesktop &tdesktop)
553 : m_tdesktop(tdesktop)
555 if( pthread_mutex_init(&m_map_mutex, NULL) ) {
556 throw Barry::Error("Failed to create map mutex");
559 if( pthread_mutex_init(&m_load_mutex, NULL) ) {
560 throw Barry::Error("Failed to create load mutex");
564 DBMap::DBCachePtr DBMap::LoadDBCache(const std::string &dbname)
566 // first, check for pre-loaded data, before the load lock,
567 // to make sure we return pre-loaded data with utmost haste
569 scoped_lock map_lock(m_map_mutex);
571 MapType::iterator i = m_map.find(dbname);
572 if( i != m_map.end() )
573 return i->second;
576 // if not found, lock and load, but be careful, since we
577 // don't want to open a window here for loading a db twice
578 scoped_lock load_lock(m_load_mutex);
580 // check again for pre-loaded data, since between
581 // map.unlock and load.lock there could have been
582 // another successful load
584 scoped_lock map_lock(m_map_mutex);
586 MapType::iterator i = m_map.find(dbname);
587 if( i != m_map.end() )
588 return i->second;
591 // do the load, without map.lock, since this can take a
592 // significant amount of time
593 DBCachePtr p( new DBCache(m_tdesktop, dbname) );
595 // lock once more to update the map, and then done
596 scoped_lock map_lock(m_map_mutex);
597 m_map[dbname] = p;
598 return p;
601 DBMap::DBCachePtr DBMap::GetDBCache(const std::string &dbname)
603 scoped_lock lock(m_map_mutex);
605 MapType::iterator i = m_map.find(dbname);
606 if( i != m_map.end() )
607 return i->second;
609 return DBCachePtr();
612 //////////////////////////////////////////////////////////////////////////////
613 // BrowseMode
615 BrowseMode::BrowseMode(wxWindow *parent, const ProbeResult &device)
616 : m_parent(parent)
617 , m_buildable(false)
618 , m_editable(false)
619 , m_show_all(false)
621 // create device identifying string
622 m_device_id_str = wxString(device.GetDisplayName().c_str(), wxConvUTF8);
625 // connect to the device
627 m_con.reset( new GUIDesktopConnector(m_parent, "", "utf-8", device) );
628 m_con->Reconnect(2);
629 m_tdesktop.reset( new ThreadableDesktop(*m_con) );
631 // keep our own copy, and sort by name for later
632 m_dbdb = m_con->GetDesktop().GetDBDB();
633 m_dbdb.SortByName();
635 // store a copy of the time zone set for record editing
636 if( TimeZones::IsLoadable(m_con->GetDesktop()) ) {
637 // load time zones from device itself
638 m_zones.reset( new TimeZones(m_con->GetDesktop()) );
640 else {
641 // use static time zone table from Barry library
642 m_zones.reset( new TimeZones );
645 CreateControls();
647 // create our DBMap and give it the threadable desktop,
648 // now that we're finished doing any desktop USB work
649 m_dbmap.reset( new DBMap(*m_tdesktop) );
652 // From here down, we assume that our constructor succeeds, with
653 // no exceptions!
656 // fire off a background thread to cache database records
657 // in advance... if it fails, don't worry about it
658 m_abort_flag = false;
659 int ret = pthread_create(&m_cache_thread, NULL,
660 &BrowseMode::FillCacheThread, this);
661 if( ret != 0 )
662 m_abort_flag = true; // no need to join later
664 // connect ourselves to the parent's event handling chain
665 // do this last, so that we are guaranteed our destructor
666 // will run, in case of exceptions
667 m_parent->PushEventHandler(this);
670 BrowseMode::~BrowseMode()
672 // unhook that event handler!
673 m_parent->PopEventHandler();
675 // make sure the cache thread is finished before we destroy it :-)
676 if( !m_abort_flag ) {
677 m_abort_flag = true;
678 void *junk;
679 pthread_join(m_cache_thread, &junk);
683 std::string& GetDBName(Barry::DatabaseDatabase::Database &db)
685 return db.Name;
688 void BrowseMode::SendStatusEvent(const std::string &dbname)
690 wxCommandEvent event(BMET_LOAD_STATUS, wxID_ANY);
691 event.SetEventObject(this);
693 if( dbname.size() ) {
694 wxString msg(_T("Loading: "));
695 msg += wxString(dbname.c_str(), wxConvUTF8);
696 event.SetString(msg);
698 else {
699 event.SetString(_T(""));
702 AddPendingEvent(event);
705 void BrowseMode::CreateControls()
707 m_top_sizer.reset( new wxBoxSizer(wxVERTICAL) );
709 // make space for the main header, which is not part of our
710 // work area
711 m_top_sizer->AddSpacer(MAIN_HEADER_OFFSET);
715 // add list boxes to main area, the list_sizer
718 wxStaticBoxSizer *list_sizer = new wxStaticBoxSizer(wxHORIZONTAL,
719 m_parent, m_device_id_str);
721 // add database listctrl
722 m_dbdb_list.reset (new wxListCtrl(m_parent, BrowseMode_DBDBList,
723 wxDefaultPosition, wxDefaultSize,
724 wxLC_REPORT | wxLC_SINGLE_SEL) ); //| wxLC_VRULES
725 // int max_db_width = GetMaxWidth(m_dbdb_list.get(),
726 // m_dbdb.Databases.begin(), m_dbdb.Databases.end(),
727 // &GetDBName);
728 list_sizer->Add( m_dbdb_list.get(), 4, wxEXPAND | wxALL, 4 );
730 // add the record listctrl
731 m_record_list.reset(new wxListCtrl(m_parent, BrowseMode_RecordList,
732 wxDefaultPosition, wxDefaultSize,
733 wxLC_REPORT | wxLC_SINGLE_SEL) ); //| wxLC_VRULES
734 list_sizer->Add( m_record_list.get(), 5, wxEXPAND | wxALL, 4 );
736 // add list sizer to top sizer
737 m_top_sizer->Add( list_sizer, 1, wxEXPAND | wxALL, 4 );
740 // add "show all" checkbox and load status static textbox, inside sizer
743 wxBoxSizer *status_sizer = new wxBoxSizer(wxHORIZONTAL);
745 m_show_all_checkbox.reset( new wxCheckBox(m_parent,
746 BrowseMode_ShowAllCheckbox,
747 _T("Show All Databases"),
748 wxDefaultPosition, wxDefaultSize,
749 wxCHK_2STATE) );
750 status_sizer->Add( m_show_all_checkbox.get(), 0, wxEXPAND, 0 );
751 m_show_all_checkbox->SetValue(m_show_all);
753 status_sizer->AddStretchSpacer();
755 m_load_status_text.reset( new wxStaticText(m_parent,
756 BrowseMode_LoadStatusText,
757 _T(""),
758 wxDefaultPosition, wxSize(200, -1),
759 wxST_NO_AUTORESIZE) );
760 status_sizer->Add( m_load_status_text.get(), 0,
761 wxEXPAND | wxALIGN_CENTRE_VERTICAL, 0 );
763 m_top_sizer->Add( status_sizer, 0, wxEXPAND | wxALL, 4 );
768 // bottom buttons
771 // add bottom buttons - these go in the bottom FOOTER area
772 // so their heights must be fixed to MAIN_HEADER_OFFSET
773 // minus a border of 5px top and bottom
774 wxSize footer(-1, MAIN_HEADER_OFFSET - 5 - 5);
775 wxBoxSizer *buttons = new wxBoxSizer(wxHORIZONTAL);
776 m_add_record_button.reset( new wxButton(m_parent,
777 BrowseMode_AddRecordButton, _T("Add..."),
778 wxDefaultPosition, footer) );
779 m_copy_record_button.reset( new wxButton(m_parent,
780 BrowseMode_CopyRecordButton, _T("Copy..."),
781 wxDefaultPosition, footer) );
782 m_edit_record_button.reset( new wxButton(m_parent,
783 BrowseMode_EditRecordButton, _T("Edit..."),
784 wxDefaultPosition, footer));
785 m_delete_record_button.reset( new wxButton(m_parent,
786 BrowseMode_DeleteRecordButton, _T("Delete..."),
787 wxDefaultPosition, footer) );
788 buttons->Add(m_add_record_button.get(), 0, wxRIGHT, 5);
789 buttons->Add(m_copy_record_button.get(), 0, wxRIGHT, 5);
790 buttons->Add(m_edit_record_button.get(), 0, wxRIGHT, 5);
791 buttons->Add(m_delete_record_button.get(), 0, wxRIGHT, 5);
792 m_top_sizer->Add(buttons, 0, wxALL | wxALIGN_RIGHT, 5);
795 // recalc size of children and add columns
797 wxSize client_size = m_parent->GetClientSize();
798 m_top_sizer->SetDimension(0, 0,
799 client_size.GetWidth(), client_size.GetHeight());
801 // m_dbdb_list
802 wxSize dbdb_size = m_dbdb_list->GetClientSize();
803 int scroll_width = wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
804 int size = dbdb_size.GetWidth() - scroll_width;
805 m_dbdb_list->InsertColumn(0, _T("Databases"), wxLIST_FORMAT_LEFT,
806 size * 0.80);
807 m_dbdb_list->InsertColumn(1, _T("Count"), wxLIST_FORMAT_LEFT,
808 size * 0.20 + scroll_width); // add back the scroll width
809 // so it doesn't look half-baked when
810 // there is no scroll bar
813 // m_record_list
814 wxSize record_size = m_record_list->GetClientSize();
815 m_record_list->InsertColumn(0, _T("Record Description"),
816 wxLIST_FORMAT_LEFT, record_size.GetWidth());
820 // add data
822 FillDBDBList();
825 // attempt to re-select the devices as we last saw them
826 ReselectDevices(m_device_set->String2Subset(wxGetApp().GetGlobalConfig().GetKey("SelectedDevices")));
827 UpdateButtons();
832 int BrowseMode::GUItoDBDBIndex(int gui_index)
834 if( m_show_all )
835 return gui_index;
837 DatabaseDatabase::DatabaseArrayType::const_iterator
838 i = m_dbdb.Databases.begin(), e = m_dbdb.Databases.end();
839 for( int index = 0; i != e; ++i, index++ ) {
840 // only bump index on the parsable databases
841 if( !m_show_all && !IsParsable(i->Name) )
842 continue;
844 if( !gui_index )
845 return index;
847 gui_index--;
850 // error
851 return -1;
854 void BrowseMode::FillDBDBList()
856 // start fresh
857 m_dbdb_list->DeleteAllItems();
859 DatabaseDatabase::DatabaseArrayType::const_iterator
860 i = m_dbdb.Databases.begin(), e = m_dbdb.Databases.end();
861 for( int index = 0; i != e; ++i, index++ ) {
862 // Only show parsable databases, depending on GUI
863 if( !m_show_all && !IsParsable(i->Name) )
864 continue;
866 // Database Name
867 wxString text(i->Name.c_str(), wxConvUTF8);
868 long item = m_dbdb_list->InsertItem(index, text);
870 // Record Count
871 ostringstream oss;
872 oss << dec << i->RecordCount;
873 text = wxString(oss.str().c_str(), wxConvUTF8);
874 m_dbdb_list->SetItem(item, 1, text);
877 UpdateButtons();
880 void BrowseMode::FillRecordList(const std::string &dbname)
882 try {
884 // start fresh
885 m_record_list->DeleteAllItems();
887 // grab our DB
888 DBMap::DBCachePtr db = m_dbmap->LoadDBCache(dbname);
890 // cycle through the cache, and insert the descriptions
891 // given for each record
892 DBCache::const_iterator b = db->begin(), e = db->end();
893 for( int index = 0; b != e; ++b, index++ ) {
894 wxString text((*b)->GetDescription().c_str(), wxConvUTF8);
895 //long item =
896 m_record_list->InsertItem(index, text);
899 } catch( Barry::Error &be ) {
900 cerr << be.what() << endl;
904 void BrowseMode::UpdateButtons()
906 int selected_count = m_record_list->GetSelectedItemCount();
908 // can only add if we have a builder and dialog for this record type
909 m_add_record_button->Enable(m_buildable && m_editable);
911 // can only copy or edit if we have a builder, a dialog, and
912 // only 1 is selected
913 m_copy_record_button->Enable(
914 m_buildable && m_editable && selected_count == 1);
915 m_edit_record_button->Enable(
916 m_buildable && m_editable && selected_count == 1);
918 // can only delete if something is selected
919 m_delete_record_button->Enable(selected_count > 0);
922 void BrowseMode::FillCache()
924 // create a copy of the dbdb, and sort in ascending order of
925 // record count, so the last db loaded is the longest...
926 // hopefully this makes the UI more user-friendly and responsive
927 DatabaseDatabase dbdb = m_dbdb;
928 dbdb.SortByRecordCount();
930 // cycle through the dbdb and load all Parsable databases
931 DatabaseDatabase::DatabaseArrayType::const_iterator
932 i = dbdb.Databases.begin(), e = dbdb.Databases.end();
933 for( ; i != e; ++i ) {
934 if( IsParsable(i->Name) ) try {
935 SendStatusEvent(i->Name);
936 m_dbmap->LoadDBCache(i->Name);
937 } catch( Barry::Error &be ) {
938 cerr << be.what() << endl;
941 if( m_abort_flag )
942 break;
945 SendStatusEvent("");
947 // finished
948 m_abort_flag = true;
951 void* BrowseMode::FillCacheThread(void *bobj)
953 BrowseMode *bm = (BrowseMode*) bobj;
954 bm->FillCache();
955 return NULL;
958 void BrowseMode::OnDBDBListSelChange(wxListEvent &event)
960 wxBusyCursor wait;
961 int index = GUItoDBDBIndex(event.GetIndex());
962 m_current_dbname = m_dbdb.Databases.at(index).Name;
963 m_buildable = ::IsBuildable(m_current_dbname);
964 m_editable = ::IsEditable(m_current_dbname);
965 m_current_record_item = -1;
967 FillRecordList(m_current_dbname);
968 UpdateButtons();
971 void BrowseMode::OnRecordListSelChange(wxListEvent &event)
973 // grab the cache for the current database... Get is ok here,
974 // since the cache is already loaded by the main db list
975 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
976 if( !p.get() )
977 return;
979 // grab the record list index
980 m_current_record_item = event.GetIndex();
981 // m_current_record_item = m_record_list->GetNextItem(
982 // m_current_record_item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
984 UpdateButtons();
987 void BrowseMode::OnRecordListActivated(wxListEvent &event)
989 wxCommandEvent ce;
990 OnEditRecord(ce);
993 void BrowseMode::OnShowAll(wxCommandEvent &event)
995 m_show_all = !m_show_all;
996 FillDBDBList();
999 void BrowseMode::OnAddRecord(wxCommandEvent &event)
1001 // grab the cache for the current database... Get is ok here,
1002 // since the cache is already loaded by the main db list
1003 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1004 if( !p.get() )
1005 return;
1007 DBCache::iterator i = p->Add(m_parent, *m_zones, p->end());
1008 if( i != p->end() ) {
1009 wxString text((*i)->GetDescription().c_str(), wxConvUTF8);
1011 // insert new record in same spot as DBCache has it
1012 m_current_record_item = p->GetIndex(i);
1013 m_record_list->InsertItem(m_current_record_item, text);
1017 void BrowseMode::OnCopyRecord(wxCommandEvent &event)
1019 // grab the cache for the current database... Get is ok here,
1020 // since the cache is already loaded by the main db list
1021 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1022 if( !p.get() )
1023 return;
1025 DBCache::iterator source = p->Get(m_current_record_item);
1026 DBCache::iterator i = p->Add(m_parent, *m_zones, source);
1027 if( i != p->end() ) {
1028 wxString text((*i)->GetDescription().c_str(), wxConvUTF8);
1030 // insert new record in same spot as DBCache has it
1031 m_current_record_item = p->GetIndex(i);
1032 m_record_list->InsertItem(m_current_record_item, text);
1036 void BrowseMode::OnEditRecord(wxCommandEvent &event)
1038 // grab the cache for the current database... Get is ok here,
1039 // since the cache is already loaded by the main db list
1040 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1041 if( !p.get() )
1042 return;
1044 DBCache::iterator i = p->Get(m_current_record_item);
1045 if( p->Edit(m_parent, *m_zones, i) ) {
1046 wxString text((*i)->GetDescription().c_str(), wxConvUTF8);
1047 m_record_list->SetItem(m_current_record_item, 0, text);
1051 void BrowseMode::OnDeleteRecord(wxCommandEvent &event)
1053 // grab the cache for the current database... Get is ok here,
1054 // since the cache is already loaded by the main db list
1055 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
1056 if( !p.get() )
1057 return;
1059 DBCache::iterator i = p->Get(m_current_record_item);
1060 if( p->Delete(m_parent, i) ) {
1061 m_record_list->DeleteItem(m_current_record_item);
1065 void BrowseMode::OnStatusEvent(wxCommandEvent &event)
1067 m_load_status_text->SetLabel(event.GetString());