desktop: made the thread locks more forgiving when pre-loading
[barry/progweb.git] / desktop / src / Mode_Browse.cc
blob8405ad8f3d7b143cea0c4cbd03d5077f4a9fc05c
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 "MemoEditDlg.h"
26 #include "windowids.h"
27 #include <iostream>
28 #include <sstream>
29 #include <iomanip>
31 using namespace std;
32 using namespace Barry;
34 BEGIN_EVENT_TABLE(BrowseMode, wxEvtHandler)
35 EVT_LIST_ITEM_SELECTED(BrowseMode_DBDBList,
36 BrowseMode::OnDBDBListSelChange)
37 EVT_LIST_ITEM_SELECTED(BrowseMode_RecordList,
38 BrowseMode::OnRecordListSelChange)
39 EVT_LIST_ITEM_ACTIVATED(BrowseMode_RecordList,
40 BrowseMode::OnRecordListActivated)
41 EVT_CHECKBOX (BrowseMode_ShowAllCheckbox,
42 BrowseMode::OnShowAll)
43 EVT_BUTTON (BrowseMode_AddRecordButton,
44 BrowseMode::OnAddRecord)
45 EVT_BUTTON (BrowseMode_CopyRecordButton,
46 BrowseMode::OnCopyRecord)
47 EVT_BUTTON (BrowseMode_EditRecordButton,
48 BrowseMode::OnEditRecord)
49 EVT_BUTTON (BrowseMode_DeleteRecordButton,
50 BrowseMode::OnDeleteRecord)
51 END_EVENT_TABLE()
54 //////////////////////////////////////////////////////////////////////////////
55 // Standalone functions
57 bool EditRecord(wxWindow *parent, bool editable, Barry::Contact &rec)
59 ContactEditDlg edit(parent, rec, editable);
60 return edit.ShowModal() == wxID_OK;
63 bool EditRecord(wxWindow *parent, bool editable, Barry::Bookmark &rec)
65 return false;
68 bool EditRecord(wxWindow *parent, bool editable, Barry::Calendar &rec)
70 return false;
73 bool EditRecord(wxWindow *parent, bool editable, Barry::CalendarAll &rec)
75 return false;
78 bool EditRecord(wxWindow *parent, bool editable, Barry::ContentStore &rec)
80 return false;
83 bool EditRecord(wxWindow *parent, bool editable, Barry::Folder &rec)
85 return false;
88 bool EditRecord(wxWindow *parent, bool editable, Barry::Memo &rec)
90 MemoEditDlg edit(parent, rec, editable);
91 return edit.ShowModal() == wxID_OK;
94 bool EditRecord(wxWindow *parent, bool editable, Barry::Message &rec)
96 return false;
99 bool EditRecord(wxWindow *parent, bool editable, Barry::CallLog &rec)
101 return false;
104 bool EditRecord(wxWindow *parent, bool editable, Barry::PINMessage &rec)
106 return false;
109 bool EditRecord(wxWindow *parent, bool editable, Barry::SavedMessage &rec)
111 return false;
114 bool EditRecord(wxWindow *parent, bool editable, Barry::ServiceBook &rec)
116 return false;
119 bool EditRecord(wxWindow *parent, bool editable, Barry::Sms &rec)
121 return false;
124 bool EditRecord(wxWindow *parent, bool editable, Barry::Task &rec)
126 return false;
129 bool EditRecord(wxWindow *parent, bool editable, Barry::Timezone &rec)
131 return false;
134 bool EditRecord(wxWindow *parent, bool editable, Barry::HandheldAgent &rec)
136 return false;
140 //////////////////////////////////////////////////////////////////////////////
141 // GUIDesktopConnector
143 bool GUIDesktopConnector::PasswordPrompt(const Barry::BadPassword &bp,
144 std::string &password_result)
146 // create prompt based on exception data
147 ostringstream oss;
148 oss << "Please enter device password: ("
149 << bp.remaining_tries()
150 << " tries remaining)";
151 wxString prompt(oss.str().c_str(), wxConvUTF8);
153 // ask user for device password
154 wxString pass = wxGetPasswordFromUser(prompt,
155 _T("Device Password"), _T(""), m_parent);
157 password_result = pass.utf8_str();
159 // assume that a blank password means the user wishes to quit...
160 // wxWidgets doesn't seem to handle this very well?
161 return password_result.size() > 0;
164 //////////////////////////////////////////////////////////////////////////////
165 // DBDataCache
167 DBDataCache::DBDataCache(DataCache::IndexType index, const Barry::DBData &raw)
168 : DataCache(index, raw.GetUniqueId())
169 , m_raw(raw)
173 bool DBDataCache::Edit(wxWindow *parent, bool editable)
175 return false;
178 std::string DBDataCache::GetDescription() const
180 return "raw data";
184 //////////////////////////////////////////////////////////////////////////////
185 // DBCache
187 DBCache::DBCache(ThreadableDesktop &tdesktop, const std::string &dbname)
188 : m_tdesktop(tdesktop)
189 , m_dbname(dbname)
191 // lock the desktop
192 DesktopInstancePtr dip = m_tdesktop.Get();
194 // grab the DBID
195 m_dbid = dip->Desktop().GetDBID(m_dbname);
197 // load the record state table
198 dip->Desktop().GetRecordStateTable(m_dbid, m_state);
200 // load all records
201 AllRecordParser ap(*this, *this);
202 RecordStateTable::StateMapType::iterator i = m_state.StateMap.begin();
203 for( ; i != m_state.StateMap.end(); ++i ) {
204 m_index = i->second.Index; // save for the callback
205 dip->Desktop().GetRecord(m_dbid, m_index, ap);
208 // sort the list of records by description
209 m_records.sort();
212 DBCache::~DBCache()
216 DBCache::iterator DBCache::Get(int list_offset)
218 iterator i = begin();
219 for( ; i != end() && list_offset; ++i, list_offset-- )
221 return i;
224 DBCache::const_iterator DBCache::Get(int list_offset) const
226 const_iterator i = begin();
227 for( ; i != end() && list_offset; ++i, list_offset-- )
229 return i;
232 int DBCache::GetIndex(iterator record) const
234 iterator i = const_cast<DBCache*> (this)->begin();
235 iterator e = const_cast<DBCache*> (this)->end();
236 for( int index = 0; i != e; ++i, index++ ) {
237 if( i == record )
238 return index;
240 return -1;
243 int DBCache::GetIndex(const_iterator record) const
245 const_iterator i = begin();
246 for( int index = 0; i != end(); ++i, index++ ) {
247 if( i == record )
248 return index;
250 return -1;
253 DBCache::iterator DBCache::Add(wxWindow *parent, iterator copy_record)
255 DataCachePtr p;
257 #undef HANDLE_BUILDER
258 #define HANDLE_BUILDER(tname) \
259 if( m_dbname == Barry::tname::GetDBName() ) { \
260 Barry::tname rec; \
261 if( copy_record != end() ) { \
262 RecordCache<Barry::tname> *rc = dynamic_cast<RecordCache<Barry::tname>* > (copy_record->get()); \
263 if( rc ) { \
264 rec = rc->GetRecord(); \
267 p.reset( new RecordCache<Barry::tname>(0, rec) ); \
269 ALL_KNOWN_BUILDER_TYPES
271 // anything else is not addable or buildable
272 if( !p.get() ) {
273 return end();
276 if( p->Edit(parent, true) ) {
277 // see if this record has a builder
278 Barry::Builder *bp = dynamic_cast<Barry::Builder*> (p.get());
279 if( !bp ) {
280 return end();
283 // give record a new UniqueID
284 uint32_t record_id = m_state.MakeNewRecordId();
285 cout << "New recordID generated: 0x" << hex << record_id << endl;
286 p->SetIds(p->GetStateIndex(), record_id);
288 // add record to device
289 DesktopInstancePtr dip = m_tdesktop.Get();
290 Barry::Mode::Desktop &desktop = dip->Desktop();
291 desktop.AddRecord(m_dbid, *bp);
293 // update our copy of the record state table from device
294 desktop.GetRecordStateTable(m_dbid, m_state);
295 cout << m_state << endl;
297 // find our new record_id in list, to find the state index
298 IndexType new_index;
299 if( !m_state.GetIndex(record_id, &new_index) ) {
300 throw std::logic_error("Need to reconnect for adding a record?");
303 // update new state_index in the data cache record
304 p->SetIds(new_index, record_id);
306 // add DataCachePtr to our own cache list
307 m_records.push_front(p);
309 // return iterator pointing to new record
310 return begin();
312 else {
313 return end();
317 bool DBCache::Edit(wxWindow *parent, iterator record)
319 if( record == end() )
320 return false;
322 if( (*record)->Edit(parent, true) && (*record)->IsBuildable() ) {
323 // see if this record has a builder
324 Barry::Builder *bp = dynamic_cast<Barry::Builder*> ((*record).get());
325 if( !bp )
326 return false;
328 cout << "Changing device record with index: 0x" << hex << (*record)->GetStateIndex() << endl;
329 cout << m_state << endl;
330 // update the device with new record data
331 DesktopInstancePtr dip = m_tdesktop.Get();
332 Barry::Mode::Desktop &desktop = dip->Desktop();
333 desktop.SetRecord(m_dbid, (*record)->GetStateIndex(), *bp);
335 return true;
337 else {
338 return false;
342 bool DBCache::Delete(wxWindow *parent, iterator record)
344 if( record == end() )
345 return false;
347 // prompt user with Yes / No message
348 wxString desc((*record)->GetDescription().c_str(), wxConvUTF8);
349 int choice = wxMessageBox(_T("Delete record: ") + desc + _T("?"),
350 _T("Record Delete"), wxYES_NO | wxICON_QUESTION, parent);
352 // if no, return false
353 if( choice != wxYES )
354 return false;
356 cout << "Deleting device record with index: 0x" << hex << (*record)->GetStateIndex() << endl;
357 cout << m_state << endl;
358 // delete record from device
359 DesktopInstancePtr dip = m_tdesktop.Get();
360 Barry::Mode::Desktop &desktop = dip->Desktop();
361 desktop.DeleteRecord(m_dbid, (*record)->GetStateIndex());
363 // remove record from cache list
364 m_records.erase(record);
365 return true;
368 // For Barry::AllRecordStore
369 #undef HANDLE_PARSER
370 #define HANDLE_PARSER(tname) \
371 void DBCache::operator() (const Barry::tname &rec) \
373 DataCachePtr p( new RecordCache<Barry::tname>(m_index, rec) ); \
374 m_records.push_front(p); \
376 ALL_KNOWN_PARSER_TYPES
378 // For Barry::Parser
379 void DBCache::ParseRecord(const Barry::DBData &data,
380 const Barry::IConverter *ic)
382 DataCachePtr p( new DBDataCache(m_index, data) );
383 m_records.push_front(p);
387 //////////////////////////////////////////////////////////////////////////////
388 // DBMap
390 DBMap::DBMap(ThreadableDesktop &tdesktop)
391 : m_tdesktop(tdesktop)
393 if( pthread_mutex_init(&m_map_mutex, NULL) ) {
394 throw Barry::Error("Failed to create map mutex");
397 if( pthread_mutex_init(&m_load_mutex, NULL) ) {
398 throw Barry::Error("Failed to create load mutex");
402 DBMap::DBCachePtr DBMap::LoadDBCache(const std::string &dbname)
404 // first, check for pre-loaded data, before the load lock,
405 // to make sure we return pre-loaded data with utmost haste
407 scoped_lock map_lock(m_map_mutex);
409 MapType::iterator i = m_map.find(dbname);
410 if( i != m_map.end() )
411 return i->second;
414 // if not found, lock and load, but be careful, since we
415 // don't want to open a window here for loading a db twice
416 scoped_lock load_lock(m_load_mutex);
418 // check again for pre-loaded data, since between
419 // map.unlock and load.lock there could have been
420 // another successful load
422 scoped_lock map_lock(m_map_mutex);
424 MapType::iterator i = m_map.find(dbname);
425 if( i != m_map.end() )
426 return i->second;
429 // do the load, without map.lock, since this can take a
430 // significant amount of time
431 DBCachePtr p( new DBCache(m_tdesktop, dbname) );
433 // lock once more to update the map, and then done
434 scoped_lock map_lock(m_map_mutex);
435 m_map[dbname] = p;
436 return p;
439 DBMap::DBCachePtr DBMap::GetDBCache(const std::string &dbname)
441 scoped_lock lock(m_map_mutex);
443 MapType::iterator i = m_map.find(dbname);
444 if( i != m_map.end() )
445 return i->second;
447 return DBCachePtr();
450 //////////////////////////////////////////////////////////////////////////////
451 // BrowseMode
453 BrowseMode::BrowseMode(wxWindow *parent, const ProbeResult &device)
454 : m_parent(parent)
455 , m_buildable(false)
456 , m_show_all(false)
458 // create device identifying string
459 m_device_id_str = wxString(device.GetDisplayName().c_str(), wxConvUTF8);
462 // connect to the device
464 m_con.reset( new GUIDesktopConnector(m_parent, "", "utf-8", device) );
465 m_con->Reconnect(2);
466 m_tdesktop.reset( new ThreadableDesktop(*m_con) );
468 // keep our own copy, and sort by name for later
469 m_dbdb = m_con->GetDesktop().GetDBDB();
470 m_dbdb.SortByName();
472 CreateControls();
474 // create our DBMap and give it the threadable desktop,
475 // now that we're finished doing any desktop USB work
476 m_dbmap.reset( new DBMap(*m_tdesktop) );
479 // From here down, we assume that our constructor succeeds, with
480 // no exceptions!
483 // fire off a background thread to cache database records
484 // in advance... if it fails, don't worry about it
485 m_abort_flag = false;
486 int ret = pthread_create(&m_cache_thread, NULL,
487 &BrowseMode::FillCacheThread, this);
488 if( ret != 0 )
489 m_abort_flag = true; // no need to join later
491 // connect ourselves to the parent's event handling chain
492 // do this last, so that we are guaranteed our destructor
493 // will run, in case of exceptions
494 m_parent->PushEventHandler(this);
497 BrowseMode::~BrowseMode()
499 // unhook that event handler!
500 m_parent->PopEventHandler();
502 // make sure the cache thread is finished before we destroy it :-)
503 if( !m_abort_flag ) {
504 m_abort_flag = true;
505 void *junk;
506 pthread_join(m_cache_thread, &junk);
510 std::string& GetDBName(Barry::DatabaseDatabase::Database &db)
512 return db.Name;
515 void BrowseMode::CreateControls()
517 m_top_sizer.reset( new wxBoxSizer(wxVERTICAL) );
519 // make space for the main header, which is not part of our
520 // work area
521 m_top_sizer->AddSpacer(MAIN_HEADER_OFFSET);
525 // add list boxes to main area, the list_sizer
528 wxStaticBoxSizer *list_sizer = new wxStaticBoxSizer(wxHORIZONTAL,
529 m_parent, m_device_id_str);
531 // add database listctrl
532 m_dbdb_list.reset (new wxListCtrl(m_parent, BrowseMode_DBDBList,
533 wxDefaultPosition, wxDefaultSize,
534 wxLC_REPORT | wxLC_SINGLE_SEL) ); //| wxLC_VRULES
535 // int max_db_width = GetMaxWidth(m_dbdb_list.get(),
536 // m_dbdb.Databases.begin(), m_dbdb.Databases.end(),
537 // &GetDBName);
538 list_sizer->Add( m_dbdb_list.get(), 4, wxEXPAND | wxALL, 4 );
540 // add the record listctrl
541 m_record_list.reset(new wxListCtrl(m_parent, BrowseMode_RecordList,
542 wxDefaultPosition, wxDefaultSize,
543 wxLC_REPORT | wxLC_SINGLE_SEL) ); //| wxLC_VRULES
544 list_sizer->Add( m_record_list.get(), 5, wxEXPAND | wxALL, 4 );
546 // add list sizer to top sizer
547 m_top_sizer->Add( list_sizer, 1, wxEXPAND | wxALL, 4 );
550 // add "show all" checkbox
553 m_show_all_checkbox.reset( new wxCheckBox(m_parent,
554 BrowseMode_ShowAllCheckbox,
555 _T("Show All Databases"),
556 wxDefaultPosition, wxDefaultSize,
557 wxCHK_2STATE) );
558 m_top_sizer->Add( m_show_all_checkbox.get(), 0, wxEXPAND | wxALL, 4 );
559 m_show_all_checkbox->SetValue(m_show_all);
562 // bottom buttons
565 // add bottom buttons - these go in the bottom FOOTER area
566 // so their heights must be fixed to MAIN_HEADER_OFFSET
567 // minus a border of 5px top and bottom
568 wxSize footer(-1, MAIN_HEADER_OFFSET - 5 - 5);
569 wxBoxSizer *buttons = new wxBoxSizer(wxHORIZONTAL);
570 m_add_record_button.reset( new wxButton(m_parent,
571 BrowseMode_AddRecordButton, _T("Add..."),
572 wxDefaultPosition, footer) );
573 m_copy_record_button.reset( new wxButton(m_parent,
574 BrowseMode_CopyRecordButton, _T("Copy..."),
575 wxDefaultPosition, footer) );
576 m_edit_record_button.reset( new wxButton(m_parent,
577 BrowseMode_EditRecordButton, _T("Edit..."),
578 wxDefaultPosition, footer));
579 m_delete_record_button.reset( new wxButton(m_parent,
580 BrowseMode_DeleteRecordButton, _T("Delete..."),
581 wxDefaultPosition, footer) );
582 buttons->Add(m_add_record_button.get(), 0, wxRIGHT, 5);
583 buttons->Add(m_copy_record_button.get(), 0, wxRIGHT, 5);
584 buttons->Add(m_edit_record_button.get(), 0, wxRIGHT, 5);
585 buttons->Add(m_delete_record_button.get(), 0, wxRIGHT, 5);
586 m_top_sizer->Add(buttons, 0, wxALL | wxALIGN_RIGHT, 5);
589 // recalc size of children and add columns
591 wxSize client_size = m_parent->GetClientSize();
592 m_top_sizer->SetDimension(0, 0,
593 client_size.GetWidth(), client_size.GetHeight());
595 // m_dbdb_list
596 wxSize dbdb_size = m_dbdb_list->GetClientSize();
597 int scroll_width = wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
598 int size = dbdb_size.GetWidth() - scroll_width;
599 m_dbdb_list->InsertColumn(0, _T("Databases"), wxLIST_FORMAT_LEFT,
600 size * 0.80);
601 m_dbdb_list->InsertColumn(1, _T("Count"), wxLIST_FORMAT_LEFT,
602 size * 0.20 + scroll_width); // add back the scroll width
603 // so it doesn't look half-baked when
604 // there is no scroll bar
607 // m_record_list
608 wxSize record_size = m_record_list->GetClientSize();
609 m_record_list->InsertColumn(0, _T("Record Description"),
610 wxLIST_FORMAT_LEFT, record_size.GetWidth());
614 // add data
616 FillDBDBList();
619 // attempt to re-select the devices as we last saw them
620 ReselectDevices(m_device_set->String2Subset(wxGetApp().GetGlobalConfig().GetKey("SelectedDevices")));
621 UpdateButtons();
626 int BrowseMode::GUItoDBDBIndex(int gui_index)
628 if( m_show_all )
629 return gui_index;
631 DatabaseDatabase::DatabaseArrayType::const_iterator
632 i = m_dbdb.Databases.begin(), e = m_dbdb.Databases.end();
633 for( int index = 0; i != e; ++i, index++ ) {
634 // only bump index on the parsable databases
635 if( !m_show_all && !IsParsable(i->Name) )
636 continue;
638 if( !gui_index )
639 return index;
641 gui_index--;
644 // error
645 return -1;
648 void BrowseMode::FillDBDBList()
650 // start fresh
651 m_dbdb_list->DeleteAllItems();
653 DatabaseDatabase::DatabaseArrayType::const_iterator
654 i = m_dbdb.Databases.begin(), e = m_dbdb.Databases.end();
655 for( int index = 0; i != e; ++i, index++ ) {
656 // Only show parsable databases, depending on GUI
657 if( !m_show_all && !IsParsable(i->Name) )
658 continue;
660 // Database Name
661 wxString text(i->Name.c_str(), wxConvUTF8);
662 long item = m_dbdb_list->InsertItem(index, text);
664 // Record Count
665 ostringstream oss;
666 oss << dec << i->RecordCount;
667 text = wxString(oss.str().c_str(), wxConvUTF8);
668 m_dbdb_list->SetItem(item, 1, text);
671 UpdateButtons();
674 void BrowseMode::FillRecordList(const std::string &dbname)
676 try {
678 // start fresh
679 m_record_list->DeleteAllItems();
681 // grab our DB
682 DBMap::DBCachePtr db = m_dbmap->LoadDBCache(dbname);
684 // cycle through the cache, and insert the descriptions
685 // given for each record
686 DBCache::const_iterator b = db->begin(), e = db->end();
687 for( int index = 0; b != e; ++b, index++ ) {
688 wxString text((*b)->GetDescription().c_str(), wxConvUTF8);
689 //long item =
690 m_record_list->InsertItem(index, text);
693 } catch( Barry::Error &be ) {
694 cerr << be.what() << endl;
698 void BrowseMode::UpdateButtons()
700 int selected_count = m_record_list->GetSelectedItemCount();
702 // can only add if we have a builder for this record type
703 m_add_record_button->Enable(m_buildable);
704 // can only copy or edit if we have a builder, and only 1 is selected
705 m_copy_record_button->Enable(m_buildable && selected_count == 1);
706 m_edit_record_button->Enable(m_buildable && selected_count == 1);
707 // can only delete if something is selected
708 m_delete_record_button->Enable(selected_count > 0);
711 void BrowseMode::FillCache()
713 // cycle through the dbdb and load all Parsable databases
714 DatabaseDatabase::DatabaseArrayType::const_iterator
715 i = m_dbdb.Databases.begin(), e = m_dbdb.Databases.end();
716 for( ; i != e; ++i ) {
717 if( IsParsable(i->Name) ) try {
718 m_dbmap->LoadDBCache(i->Name);
719 } catch( Barry::Error &be ) {
720 cerr << be.what() << endl;
723 if( m_abort_flag )
724 break;
727 // finished
728 m_abort_flag = true;
731 void* BrowseMode::FillCacheThread(void *bobj)
733 BrowseMode *bm = (BrowseMode*) bobj;
734 bm->FillCache();
735 return NULL;
738 void BrowseMode::OnDBDBListSelChange(wxListEvent &event)
740 wxBusyCursor wait;
741 int index = GUItoDBDBIndex(event.GetIndex());
742 m_current_dbname = m_dbdb.Databases.at(index).Name;
743 m_buildable = ::IsBuildable(m_current_dbname);
744 m_current_record_item = -1;
746 FillRecordList(m_current_dbname);
747 UpdateButtons();
750 void BrowseMode::OnRecordListSelChange(wxListEvent &event)
752 // grab the cache for the current database... Get is ok here,
753 // since the cache is already loaded by the main db list
754 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
755 if( !p.get() )
756 return;
758 // grab the record list index
759 m_current_record_item = event.GetIndex();
760 // m_current_record_item = m_record_list->GetNextItem(
761 // m_current_record_item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
763 UpdateButtons();
766 void BrowseMode::OnRecordListActivated(wxListEvent &event)
768 wxCommandEvent ce;
769 OnEditRecord(ce);
772 void BrowseMode::OnShowAll(wxCommandEvent &event)
774 m_show_all = !m_show_all;
775 FillDBDBList();
778 void BrowseMode::OnAddRecord(wxCommandEvent &event)
780 // grab the cache for the current database... Get is ok here,
781 // since the cache is already loaded by the main db list
782 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
783 if( !p.get() )
784 return;
786 DBCache::iterator i = p->Add(m_parent, p->end());
787 if( i != p->end() ) {
788 wxString text((*i)->GetDescription().c_str(), wxConvUTF8);
790 // insert new record in same spot as DBCache has it
791 m_current_record_item = p->GetIndex(i);
792 m_record_list->InsertItem(m_current_record_item, text);
796 void BrowseMode::OnCopyRecord(wxCommandEvent &event)
798 // grab the cache for the current database... Get is ok here,
799 // since the cache is already loaded by the main db list
800 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
801 if( !p.get() )
802 return;
804 DBCache::iterator source = p->Get(m_current_record_item);
805 DBCache::iterator i = p->Add(m_parent, source);
806 if( i != p->end() ) {
807 wxString text((*i)->GetDescription().c_str(), wxConvUTF8);
809 // insert new record in same spot as DBCache has it
810 m_current_record_item = p->GetIndex(i);
811 m_record_list->InsertItem(m_current_record_item, text);
815 void BrowseMode::OnEditRecord(wxCommandEvent &event)
817 // grab the cache for the current database... Get is ok here,
818 // since the cache is already loaded by the main db list
819 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
820 if( !p.get() )
821 return;
823 DBCache::iterator i = p->Get(m_current_record_item);
824 if( p->Edit(m_parent, i) ) {
825 wxString text((*i)->GetDescription().c_str(), wxConvUTF8);
826 m_record_list->SetItem(m_current_record_item, 0, text);
830 void BrowseMode::OnDeleteRecord(wxCommandEvent &event)
832 // grab the cache for the current database... Get is ok here,
833 // since the cache is already loaded by the main db list
834 DBMap::DBCachePtr p = m_dbmap->GetDBCache(m_current_dbname);
835 if( !p.get() )
836 return;
838 DBCache::iterator i = p->Get(m_current_record_item);
839 if( p->Delete(m_parent, i) ) {
840 m_record_list->DeleteItem(m_current_record_item);