/*
 * Copyright (c) 2003-2016 Rony Shapiro <ronys@pwsafe.org>.
 * All rights reserved. Use of the code is allowed under the
 * Artistic License 2.0 terms, as specified in the LICENSE file
 * distributed with this code, or available from
 * http://www.opensource.org/licenses/artistic-license-2.0.php
 */

/** \file passwordsafeframe.cpp
*
*/

// Generated by DialogBlocks, Wed 14 Jan 2009 10:24:11 PM IST

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#include "wx/filename.h"

////@begin includes
#include "safecombinationchange.h"
#include "about.h"
#include "PWSgrid.h"
#include "PWStree.h"
#include "PWStatusBar.h"
////@end includes
#include "PWSgridtable.h"
#include "safecombinationsetup.h"

#include "passwordsafeframe.h"
#include "safecombinationprompt.h"
#include "properties.h"
#include "core/PWSprefs.h"
#include "core/PWSdirs.h"
#include "PasswordSafeSearch.h"
#include "pwsclip.h"
#include "SystemTray.h"
#include "wxutils.h"
#include "guiinfo.h"
#include <wx/clipbrd.h>
#include "pwsafeapp.h"
#include "../../os/file.h"
#include "./ImportTextDlg.h"
#include "./ImportXmlDlg.h"
#include "./ExportTextWarningDlg.h"
#include "../../os/sleep.h"
#include "./ViewReport.h"
#include "../../core/XML/XMLDefs.h"  // Required if testing "USE_XML_LIBRARY"
#include <wx/fontdlg.h>
#include "./PWSDragBar.h"
#include "./MergeDlg.h"
#include <algorithm>
#include "./PwsSync.h"
#include "./SystemTrayMenuId.h"
#include "./CompareDlg.h"
#include "../../core/core.h"
#include "../../core/PWScore.h"
#include "./SelectionCriteria.h"

// main toolbar images
#include "./PwsToolbarButtons.h"
#include "./pwsmenushortcuts.h"

#ifdef __WXMSW__
#include <wx/msw/msvcrt.h>
#endif

////@begin XPM images
////@end XPM images
#include "./graphics/cpane.xpm"

using pws_os::CUUID;

using std::get;
using std::make_tuple;


/*!
 * PasswordSafeFrame type definition
 */

IMPLEMENT_CLASS( PasswordSafeFrame, wxFrame )

DEFINE_EVENT_TYPE(wxEVT_DB_PREFS_CHANGE)
DEFINE_EVENT_TYPE(wxEVT_GUI_DB_PREFS_CHANGE)

/*!
 * PasswordSafeFrame event table definition
 */

BEGIN_EVENT_TABLE( PasswordSafeFrame, wxFrame )

////@begin PasswordSafeFrame event table entries
  EVT_CLOSE( PasswordSafeFrame::OnCloseWindow )
  EVT_MENU( wxID_NEW, PasswordSafeFrame::OnNewClick )
  EVT_MENU( wxID_OPEN, PasswordSafeFrame::OnOpenClick )
  EVT_MENU( wxID_CLOSE, PasswordSafeFrame::OnCloseClick )
  EVT_MENU( wxID_SAVE, PasswordSafeFrame::OnSaveClick )
  EVT_MENU( wxID_SAVEAS, PasswordSafeFrame::OnSaveAsClick )
  EVT_MENU( wxID_PROPERTIES, PasswordSafeFrame::OnPropertiesClick )
  EVT_MENU( wxID_EXIT, PasswordSafeFrame::OnExitClick )
  EVT_MENU( wxID_ADD, PasswordSafeFrame::OnAddClick )
  EVT_MENU( ID_EDIT, PasswordSafeFrame::OnEditClick )
  EVT_MENU( wxID_DELETE, PasswordSafeFrame::OnDeleteClick )
  EVT_MENU( ID_CLEARCLIPBOARD, PasswordSafeFrame::OnClearclipboardClick )
  EVT_MENU( ID_COPYPASSWORD, PasswordSafeFrame::OnCopypasswordClick )
  EVT_MENU( ID_COPYUSERNAME, PasswordSafeFrame::OnCopyusernameClick )
  EVT_MENU( ID_COPYNOTESFLD, PasswordSafeFrame::OnCopynotesfldClick )
  EVT_MENU( ID_COPYURL, PasswordSafeFrame::OnCopyurlClick )
  EVT_MENU( ID_LIST_VIEW, PasswordSafeFrame::OnListViewClick )
  EVT_MENU( ID_TREE_VIEW, PasswordSafeFrame::OnTreeViewClick )
  EVT_MENU( ID_SHOWHIDE_UNSAVED, PasswordSafeFrame::OnShowUnsavedEntriesClick )
  EVT_MENU( ID_SHOW_ALL_EXPIRY, PasswordSafeFrame::OnShowAllExpiryClick )
  EVT_MENU( ID_CHANGECOMBO, PasswordSafeFrame::OnChangePasswdClick )
  EVT_MENU( wxID_PREFERENCES, PasswordSafeFrame::OnPreferencesClick )
  EVT_MENU( ID_PWDPOLSM, PasswordSafeFrame::OnPwdPolsMClick )

#ifndef NO_YUBI
  EVT_MENU( ID_YUBIKEY_MNG, PasswordSafeFrame::OnYubikeyMngClick )
#endif

  EVT_MENU_RANGE( ID_LANGUAGE_BEGIN, ID_LANGUAGE_END, PasswordSafeFrame::OnLanguageClick )

  EVT_MENU( wxID_ABOUT, PasswordSafeFrame::OnAboutClick )
////@end PasswordSafeFrame event table entries
  EVT_MENU( ID_COPYEMAIL, PasswordSafeFrame::OnCopyEmailClick )

  EVT_MENU( wxID_FIND, PasswordSafeFrame::OnFindClick )

  EVT_MENU( ID_EDITMENU_FIND_NEXT, PasswordSafeFrame::OnFindNext )

  EVT_MENU( ID_EDITMENU_FIND_PREVIOUS, PasswordSafeFrame::OnFindPrevious )

  EVT_MENU( ID_BROWSEURL, PasswordSafeFrame::OnBrowseURL )

  EVT_MENU( ID_BROWSEURLPLUS, PasswordSafeFrame::OnBrowseUrlAndAutotype )

  EVT_MENU( ID_SENDEMAIL, PasswordSafeFrame::OnSendEmail )

  EVT_MENU( ID_COPYRUNCOMMAND, PasswordSafeFrame::OnCopyRunCmd )

  EVT_MENU( ID_RUNCOMMAND, PasswordSafeFrame::OnRunCommand )

  EVT_MENU( ID_AUTOTYPE, PasswordSafeFrame::OnAutoType )

  EVT_MENU( ID_GOTOBASEENTRY, PasswordSafeFrame::OnGotoBase )

  EVT_MENU( ID_EDITBASEENTRY, PasswordSafeFrame::OnEditBase )

  EVT_MENU( ID_CREATESHORTCUT, PasswordSafeFrame::OnCreateShortcut )

  EVT_MENU( ID_DUPLICATEENTRY, PasswordSafeFrame::OnDuplicateEntry )

  EVT_MENU( ID_IMPORT_PLAINTEXT, PasswordSafeFrame::OnImportText )

  EVT_MENU( ID_IMPORT_KEEPASS, PasswordSafeFrame::OnImportKeePass )

  EVT_MENU( ID_IMPORT_XML, PasswordSafeFrame::OnImportXML )

  EVT_MENU( ID_EXPORT2OLD1XFORMAT, PasswordSafeFrame::OnExportVx )

  EVT_MENU( ID_EXPORT2V2FORMAT, PasswordSafeFrame::OnExportVx )

  EVT_MENU( ID_EXPORT2V4FORMAT, PasswordSafeFrame::OnExportVx )

  EVT_MENU( ID_EXPORT2PLAINTEXT, PasswordSafeFrame::OnExportPlainText )

  EVT_MENU( ID_EXPORT2XML, PasswordSafeFrame::OnExportXml )

  EVT_MENU(wxID_UNDO,          PasswordSafeFrame::OnUndo )
  EVT_MENU(wxID_REDO,          PasswordSafeFrame::OnRedo )

  EVT_MENU(ID_EXPANDALL,       PasswordSafeFrame::OnExpandAll )
  EVT_MENU(ID_COLLAPSEALL,     PasswordSafeFrame::OnCollapseAll )

  EVT_MENU(ID_CHANGETREEFONT,  PasswordSafeFrame::OnChangeTreeFont )
  EVT_MENU(ID_CHANGEPSWDFONT,  PasswordSafeFrame::OnChangePasswordFont )

  EVT_MENU(ID_SHOWHIDE_TOOLBAR,  PasswordSafeFrame::OnShowHideToolBar )
  EVT_MENU(ID_SHOWHIDE_DRAGBAR,  PasswordSafeFrame::OnShowHideDragBar )
  EVT_MENU( ID_TOOLBAR_NEW,     PasswordSafeFrame::OnChangeToolbarType )
  EVT_MENU( ID_TOOLBAR_CLASSIC, PasswordSafeFrame::OnChangeToolbarType )

  EVT_MENU(ID_MERGE,            PasswordSafeFrame::OnMergeAnotherSafe )
  EVT_MENU(ID_SYNCHRONIZE,      PasswordSafeFrame::OnSynchronize )
  EVT_MENU(ID_COMPARE,          PasswordSafeFrame::OnCompare )

  EVT_MENU( ID_MENU_CLEAR_MRU, PasswordSafeFrame::OnClearRecentHistory )
  EVT_UPDATE_UI( ID_MENU_CLEAR_MRU, PasswordSafeFrame::OnUpdateClearRecentDBHistory )

  EVT_MENU(ID_BACKUP,      PasswordSafeFrame::OnBackupSafe)
  EVT_MENU(ID_RESTORE,     PasswordSafeFrame::OnRestoreSafe)

  EVT_MENU(ID_VISITWEBSITE, PasswordSafeFrame::OnVisitWebsite)

  EVT_ICONIZE(PasswordSafeFrame::OnIconize)

  EVT_UPDATE_UI(wxID_SAVE,          PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_ADDGROUP,        PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_RENAME,          PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_COLLAPSEALL,     PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_EXPANDALL,       PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_GOTOBASEENTRY,   PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_EDITBASEENTRY,   PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_BROWSEURL,       PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_BROWSEURLPLUS,   PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_COPYURL,         PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_SENDEMAIL,       PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_COPYEMAIL,       PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_COPYUSERNAME,    PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_COPYNOTESFLD,    PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_RUNCOMMAND,      PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_COPYRUNCOMMAND,  PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_CREATESHORTCUT,  PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_DUPLICATEENTRY,  PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_COPYPASSWORD,    PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_AUTOTYPE,        PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_EDIT,            PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_PASSWORDSUBSET,  PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(wxID_UNDO,          PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(wxID_REDO,          PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(ID_SYNCHRONIZE,     PasswordSafeFrame::OnUpdateUI )
  EVT_UPDATE_UI(wxID_DELETE,        PasswordSafeFrame::OnUpdateUI )
END_EVENT_TABLE()

static void DisplayFileWriteError(int rc, const StringX &fname);

/*!
 * PasswordSafeFrame constructors
 */

PasswordSafeFrame::PasswordSafeFrame(PWScore &core)
: m_core(core), m_currentView(GRID), m_search(0), m_sysTray(new SystemTray(this)), m_exitFromMenu(false),
  m_RUEList(core), m_guiInfo(new GUIInfo), m_bTSUpdated(false), m_savedDBPrefs(wxEmptyString),
  m_bShowExpiry(false), m_bFilterActive(false)
{
    Init();
}

PasswordSafeFrame::PasswordSafeFrame(wxWindow* parent, PWScore &core,
                                     wxWindowID id, const wxString& caption,
                                     const wxPoint& pos, const wxSize& size,
                                     long style)
  : m_core(core), m_currentView(GRID), m_search(0), m_sysTray(new SystemTray(this)), m_exitFromMenu(false),
    m_RUEList(core), m_guiInfo(new GUIInfo), m_bTSUpdated(false), m_savedDBPrefs(wxEmptyString),
    m_bShowExpiry(false), m_bFilterActive(false)
{
    Init();
    m_currentView = (PWSprefs::GetInstance()->GetPref(PWSprefs::LastView) == _T("list")) ? GRID : TREE;
    if (PWSprefs::GetInstance()->GetPref(PWSprefs::AlwaysOnTop))
      style |= wxSTAY_ON_TOP;
    Create( parent, id, caption, pos, size, style );
}


/*!
 * PasswordSafeFrame creator
 */

bool PasswordSafeFrame::Create( wxWindow* parent, wxWindowID id, const wxString& caption, const wxPoint& pos, const wxSize& size, long style )
{
  ////@begin PasswordSafeFrame creation
  wxFrame::Create( parent, id, caption, pos, size, style );

  CreateMenubar();
  CreateControls();
  SetIcon(GetIconResource(wxT("../graphics/wxWidgets/cpane.xpm")));
  Centre();
////@end PasswordSafeFrame creation
  m_search = new PasswordSafeSearch(this);
  CreateMainToolbar();
  CreateDragBar();
  return true;
}

void PasswordSafeFrame::CreateDragBar()
{
  wxSizer* origSizer = GetSizer();

  wxASSERT(origSizer);
  wxASSERT(origSizer->IsKindOf(wxBoxSizer(wxVERTICAL).GetClassInfo()));
  wxASSERT(((wxBoxSizer*)origSizer)->GetOrientation() == wxVERTICAL);

  PWSDragBar* dragbar = new PWSDragBar(this);
  origSizer->Insert(0, dragbar);

  const bool bShow = PWSprefs::GetInstance()->GetPref(PWSprefs::ShowDragbar);
  if (!bShow) {
    dragbar->Hide();
  }
  GetMenuBar()->Check(ID_SHOWHIDE_DRAGBAR, bShow);
}

/*!
 * PasswordSafeFrame destructor
 */

PasswordSafeFrame::~PasswordSafeFrame()
{
////@begin PasswordSafeFrame destruction
////@end PasswordSafeFrame destruction
  delete m_search;
  m_search = 0;

  delete m_sysTray;
  m_sysTray = 0;

  delete m_guiInfo;
  m_guiInfo = 0;

  m_core.ClearData();
}

/*!
 * Member initialisation
 */

void PasswordSafeFrame::Init()
{
  std::bitset<UIInterFace::NUM_SUPPORTED> bsSupportedFunctions;
  bsSupportedFunctions.set(UIInterFace::DATABASEMODIFIED);
  bsSupportedFunctions.set(UIInterFace::UPDATEGUI);
  bsSupportedFunctions.set(UIInterFace::GUISETUPDISPLAYINFO);
  bsSupportedFunctions.set(UIInterFace::GUIREFRESHENTRY);
  //bsSupportedFunctions.set(UIInterFace::UPDATEWIZARD);

  m_core.SetUIInterFace(this, UIInterFace::NUM_SUPPORTED, bsSupportedFunctions);

  m_RUEList.SetMax(PWSprefs::GetInstance()->PWSprefs::MaxREItems);
////@begin PasswordSafeFrame member initialisation
  m_grid = NULL;
  m_tree = NULL;
  m_statusBar = NULL;
////@end PasswordSafeFrame member initialisation
  RegisterLanguageMenuItems();
}

/**
 Register menu items for available languages
*/
void PasswordSafeFrame::RegisterLanguageMenuItems() {
  // Using unlocalized language names here, it will be translated in AddLanguageMenu
  AddLanguage( ID_LANGUAGE_CHINESE, wxLANGUAGE_CHINESE, L"Chinese"  );  /* code: 'zh' */
  AddLanguage( ID_LANGUAGE_DANISH,  wxLANGUAGE_DANISH,  L"Danish"   );  /* code: 'da' */
  AddLanguage( ID_LANGUAGE_DUTCH,   wxLANGUAGE_DUTCH,   L"Dutch"    );  /* code: 'nl' */
  AddLanguage( ID_LANGUAGE_ENGLISH, wxLANGUAGE_ENGLISH, L"English"  );  /* code: 'en' */
  AddLanguage( ID_LANGUAGE_FRENCH,  wxLANGUAGE_FRENCH,  L"French"   );  /* code: 'fr' */
  AddLanguage( ID_LANGUAGE_GERMAN,  wxLANGUAGE_GERMAN,  L"German"   );  /* code: 'de' */
  AddLanguage( ID_LANGUAGE_ITALIAN, wxLANGUAGE_ITALIAN, L"Italian"  );  /* code: 'it' */
  AddLanguage( ID_LANGUAGE_KOREAN,  wxLANGUAGE_KOREAN,  L"Korean"   );  /* code: 'ko' */
  AddLanguage( ID_LANGUAGE_POLISH,  wxLANGUAGE_POLISH,  L"Polish"   );  /* code: 'pl' */
  AddLanguage( ID_LANGUAGE_RUSSIAN, wxLANGUAGE_RUSSIAN, L"Russian"  );  /* code: 'ru' */
  AddLanguage( ID_LANGUAGE_SPANISH, wxLANGUAGE_SPANISH, L"Spanish"  );  /* code: 'es' */
  AddLanguage( ID_LANGUAGE_SWEDISH, wxLANGUAGE_SWEDISH, L"Swedish"  );  /* code: 'sv' */

  m_selectedLanguage = ID_LANGUAGE_ENGLISH;
  wxLanguage current_language = wxGetApp().GetSelectedLanguage();
  for (auto &item : m_languages) {
    if (get<0>(item.second) == current_language) {
      m_selectedLanguage = item.first;
      pws_os::Trace(L"Found user-preferred language: menu id= %d, lang id= %d\n", m_selectedLanguage, current_language);
    }
    // Mark whether language can be activated
    get<2>(item.second) = wxGetApp().ActivateLanguage(get<0>(item.second), true);
  }
  // Don't activate language here!
  // 1st - selected language already activated
  // 2nd - when we called from constructor, its caption parameter points
  //       to string located inside previously selected global translation object
  //       (leads to crash in Release, but works in Debug)
  pws_os::Trace(L"Selected language: menu id= %d\n", m_selectedLanguage);
}

/**
 * Menu bar creation for PasswordSafeFrame
 */

void PasswordSafeFrame::CreateMenubar()
{
  wxMenuBar* menuBar = GetMenuBar();

  // Create a new menu bar if none has been created so far
  if (menuBar == nullptr)
    menuBar = new wxMenuBar;

  menuBar->Freeze();

  // Removing all existing menu items is necessary for language switching
  while( menuBar->GetMenuCount() )
    delete menuBar->Remove( 0 );

  // Create all menu items
  // Recreating the menu items updates also their translation
////@begin PasswordSafeFrame content construction
  PasswordSafeFrame* itemFrame1 = this;

  wxMenu* itemMenu3 = new wxMenu;
  itemMenu3->Append(wxID_NEW, _("&New..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu3->Append(wxID_OPEN, _("&Open..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu3->Append(wxID_CLOSE, _("&Close"), wxEmptyString, wxITEM_NORMAL);
  itemMenu3->AppendSeparator();
  itemMenu3->Append(ID_MENU_CLEAR_MRU, _("Clear Recent Safe List"), wxEmptyString, wxITEM_NORMAL);
  itemMenu3->AppendSeparator();
  itemMenu3->Append(wxID_SAVE, _("&Save..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu3->Append(wxID_SAVEAS, _("Save &As..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu3->AppendSeparator();
  wxMenu* itemMenu13 = new wxMenu;
  itemMenu13->Append(ID_EXPORT2OLD1XFORMAT, _("v&1.x format..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu13->Append(ID_EXPORT2V2FORMAT, _("v&2 format..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu13->Append(ID_EXPORT2V4FORMAT, _("v&4 format (EXPERIMENTAL)..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu13->Append(ID_EXPORT2PLAINTEXT, _("&Plain Text (tab separated)..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu13->Append(ID_EXPORT2XML, _("&XML format..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu3->Append(ID_EXPORTMENU, _("Export &To"), itemMenu13);
  wxMenu* itemMenu19 = new wxMenu;
  itemMenu19->Append(ID_IMPORT_PLAINTEXT, _("&Plain Text..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu19->Append(ID_IMPORT_XML, _("&XML format..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu19->Append(ID_IMPORT_KEEPASS, _("&KeePass..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu3->Append(ID_IMPORTMENU, _("Import &From"), itemMenu19);
  itemMenu3->Append(ID_MERGE, _("Merge..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu3->Append(ID_COMPARE, _("Compare..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu3->Append(ID_SYNCHRONIZE, _("S&ynchronize..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu3->AppendSeparator();
  itemMenu3->Append(wxID_PROPERTIES, _("&Properties"), wxEmptyString, wxITEM_NORMAL);
  itemMenu3->AppendSeparator();
  itemMenu3->Append(wxID_EXIT, _("E&xit"), wxEmptyString, wxITEM_NORMAL);
  menuBar->Append(itemMenu3, _("&File"));
  wxMenu* itemMenu29 = new wxMenu;
  itemMenu29->Append(wxID_ADD, _("&Add Entry...\tCtrl+A"), wxEmptyString, wxITEM_NORMAL);
  itemMenu29->Append(ID_EDIT, _("Edit/&View Entry...\tCtrl+Enter"), wxEmptyString, wxITEM_NORMAL);
  itemMenu29->Append(wxID_DELETE, _("&Delete Entry\tDel"), wxEmptyString, wxITEM_NORMAL);
  itemMenu29->Append(ID_RENAME, _("Rename Entry\tF2"), wxEmptyString, wxITEM_NORMAL);
  itemMenu29->Append(wxID_FIND, _("&Find Entry...\tCtrl+F"), wxEmptyString, wxITEM_NORMAL);
  itemMenu29->Append(ID_DUPLICATEENTRY, _("&Duplicate Entry\tCtrl+D"), wxEmptyString, wxITEM_NORMAL);
  itemMenu29->AppendSeparator();
  itemMenu29->Append(ID_ADDGROUP, _("Add Group"), wxEmptyString, wxITEM_NORMAL);
  itemMenu29->AppendSeparator();
  itemMenu29->Append(wxID_UNDO);
  itemMenu29->Append(wxID_REDO);
  itemMenu29->Append(ID_CLEARCLIPBOARD, _("C&lear Clipboard\tCtrl+Del"), wxEmptyString, wxITEM_NORMAL);
  itemMenu29->AppendSeparator();
  itemMenu29->Append(ID_COPYPASSWORD, _("&Copy Password to Clipboard\tCtrl+C"), wxEmptyString, wxITEM_NORMAL);
  itemMenu29->Append(ID_COPYUSERNAME, _("Copy &Username to Clipboard\tCtrl+U"), wxEmptyString, wxITEM_NORMAL);
  itemMenu29->Append(ID_COPYNOTESFLD, _("Copy &Notes to Clipboard\tCtrl+G"), wxEmptyString, wxITEM_NORMAL);
  itemMenu29->Append(ID_COPYURL, _("Copy URL to Clipboard\tCtrl+Alt+L"), wxEmptyString, wxITEM_NORMAL);
  itemMenu29->Append(ID_BROWSEURL, _("&Browse to URL\tCtrl+L"), wxEmptyString, wxITEM_NORMAL);
  itemMenu29->Append(ID_AUTOTYPE, _("Perform Auto&type\tCtrl+T"), wxEmptyString, wxITEM_NORMAL);
  itemMenu29->Append(ID_GOTOBASEENTRY, _("Go to Base entry"), wxEmptyString, wxITEM_NORMAL);
  menuBar->Append(itemMenu29, _("Edit"));
  wxMenu* itemMenu48 = new wxMenu;
  itemMenu48->Append(ID_LIST_VIEW, _("Flattened &List"), wxEmptyString, wxITEM_RADIO);
  itemMenu48->Append(ID_TREE_VIEW, _("Nested &Tree"), wxEmptyString, wxITEM_RADIO);
  itemMenu48->AppendSeparator();
  itemMenu48->Append(ID_SHOWHIDE_TOOLBAR, _("Toolbar &visible"), wxEmptyString, wxITEM_CHECK);
  itemMenu48->AppendRadioItem(ID_TOOLBAR_NEW, _("&New Toolbar"));
  itemMenu48->AppendRadioItem(ID_TOOLBAR_CLASSIC, _("&Classic Toolbar"));
  itemMenu48->Append(ID_SHOWHIDE_DRAGBAR, _("&Dragbar visible"), wxEmptyString, wxITEM_CHECK);
  itemMenu48->AppendSeparator();
  itemMenu48->Append(ID_EXPANDALL, _("Expand All"), wxEmptyString, wxITEM_NORMAL);
  itemMenu48->Append(ID_COLLAPSEALL, _("Collapse All"), wxEmptyString, wxITEM_NORMAL);
  itemMenu48->Append(ID_SHOWHIDE_UNSAVED, _("Show &Unsaved Changes"), wxEmptyString, wxITEM_CHECK);
  itemMenu48->Append(ID_SHOW_ALL_EXPIRY, _("Show entries with E&xpiry dates"), wxEmptyString, wxITEM_CHECK);
  wxMenu* itemMenu58 = new wxMenu;
  itemMenu58->Append(ID_EDITFILTER, _("&New/Edit Filter..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu58->Append(ID_APPLYFILTER, _("&Apply current"), wxEmptyString, wxITEM_NORMAL);
  itemMenu58->Append(ID_MANAGEFILTERS, _("&Manage..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu48->Append(ID_FILTERMENU, _("&Filters"), itemMenu58);
  itemMenu48->AppendSeparator();
  itemMenu48->Append(ID_CUSTOMIZETOOLBAR, _("Customize &Main Toolbar..."), wxEmptyString, wxITEM_NORMAL);
  wxMenu* itemMenu64 = new wxMenu;
  itemMenu64->Append(ID_CHANGETREEFONT, _("&Tree/List Font"), wxEmptyString, wxITEM_NORMAL);
  itemMenu64->Append(ID_CHANGEPSWDFONT, _("&Password Font"), wxEmptyString, wxITEM_NORMAL);
  itemMenu48->Append(ID_CHANGEFONTMENU, _("Change &Font"), itemMenu64);
  wxMenu* itemMenu67 = new wxMenu;
  itemMenu67->Append(ID_REPORT_COMPARE, _("&Compare"), wxEmptyString, wxITEM_NORMAL);
  itemMenu67->Append(ID_REPORT_FIND, _("&Find"), wxEmptyString, wxITEM_NORMAL);
  itemMenu67->Append(ID_REPORT_IMPORTTEXT, _("Import &Text"), wxEmptyString, wxITEM_NORMAL);
  itemMenu67->Append(ID_REPORT_IMPORTXML, _("Import &XML"), wxEmptyString, wxITEM_NORMAL);
  itemMenu67->Append(ID_REPORT_MERGE, _("&Merge"), wxEmptyString, wxITEM_NORMAL);
  itemMenu67->Append(ID_REPORT_VALIDATE, _("&Validate"), wxEmptyString, wxITEM_NORMAL);
  itemMenu48->Append(ID_REPORTSMENU, _("Reports"), itemMenu67);
  menuBar->Append(itemMenu48, _("View"));
  wxMenu* itemMenu74 = new wxMenu;
  itemMenu74->Append(ID_CHANGECOMBO, _("&Change Safe Combination..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu74->AppendSeparator();
  itemMenu74->Append(ID_BACKUP, _("Make &Backup\tCtrl+B"), wxEmptyString, wxITEM_NORMAL);
  itemMenu74->Append(ID_RESTORE, _("&Restore from Backup...\tCtrl+R"), wxEmptyString, wxITEM_NORMAL);
  itemMenu74->AppendSeparator();
  itemMenu74->Append(wxID_PREFERENCES, _("&Options...\tCtrl+M"), wxEmptyString, wxITEM_NORMAL);
  itemMenu74->Append(ID_PWDPOLSM, _("Password Policies..."), wxEmptyString, wxITEM_NORMAL);
#ifndef NO_YUBI
  itemMenu74->AppendSeparator();
  itemMenu74->Append(ID_YUBIKEY_MNG, _("YubiKey..."), _("Configure and backup YubiKeys"), wxITEM_NORMAL);
#endif
  itemMenu74->AppendSeparator();
  AddLanguageMenu( itemMenu74 );
  menuBar->Append(itemMenu74, _("Manage"));
  wxMenu* itemMenu79 = new wxMenu;
  itemMenu79->Append(wxID_HELP, _("Get &Help"), wxEmptyString, wxITEM_NORMAL);
  itemMenu79->Append(ID_VISITWEBSITE, _("Visit Password Safe &website..."), wxEmptyString, wxITEM_NORMAL);
  itemMenu79->Append(wxID_ABOUT, _("&About Password Safe..."), wxEmptyString, wxITEM_NORMAL);
  menuBar->Append(itemMenu79, _("Help"));
  itemFrame1->SetMenuBar(menuBar);

  m_statusBar = new CPWStatusBar( itemFrame1, ID_STATUSBAR, wxST_SIZEGRIP|wxNO_BORDER );
  m_statusBar->SetFieldsCount(6);
  itemFrame1->SetStatusBar(m_statusBar);

////@end PasswordSafeFrame content construction

  menuBar->Thaw();

  // If there was no previous menu bar then we set the new created one
  // otherwise the already existing one is triggered to refresh
  if (GetMenuBar() == nullptr)
    SetMenuBar(menuBar);
  else
    menuBar->Refresh();

  // Update menu selections
  GetMenuBar()->Check( (m_currentView == TREE) ? ID_TREE_VIEW : ID_LIST_VIEW, true);
  GetMenuBar()->Check( PWSprefs::GetInstance()->GetPref(PWSprefs::UseNewToolbar) ?
                       ID_TOOLBAR_NEW: ID_TOOLBAR_CLASSIC, true );
  m_statusBar->Setup();

}

/**
 * Control creation for PasswordSafeFrame
 */
void PasswordSafeFrame::CreateControls()
{
  PWSMenuShortcuts* scmgr = PWSMenuShortcuts::CreateShortcutsManager( GetMenuBar() );
  scmgr->ReadApplyUserShortcuts();

  wxBoxSizer* mainsizer = new wxBoxSizer(wxVERTICAL); //to add the search bar later to the bottom
  wxBoxSizer* itemBoxSizer83 = new wxBoxSizer(wxHORIZONTAL);
  mainsizer->Add(itemBoxSizer83, 1, wxEXPAND);
  SetSizer(mainsizer);

  m_grid = new PWSGrid( this, m_core, ID_LISTBOX, wxDefaultPosition,
                        wxDefaultSize, wxHSCROLL|wxVSCROLL );
  itemBoxSizer83->Add(m_grid, wxSizerFlags().Expand().Border(0).Proportion(1));

  m_tree = new PWSTreeCtrl( this, m_core, ID_TREECTRL, wxDefaultPosition,
                            wxDefaultSize,
                            wxTR_EDIT_LABELS|wxTR_HAS_BUTTONS |wxTR_HIDE_ROOT|wxTR_SINGLE );
  // let the tree ctrl handle ID_ADDGROUP & ID_RENAME all by itself
  Connect(ID_ADDGROUP, wxEVT_COMMAND_MENU_SELECTED,
                       wxCommandEventHandler(PWSTreeCtrl::OnAddGroup), NULL, m_tree);
  Connect(ID_RENAME, wxEVT_COMMAND_MENU_SELECTED,
                       wxCommandEventHandler(PWSTreeCtrl::OnRenameGroup), NULL, m_tree);

  itemBoxSizer83->Add(m_tree, wxSizerFlags().Expand().Border(0).Proportion(1));
  itemBoxSizer83->Layout();

  const CRecentDBList& rdb = wxGetApp().recentDatabases();
  Connect(rdb.GetBaseId(), rdb.GetBaseId() + rdb.GetMaxFiles() - 1, wxEVT_COMMAND_MENU_SELECTED,
            wxCommandEventHandler(PasswordSafeFrame::OnOpenRecentDB));
}

/**
 * Creates the language sub menu.
 *
 * \param parent the parent to which the sub-menu should be added
 */
void PasswordSafeFrame::AddLanguageMenu(wxMenu* parent)
{
  if (parent == nullptr)
    return;
  wxMenu* child = new wxMenu;
  wxMenuItem* menu_item = nullptr;
  wxLanguage system_language = wxGetApp().GetSystemLanguage();

  for (auto &item : m_languages) {
    wxString lang_name = _(get<1>(item.second));
    if (get<0>(item.second) == system_language) {
      lang_name = L"[ " + lang_name + L" ]";
    }
    if (m_selectedLanguage != ID_LANGUAGE_ENGLISH && item.first == ID_LANGUAGE_ENGLISH) {
      // duplicate English label to simplify switching to it  in case of wrong selection
      lang_name += L" (English)";
    }

    menu_item = child->Append(
        item.first,               /* The key of the map that holds menu item id's */
        lang_name, /* The value of the map is a tuple.
                                     The tuple consists of three elements.
                                     Index 0: the language id as wxLanguage
                                     Index 1: the language literal as wxString
                                     Index 2: the indicator whether the language can be activated
                                     */
        L"",                   /* The menu items tooltip */
        wxITEM_CHECK
        );

    if (menu_item != nullptr)
      menu_item->Enable(get<2>(item.second));
  }

  parent->Append(ID_LANGUAGEMENU, _("Select Language") + L" (Select Language)", child);
  if ((m_selectedLanguage > ID_LANGUAGE_BEGIN) && (m_selectedLanguage < ID_LANGUAGE_END))
    child->Check( m_selectedLanguage, true );
}

/**
 * Adds language specific data to internal map
 * for language switching functionality.
 *
 * \param menu_id the id of the language menu item
 * \param lang_id the wx framework specific language id
 * \param lang_name the textual language representation on the menu
 */
void PasswordSafeFrame::AddLanguage(int menu_id, wxLanguage lang_id, const wxString& lang_name)
{
    m_languages[menu_id] = make_tuple(lang_id, lang_name, false);
}

/**
 * Creates the main toolbar
 */
void PasswordSafeFrame::CreateMainToolbar()
{
  wxToolBar* toolbar = CreateToolBar(wxBORDER_NONE | wxTB_TOP | wxTB_HORIZONTAL, wxID_ANY, wxT("Main Toolbar"));

  RefreshToolbarButtons();

  wxCHECK_RET(toolbar->Realize(), wxT("Could not create main toolbar"));

  const bool bShow = PWSprefs::GetInstance()->GetPref(PWSprefs::ShowToolbar);
  if (!bShow) {
    toolbar->Hide();
  }
  GetMenuBar()->Check(ID_SHOWHIDE_TOOLBAR, bShow);
}

/**
 * Recreates the main toolbar.
 *
 * This assumes that the main toolbar has already been created.
 * If this is the case all existing elements are removed and
 * added again to the toolbar instance.
 */
void PasswordSafeFrame::ReCreateMainToolbar()
{
    wxToolBar* toolbar = GetToolBar();
    wxCHECK_RET(toolbar, wxT("Couldn't find toolbar"));
    toolbar->ClearTools();
    RefreshToolbarButtons();
}

/**
 * Recreates the dragbar.
 *
 * This assumes that the dragbar has already been created.
 * If this is the case all existing elements are removed and
 * re-added.
 */
void PasswordSafeFrame::ReCreateDragToolbar()
{
    PWSDragBar* dragbar = GetDragBar();
    wxCHECK_RET(dragbar, wxT("Couldn't find dragbar"));
    dragbar->ClearTools();
    dragbar->RefreshButtons();
}

void PasswordSafeFrame::RefreshToolbarButtons()
{
  wxToolBar* tb = GetToolBar();
  wxASSERT(tb);
  if (tb->GetToolsCount() == 0) {  //being created?
    if (PWSprefs::GetInstance()->GetPref(PWSprefs::UseNewToolbar)) {
      for (size_t idx = 0; idx < NumberOf(PwsToolbarButtons); ++idx) {
        if (PwsToolbarButtons[idx].id == ID_SEPARATOR)
          tb->AddSeparator();
        else
          tb->AddTool(PwsToolbarButtons[idx].id, wxEmptyString, wxBitmap(PwsToolbarButtons[idx].bitmap_normal),
                              wxBitmap(PwsToolbarButtons[idx].bitmap_disabled), wxITEM_NORMAL,
                              wxGetTranslation(PwsToolbarButtons[idx].tooltip) );
      }
    }
    else {
      for (size_t idx = 0; idx < NumberOf(PwsToolbarButtons); ++idx) {
        if (PwsToolbarButtons[idx].id == ID_SEPARATOR)
          tb->AddSeparator();
        else
          tb->AddTool(PwsToolbarButtons[idx].id, wxEmptyString, wxBitmap(PwsToolbarButtons[idx].bitmap_classic),
                          wxGetTranslation(PwsToolbarButtons[idx].tooltip) );
      }
    }
  }
  else { //toolbar type was changed from the menu
    if (PWSprefs::GetInstance()->GetPref(PWSprefs::UseNewToolbar)) {
      for (size_t idx = 0; idx < NumberOf(PwsToolbarButtons); ++idx) {
        if (PwsToolbarButtons[idx].id == ID_SEPARATOR)
          continue;
        tb->SetToolNormalBitmap(PwsToolbarButtons[idx].id, wxBitmap(PwsToolbarButtons[idx].bitmap_normal));
        tb->SetToolDisabledBitmap(PwsToolbarButtons[idx].id, wxBitmap(PwsToolbarButtons[idx].bitmap_disabled));
      }
    }
    else {
      for (size_t idx = 0; idx < NumberOf(PwsToolbarButtons); ++idx) {
        if (PwsToolbarButtons[idx].id == ID_SEPARATOR)
          continue;
        tb->SetToolNormalBitmap(PwsToolbarButtons[idx].id, wxBitmap(PwsToolbarButtons[idx].bitmap_classic));
        tb->SetToolDisabledBitmap(PwsToolbarButtons[idx].id, wxNullBitmap);
      }
    }
  }
}

/*!
 * Should we show tooltips?
 */

bool PasswordSafeFrame::ShowToolTips()
{
    return true;
}

/*!
 * Get bitmap resources
 */

wxBitmap PasswordSafeFrame::GetBitmapResource( const wxString& name)
{
    // Bitmap retrieval
////@begin PasswordSafeFrame bitmap retrieval
  wxUnusedVar(name);
  return wxNullBitmap;
////@end PasswordSafeFrame bitmap retrieval
}

/*!
 * Get icon resources
 */

wxIcon PasswordSafeFrame::GetIconResource( const wxString& name )
{
    // Icon retrieval
////@begin PasswordSafeFrame icon retrieval
  wxUnusedVar(name);
  if (name == wxT("../graphics/wxWidgets/cpane.xpm"))
  {
    wxIcon icon(cpane_xpm);
    return icon;
  }
  return wxNullIcon;
////@end PasswordSafeFrame icon retrieval
}

void PasswordSafeFrame::SetTitle(const wxString& title)
{
  wxString newtitle = _T("PasswordSafe");
  if (!title.empty()) {
    newtitle += _T(" - ");
    StringX fname = tostringx(title);
    StringX::size_type findex = fname.rfind(_T("/"));
    if (findex != StringX::npos)
      fname = fname.substr(findex + 1);
    newtitle += fname.c_str();
  }
  wxFrame::SetTitle(newtitle);
}

int PasswordSafeFrame::Load(const StringX &passwd)
{
  int status = m_core.ReadCurFile(passwd);
  if (status == PWScore::SUCCESS) {
    wxGetApp().ConfigureIdleTimer();
    SetTitle(m_core.GetCurFile().c_str());
    m_sysTray->SetTrayStatus(SystemTray::TRAY_UNLOCKED);
  } else {
    SetTitle(wxEmptyString);
    m_sysTray->SetTrayStatus(SystemTray::TRAY_CLOSED);
  }
  UpdateStatusBar();
  return status;
}

bool PasswordSafeFrame::Show(bool show)
{
  ShowGrid(show && (m_currentView == GRID));
  ShowTree(show && (m_currentView == TREE));
  return wxFrame::Show(show);
}

/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for wxID_EXIT
 */

void PasswordSafeFrame::OnExitClick( wxCommandEvent& /* evt */ )
{
  m_exitFromMenu = true;
  Close();
}

void PasswordSafeFrame::ShowGrid(bool show)
{
  if (show) {
    m_grid->SetTable(new PWSGridTable(m_grid), true, wxGrid::wxGridSelectRows); // true => auto-delete
    m_grid->EnableEditing(false);
    m_grid->Clear();
    wxFont font(towxstring(PWSprefs::GetInstance()->GetPref(PWSprefs::TreeFont)));
    if (font.IsOk())
      m_grid->SetDefaultCellFont(font);
    ItemListConstIter iter;
    int i;
    for (iter = m_core.GetEntryIter(), i = 0;
         iter != m_core.GetEntryEndIter();
         iter++) {
      if (!m_bFilterActive ||
          m_FilterManager.PassesFiltering(iter->second, m_core))
        m_grid->AddItem(iter->second, i++);
    }

    m_guiInfo->RestoreGridViewInfo(m_grid);
  }
  else {
    m_guiInfo->SaveGridViewInfo(m_grid);
  }

  m_grid->Show(show);
  GetSizer()->Layout();
}

void PasswordSafeFrame::ShowTree(bool show)
{
  if (show) {
    m_tree->Clear();
    wxFont font(towxstring(PWSprefs::GetInstance()->GetPref(PWSprefs::TreeFont)));
    if (font.IsOk())
      m_tree->SetFont(font);
    ItemListConstIter iter;
    for (iter = m_core.GetEntryIter();
         iter != m_core.GetEntryEndIter();
         iter++) {
      if (!m_bFilterActive ||
          m_FilterManager.PassesFiltering(iter->second, m_core))
        m_tree->AddItem(iter->second);
    }

    // Empty groups need to be added separately
    typedef std::vector<StringX> StringVectorX;
    const StringVectorX& emptyGroups = m_core.GetEmptyGroups();
    for (StringVectorX::const_iterator itr = emptyGroups.begin(); itr != emptyGroups.end(); ++itr)
      m_tree->AddEmptyGroup(*itr);

    if (!m_tree->IsEmpty()) // avoid assertion!
      m_tree->SortChildrenRecursively(m_tree->GetRootItem());

    m_guiInfo->RestoreTreeViewInfo(m_tree);
  }
  else {
    m_guiInfo->SaveTreeViewInfo(m_tree);
  }

  m_tree->Show(show);
  GetSizer()->Layout();
}

PWSDragBar* PasswordSafeFrame::GetDragBar()
{
  wxSizer* origSizer = GetSizer();

  wxASSERT(origSizer);
  wxASSERT(origSizer->IsKindOf(wxBoxSizer(wxVERTICAL).GetClassInfo()));
  wxASSERT(((wxBoxSizer*)origSizer)->GetOrientation() == wxVERTICAL);

  wxSizerItem* dragbarItem = origSizer->GetItem(size_t(0));
  wxASSERT_MSG(dragbarItem && dragbarItem->IsWindow() &&
                      wxIS_KIND_OF(dragbarItem->GetWindow(), PWSDragBar),
                    wxT("Found unexpected item while searching for DragBar"));

  PWSDragBar* dragbar = wxDynamicCast(dragbarItem->GetWindow(), PWSDragBar);
  return dragbar;
}

int PasswordSafeFrame::Save(SaveType st /* = ST_INVALID*/)
{
  stringT bu_fname; // used to undo backup if save failed
  PWSprefs *prefs = PWSprefs::GetInstance();

  // Save Application related preferences
  prefs->SaveApplicationPreferences();
  prefs->SaveShortcuts();

  if (m_core.GetCurFile().empty())
    return SaveAs();

  switch (m_core.GetReadFileVersion()) {
    case PWSfile::VCURRENT:
    case PWSfile::V40:
      if (prefs->GetPref(PWSprefs::BackupBeforeEverySave)) {
        int maxNumIncBackups = prefs->GetPref(PWSprefs::BackupMaxIncremented);
        int backupSuffix = prefs->GetPref(PWSprefs::BackupSuffix);
        std::wstring userBackupPrefix = prefs->GetPref(PWSprefs::BackupPrefixValue).c_str();
        std::wstring userBackupDir = prefs->GetPref(PWSprefs::BackupDir).c_str();
        if (!m_core.BackupCurFile(maxNumIncBackups, backupSuffix,
                                  userBackupPrefix, userBackupDir, bu_fname)) {
          switch (st) {
            case ST_NORMALEXIT:
              if (wxMessageBox(_("Unable to create intermediate backup.  Save database elsewhere or with another name?\n\nClick 'No' to exit without saving."),
                               _("Write Error"), wxYES_NO | wxICON_EXCLAMATION, this) == wxID_NO)
                return PWScore::SUCCESS;
              else
                return SaveAs();
            case ST_INVALID:
              // No particular end of PWS exit i.e. user clicked Save or
              // saving a changed database before opening another
              wxMessageBox(_("Unable to create intermediate backup."), _("Write Error"), wxOK|wxICON_ERROR, this);
              return PWScore::USER_CANCEL;
            default:
              break;
          }
          wxMessageBox(_("Unable to create intermediate backup."), _("Write Error"), wxOK|wxICON_ERROR, this);
          return SaveAs();
        } // BackupCurFile failed
      } // BackupBeforeEverySave
      break;
    case PWSfile::NEWFILE:
    {
      // file version mis-match
      stringT NewName = PWSUtil::GetNewFileName(m_core.GetCurFile().c_str(), DEFAULT_SUFFIX);

      wxString msg( wxString::Format(_("The original database, \"%ls\", is in pre-3.0 format. It will be unchanged.\nYour changes will be written as \"%ls\" in the new format, which is unusable by old versions of PasswordSafe. To save your changes in the old format, use the \"File->Export To-> Old (1.x or 2) format\" command."),
                                     m_core.GetCurFile().c_str(), NewName.c_str()));
      if (wxMessageBox(msg, _("File version warning"), wxOK|wxCANCEL|wxICON_INFORMATION, this) == wxID_CANCEL)
        return PWScore::USER_CANCEL;

      m_core.SetCurFile(NewName.c_str());
#if 0
      m_titlebar = PWSUtil::NormalizeTTT(wxT("Password Safe - ") +
                                         m_core.GetCurFile()).c_str();
      SetWindowText(LPCWSTR(m_titlebar));
      app.SetTooltipText(m_core.GetCurFile().c_str());
#endif
      break;
    }
    default:
      ASSERT(0);
      break;
  } // switch on file version

  UUIDList RUElist;
  m_RUEList.GetRUEList(RUElist);
  m_core.SetRUEList(RUElist);

  const int rc = m_core.WriteCurFile();

  if (rc != PWScore::SUCCESS) { // Save failed!
    // Restore backup, if we have one
    if (!bu_fname.empty() && !m_core.GetCurFile().empty())
      pws_os::RenameFile(bu_fname, m_core.GetCurFile().c_str());
    // Show user that we have a problem
    DisplayFileWriteError(rc, m_core.GetCurFile());
    return rc;
  }

  m_core.ResetStateAfterSave();
  m_core.ClearChangedNodes();
  SetChanged(Clear);
//  ChangeOkUpdate();

  // Added/Modified entries now saved - reverse it & refresh display
//  if (m_bUnsavedDisplayed)
//    OnShowUnsavedEntries();

//  if (m_bFilterActive && m_bFilterForStatus) {
//    m_ctlItemList.Invalidate();
//    m_ctlItemTree.Invalidate();
//  }

  // Only refresh views if not existing
  if (st != ST_NORMALEXIT)
    RefreshViews();

  return PWScore::SUCCESS;
}

int PasswordSafeFrame::SaveIfChanged()
{
  if (m_core.IsReadOnly())
    return PWScore::SUCCESS;

  // Note: RUE list saved here via time stamp being updated.
  // Otherwise it won't be saved unless something else has changed
  if ((m_bTSUpdated || m_core.WasDisplayStatusChanged()) &&
       m_core.GetNumEntries() > 0) {
    int rc = Save();
    if (rc != PWScore::SUCCESS)
      return PWScore::USER_CANCEL;
    else
      return PWScore::SUCCESS;
  }


  // offer to save existing database if it was modified.
  // used before loading another
  // returns PWScore::SUCCESS if save succeeded or if user decided
  // not to save

  if (m_core.IsChanged() || m_core.HaveDBPrefsChanged()) {
    wxString prompt(_("Do you want to save changes to the password database"));
    if (!m_core.GetCurFile().empty()) {
      prompt += wxT(": ");
      prompt += m_core.GetCurFile().c_str();
    }
    prompt += wxT("?");
    wxMessageDialog dlg(this, prompt, GetTitle(),
                        (wxICON_QUESTION | wxCANCEL |
                         wxYES_NO | wxYES_DEFAULT));
    int rc = dlg.ShowModal();
    switch (rc) {
      case wxID_CANCEL:
        return PWScore::USER_CANCEL;
      case wxID_YES:
        rc = Save();
        // Make sure that file was successfully written
        if (rc != PWScore::SUCCESS)
          return PWScore::CANT_OPEN_FILE;
      case wxID_NO:
        // Reset changed flag
        SetChanged(Clear);
        break;
      default:
        ASSERT(0);
    }
  }
  return PWScore::SUCCESS;
}

void PasswordSafeFrame::ClearData()
{
  m_grid->Clear();
  m_tree->Clear();
  //the grid would have deleted the data in one of its callbacks
  //but only if it was initialized, which might not happen
  //if it was never shown.  In those cases, clear the data here
  if (m_core.GetNumEntries() != 0) {
    m_core.ClearData();
  }
  m_core.ReInit();
}

CItemData *PasswordSafeFrame::GetSelectedEntry() const
{
  if (m_tree->IsShown()) {
    // get selected from tree
    return m_tree->GetItem(m_tree->GetSelection());
  } else if (m_grid->IsShown()) {
    // get selected from grid
    return m_grid->GetItem(m_grid->GetGridCursorRow());
  }
  return NULL;
}

/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for wxID_OPEN
 */

void PasswordSafeFrame::OnOpenClick( wxCommandEvent& /* evt */ )
{
  DoOpen(_("Please Choose a Database to Open:"));
}


/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for wxID_CLOSE
 */

void PasswordSafeFrame::OnCloseClick( wxCommandEvent& /* evt */ )
{
  PWSprefs *prefs = PWSprefs::GetInstance();

  // Save Application related preferences
  prefs->SaveApplicationPreferences();
  if( !m_core.GetCurFile().empty() ) {
    int rc = SaveIfChanged();
    if (rc != PWScore::SUCCESS)
      return;
    m_core.UnlockFile(m_core.GetCurFile().c_str());
    m_core.SetCurFile(wxEmptyString);
    ClearData();
    SetTitle(wxEmptyString);
    m_sysTray->SetTrayStatus(SystemTray::TRAY_CLOSED);
    m_core.SetReadOnly(false);
    UpdateStatusBar();
  }
}

int PasswordSafeFrame::DoOpen(const wxString& title)
{
  stringT dir = PWSdirs::GetSafeDir();
  //Open-type dialog box
  wxFileDialog fd(this, title, dir.c_str(), wxT("pwsafe.psafe4"),
                  _("Password Safe Databases (*.psafe4; *.psafe3; *.dat)|*.psafe4;*.psafe3;*.dat|Password Safe Backups (*.bak)|*.bak|Password Safe Intermediate Backups (*.ibak)|*.ibak|All files (*.*; *)|*.*;*"),
                  (wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_CHANGE_DIR));

  while (1) {
    if (fd.ShowModal() == wxID_OK) {
      int rc = Open(fd.GetPath()); // prompt for password of new file and load.
      if (rc == PWScore::SUCCESS) {
        return PWScore::SUCCESS;
      }
    } else { // user cancelled
      return PWScore::USER_CANCEL;
    }
  }
}

int PasswordSafeFrame::Open(const wxString &fname)
{

  //Check that this file isn't already open
  if (wxFileName(fname).SameAs(towxstring(m_core.GetCurFile()))) {
    //It is the same damn file
    wxMessageBox(_("That file is already open."), _("Open database"), wxOK|wxICON_EXCLAMATION, this);
    return PWScore::ALREADY_OPEN;
  }

  int rc = SaveIfChanged();
  if (rc != PWScore::SUCCESS)
    return rc;

  // prompt for password, try to Load.
  CSafeCombinationPrompt pwdprompt(this, m_core, fname);
  if (pwdprompt.ShowModal() == wxID_OK) {
    m_core.SetCurFile(tostringx(fname));
    StringX password = pwdprompt.GetPassword();
    int retval = Load(password);
    if (retval == PWScore::SUCCESS) {
      Show();
      wxGetApp().recentDatabases().AddFileToHistory(fname);
    }
    return retval;
  } else
    return PWScore::USER_CANCEL;

#if 0

  rc = GetAndCheckPassword(pszFilename, passkey, GCP_NORMAL, bReadOnly);  // OK, CANCEL, HELP
  switch (rc) {
    case PWScore::SUCCESS:
      app.AddToMRU(pszFilename.c_str());
      m_bAlreadyToldUserNoSave = false;
      break; // Keep going...
    case PWScore::CANT_OPEN_FILE:
      temp.Format(IDS_SAFENOTEXIST, pszFilename.c_str());
      cs_title.LoadString(IDS_FILEOPENERROR);
      MessageBox(temp, cs_title, MB_OK|MB_ICONWARNING);
    case TAR_OPEN:
      return Open();
    case TAR_NEW:
      return New();
    case PWScore::WRONG_PASSWORD:
    case PWScore::USER_CANCEL:
      /*
      If the user just cancelled out of the password dialog,
      assume they want to return to where they were before...
      */
      return PWScore::USER_CANCEL;
    default:
      ASSERT(0); // we should take care of all cases explicitly
      return PWScore::USER_CANCEL; // conservative behaviour for release version
  }

  // clear the data before loading the new file
  ClearData();

  cs_title.LoadString(IDS_FILEREADERROR);
  MFCAsker q;
  MFCReporter r;
  m_core.SetAsker(&q);
  m_core.SetReporter(&r);
  rc = m_core.ReadFile(pszFilename, passkey);
  m_core.SetAsker(NULL);
  m_core.SetReporter(NULL);
  switch (rc) {
    case PWScore::SUCCESS:
      break;
    case PWScore::CANT_OPEN_FILE:
      temp.Format(IDS_CANTOPENREADING, pszFilename.c_str());
      MessageBox(temp, cs_title, MB_OK|MB_ICONWARNING);
      /*
      Everything stays as is... Worst case,
      they saved their file....
      */
      return PWScore::CANT_OPEN_FILE;
    case PWScore::BAD_DIGEST:
    {
      temp.Format(IDS_FILECORRUPT, pszFilename.c_str());
      const int yn = MessageBox(temp, cs_title, MB_YESNO|MB_ICONERROR);
      if (yn == IDYES) {
        rc = PWScore::SUCCESS;
        break;
      } else
        return rc;
    }
    default:
      temp.Format(IDS_UNKNOWNERROR, pszFilename.c_str());
      MessageBox(temp, cs_title, MB_OK|MB_ICONERROR);
      return rc;
  }
  m_core.SetCurFile(pszFilename);
  m_titlebar = PWSUtil::NormalizeTTT(_T("Password Safe - ") +
                                     m_core.GetCurFile()).c_str();
  SetWindowText(LPCTSTR(m_titlebar));
  CheckExpiredPasswords();
  ChangeOkUpdate();

  // Tidy up filters
  m_currentfilter.Empty();
  m_bFilterActive = false;

  RefreshViews();
  SetInitialDatabaseDisplay();
  m_core.SetDefUsername(PWSprefs::GetInstance()->
                        GetPref(PWSprefs::DefaultUsername));
  m_core.SetUseDefUser(PWSprefs::GetInstance()->
                       GetPref(PWSprefs::UseDefaultUser) ? true : false);
  m_needsreading = false;
  SelectFirstEntry();

  return rc;
#endif
}


void PasswordSafeFrame::OnPropertiesClick( wxCommandEvent& /* evt */ )
{
  CProperties props(this, m_core);
  props.ShowModal();
}


/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for ID_CHANGECOMBO
 */

void PasswordSafeFrame::OnChangePasswdClick( wxCommandEvent& /* evt */ )
{
  CSafeCombinationChange* window = new CSafeCombinationChange(this, m_core);
  int returnValue = window->ShowModal();
  if (returnValue == wxID_OK) {
    m_core.ChangePasskey(window->GetNewpasswd());
  }
  window->Destroy();
}


/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for wxID_SAVE
 */

void PasswordSafeFrame::OnSaveClick( wxCommandEvent& /* evt */ )
{
  Save();
}


void PasswordSafeFrame::OnSaveAsClick(wxCommandEvent& evt)
{
  UNREFERENCED_PARAMETER(evt);
  SaveAs();
}

int PasswordSafeFrame::SaveAs()
{
  const PWSfile::VERSION curver = m_core.GetReadFileVersion();
  
  if (curver != PWSfile::V30 && curver != PWSfile::V40 &&
      curver != PWSfile::UNKNOWN_VERSION) {
    if (wxMessageBox( wxString::Format(_("The original database, '%ls', is in pre-3.0 format. The data will now be written in the new format, which is unusable by old versions of PasswordSafe. To save the data in the old format, use the 'File->Export To-> Old (1.x or 2) format' command."),
                                        m_core.GetCurFile().c_str()), _("File version warning"),
                                        wxOK | wxCANCEL | wxICON_EXCLAMATION, this) == wxCANCEL) {
      return PWScore::USER_CANCEL;
    }
  }

  StringX cf(m_core.GetCurFile());
  if(cf.empty()) {
    cf = wxT("pwsafe"); // reasonable default for first time user
  }
  wxString v3FileName = towxstring(PWSUtil::GetNewFileName(cf.c_str(), DEFAULT_SUFFIX));

  wxString title = (m_core.GetCurFile().empty()? _("Please choose a name for the current (Untitled) database:") :
                                    _("Please choose a new name for the current database:"));
  wxFileName filename(v3FileName);
  wxString dir = filename.GetPath();
  if (dir.empty())
    dir = towxstring(PWSdirs::GetSafeDir());

  //filename cannot have the path
  wxFileDialog fd(this, title, dir, filename.GetFullName(),
                  _("Password Safe Databases (*.psafe3; *.dat)|*.psafe3;*.dat|All files (*.*; *)|*.*;*"),
                   wxFD_SAVE | wxFD_OVERWRITE_PROMPT);

  if (fd.ShowModal() != wxID_OK) {
    return PWScore::USER_CANCEL;
  }

  StringX newfile = tostringx(fd.GetPath());

  std::wstring locker(L""); // null init is important here
  // Note: We have to lock the new file before releasing the old (on success)
  if (!m_core.LockFile2(newfile.c_str(), locker)) {
    wxMessageBox(wxString::Format(_("%ls\n\nFile is currently locked by %ls"), newfile.c_str(), locker.c_str()),
                    _("File lock error"), wxOK | wxICON_ERROR, this);
    return PWScore::CANT_OPEN_FILE;
  }

  // Save file UUID, clear it to generate new one, restore if necessary
  pws_os::CUUID file_uuid = m_core.GetFileUUID();
  m_core.ClearFileUUID();

  UUIDList RUElist;
  m_RUEList.GetRUEList(RUElist);
  m_core.SetRUEList(RUElist);

  int rc = m_core.WriteFile(newfile, curver);
  m_core.ResetStateAfterSave();
  m_core.ClearChangedNodes();

  if (rc != PWScore::SUCCESS) {
    m_core.SetFileUUID(file_uuid);
    m_core.UnlockFile2(newfile.c_str());
    DisplayFileWriteError(rc, newfile);
    return PWScore::CANT_OPEN_FILE;
  }
  if (!m_core.GetCurFile().empty())
    m_core.UnlockFile(m_core.GetCurFile().c_str());

  // Move the newfile lock to the right place
  m_core.MoveLock();

  m_core.SetCurFile(newfile);
#if 0
  m_titlebar = PWSUtil::NormalizeTTT(wxT("Password Safe - ") +
                                     m_core.GetCurFile()).c_str();
  SetWindowText(LPCWSTR(m_titlebar));
  app.SetTooltipText(m_core.GetCurFile().c_str());
#endif
  SetTitle(towxstring(m_core.GetCurFile()));
  SetChanged(Clear);
#if 0
  ChangeOkUpdate();

  // Added/Modified entries now saved - reverse it & refresh display
  if (m_bUnsavedDisplayed)
    OnShowUnsavedEntries();

  if (m_bFilterActive && m_bFilterForStatus) {
    m_ctlItemList.Invalidate();
    m_ctlItemTree.Invalidate();
  }
#endif
  RefreshViews();

  wxGetApp().recentDatabases().AddFileToHistory(towxstring(newfile));

  if (m_core.IsReadOnly()) {
    // reset read-only status (new file can't be read-only!)
    // and so cause toolbar to be the correct version
    m_core.SetReadOnly(false);
  }

  return PWScore::SUCCESS;
}

/*!
 * wxEVT_CLOSE_WINDOW event handler for ID_PASSWORDSAFEFRAME
 */

void PasswordSafeFrame::OnCloseWindow( wxCloseEvent& evt )
{
  wxGetApp().SaveFrameCoords();
  const bool systrayEnabled = PWSprefs::GetInstance()->GetPref(PWSprefs::UseSystemTray);
  /*
   * Really quit if the user chooses to quit from File menu, or
   * by clicking the 'X' in title bar or the system menu pulldown
   * from the top-left of the titlebar while systray is disabled
   */
  if (m_exitFromMenu || !systrayEnabled) {
    if (evt.CanVeto()) {
      int rc = SaveIfChanged();
      if (rc == PWScore::USER_CANCEL) {
        evt.Veto();
        m_exitFromMenu = false;
        return;
      }
    }

    if (PWSprefs::GetInstance()->GetPref(PWSprefs::ClearClipboardOnExit)) {
      PWSclipboard::GetInstance()->ClearData();
    }

    // Don't leave dangling locks!
    if (!m_core.GetCurFile().empty())
      m_core.UnlockFile(m_core.GetCurFile().c_str());

    SaveSettings();
    Destroy();
  }
  else {
    const bool lockOnMinimize = PWSprefs::GetInstance()->GetPref(PWSprefs::DatabaseClear);

    if (PWSprefs::GetInstance()->GetPref(PWSprefs::ClearClipboardOnExit)) {
      PWSclipboard::GetInstance()->ClearData();
    }
#if wxCHECK_VERSION(2,9,5)
    CallAfter(&PasswordSafeFrame::HideUI, lockOnMinimize);
#else
    HideUI(lockOnMinimize);
#endif
  }
}

/**
 * Changes the language on the fly to one of the supported languages.
 *
 * \see PasswordSafeFrame::Init() for currently supported languages.
 */
void PasswordSafeFrame::OnLanguageClick(wxCommandEvent& evt)
{
  auto id = evt.GetId();
  // First, uncheck all language menu items, hence the previously selected but also the new one
  for (size_t menu_id = ID_LANGUAGE_BEGIN+1; menu_id<ID_LANGUAGE_END; menu_id++)
    GetMenuBar()->Check( menu_id, false );

  // If a new language has been selected successfully we have to
  // recreate the UI so that the language change takes effect
  wxLanguage userLang=get<0>(m_languages[id]);
  if (wxGetApp().ActivateLanguage(userLang, false)) {
    m_selectedLanguage = id;
    wxString userLangName=wxLocale::GetLanguageCanonicalName(userLang);
    if (!userLangName.IsEmpty()){
      PWSprefs::GetInstance()->SetPref(PWSprefs::LanguageFile, tostringx(userLangName));
      pws_os::Trace(L"Saved user-preferred language: name= %ls\n", ToStr(userLangName));
    }

    // Recreate menubar
    CreateMenubar();

    // Recreate toolbar
    ReCreateMainToolbar();

    // Recreate dragbar
    ReCreateDragToolbar();

    // Recreate search bar
    wxCHECK_RET(m_search, wxT("Search object not created so far"));
    m_search->ReCreateSearchBar();
  } else {
    GetMenuBar()->Check( m_selectedLanguage, true );
  }
}

/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for wxID_ABOUT
 */

void PasswordSafeFrame::OnAboutClick( wxCommandEvent& /* evt */ )
{
  CAbout* window = new CAbout(this);
  window->ShowModal();
  window->Destroy();
}

/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for ID_BROWSEURL
 */

void PasswordSafeFrame::OnBrowseURL(wxCommandEvent& evt)
{
  CItemData rueItem;
  CItemData* item = IsRUEEvent(evt)? (m_RUEList.GetPWEntry(GetEventRUEIndex(evt), rueItem)? &rueItem: NULL) : GetSelectedEntry();
  if (item)
    DoBrowse(*item, false); //false => no autotype
}

/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for ID_BROWSEURLPLUS
 */

void PasswordSafeFrame::OnBrowseUrlAndAutotype(wxCommandEvent& evt)
{
  CItemData rueItem;
  CItemData* item = IsRUEEvent(evt)? (m_RUEList.GetPWEntry(GetEventRUEIndex(evt), rueItem)? &rueItem: NULL) : GetSelectedEntry();
  if (item) {
    DoBrowse(*item, true); //true => autotype
  }
}

/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for ID_SENDEMAIL
 */

void PasswordSafeFrame::OnSendEmail(wxCommandEvent& evt)
{
  CItemData rueItem;
  CItemData* item = IsRUEEvent(evt)? (m_RUEList.GetPWEntry(GetEventRUEIndex(evt), rueItem)? &rueItem: NULL) : GetSelectedEntry();
  if (item)
    DoEmail(*item);
}

/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for ID_RUNCOMMAND
 */

void PasswordSafeFrame::OnRunCommand(wxCommandEvent& evt)
{
  CItemData rueItem;
  CItemData* item = IsRUEEvent(evt)? (m_RUEList.GetPWEntry(GetEventRUEIndex(evt), rueItem)? &rueItem: NULL) : GetSelectedEntry();
  if (item)
    DoRun(*item);
}

/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for ID_AUTOTYPE
 */

void PasswordSafeFrame::OnAutoType(wxCommandEvent& evt)
{
  CItemData rueItem;
  CItemData* item = IsRUEEvent(evt)? (m_RUEList.GetPWEntry(GetEventRUEIndex(evt), rueItem)? &rueItem: NULL) : GetSelectedEntry();
  if (item) {
#ifdef __WXMAC__
    Lower();
#endif
    DoAutotype(*item);
  }
}

void PasswordSafeFrame::OnGotoBase(wxCommandEvent& /*evt*/)
{
  CItemData* item = GetSelectedEntry();
  if (item && (item->IsAlias() || item->IsShortcut())) {
    item = m_core.GetBaseEntry(item);
    CUUID base_uuid = item->GetUUID();
    SelectItem(base_uuid);
    UpdateAccessTime(*item);
  }
}

void PasswordSafeFrame::OnEditBase(wxCommandEvent& /*evt*/)
{
  CItemData* item = GetSelectedEntry();
  if (item && item->IsDependent()) {
    item = m_core.GetBaseEntry(item);
    ASSERT(item != NULL);
    DoEdit(*item);
    UpdateAccessTime(*item);
  }
}

void PasswordSafeFrame::SelectItem(const CUUID& uuid)
{
    if (m_currentView == GRID) {
      m_grid->SelectItem(uuid);
    }
    else {
      m_tree->SelectItem(uuid);
    }

}

void PasswordSafeFrame::SaveSettings(void) const
{
  m_grid->SaveSettings();
}

void PasswordSafeFrame::SetChanged(ChangeType changed)
{
  if (m_core.IsReadOnly())
    return;

  switch (changed) {
    case Data:
      if (PWSprefs::GetInstance()->GetPref(PWSprefs::SaveImmediately)) {
        // Don't save if just adding group as it will just 'disappear'!
        Save();
      } else {
        m_core.SetDBChanged(true);
      }
      break;
    case Clear:
      m_core.SetChanged(false, false);
      m_bTSUpdated = false;
      break;
    case TimeStamp:
      if (PWSprefs::GetInstance()->GetPref(PWSprefs::MaintainDateTimeStamps))
        m_bTSUpdated = true;
      break;
    case DBPrefs:
      m_core.SetDBPrefsChanged(true);
      break;
    case ClearDBPrefs:
      m_core.SetDBPrefsChanged(false);
      break;
    default:
      ASSERT(0);
  }
  UpdateStatusBar();
}

bool PasswordSafeFrame::IsRUEEvent(const wxCommandEvent& evt) const
{
  const int cmd = int(evt.GetExtraLong());
  return IsRUECommand(cmd) && GetRUEIndex(cmd) < m_RUEList.GetCount();
}

long PasswordSafeFrame::GetEventRUEIndex(const wxCommandEvent& evt) const
{
  return GetRUEIndex(int(evt.GetExtraLong()));
}


void PasswordSafeFrame::UpdateAccessTime(CItemData &ci)
{
  // Mark access time if so configured
  // First add to RUE List
  m_RUEList.AddRUEntry(ci.GetUUID());
  bool bMaintainDateTimeStamps = PWSprefs::GetInstance()->
              GetPref(PWSprefs::MaintainDateTimeStamps);

  if (!m_core.IsReadOnly() && bMaintainDateTimeStamps) {
    ci.SetATime();
    SetChanged(TimeStamp);
#ifdef NOTYET
    // Need to update view if there
    if (m_nColumnIndexByType[CItemData::ATIME] != -1) {
      // Get index of entry
      DisplayInfo *pdi = (DisplayInfo *)ci.GetDisplayInfo();
      // Get value in correct format
      CString cs_atime = ci.GetATimeL().c_str();
      // Update it
      m_ctlItemList.SetItemText(pdi->list_index,
        m_nColumnIndexByType[CItemData::ATIME], cs_atime);
    }
#endif
  }
}

void PasswordSafeFrame::DispatchDblClickAction(CItemData &item)
{
  /**
   * If entry has a double-click action, use it.
   * (Unless the entry's a shortcut, in which case we ask the base)
   * Otherwise, use the preference.
   */

  bool isShift = ::wxGetKeyState(WXK_SHIFT);
  PWSprefs::IntPrefs pref = (isShift) ?
    PWSprefs::ShiftDoubleClickAction : PWSprefs::DoubleClickAction;

  short DCA = short(PWSprefs::GetInstance()->GetPref(pref));

  CItemData *pci = item.IsShortcut() ? m_core.GetBaseEntry(&item) : &item;

  short itemDCA;
  pci->GetDCA(itemDCA, isShift);

  if (itemDCA >= PWSprefs::minDCA && itemDCA <= PWSprefs::maxDCA)
    DCA = itemDCA;

  switch (DCA) {
  case PWSprefs::DoubleClickAutoType:
    DoAutotype(item);
    break;
  case PWSprefs::DoubleClickBrowse:
    DoBrowse(item, false); //false => no autotype
    break;
  case PWSprefs::DoubleClickCopyNotes:
    DoCopyNotes(item);
    break;
  case PWSprefs::DoubleClickCopyPassword:
    DoCopyPassword(item);
    break;
  case PWSprefs::DoubleClickCopyUsername:
    DoCopyUsername(item);
    break;
  case PWSprefs::DoubleClickCopyPasswordMinimize:
    DoCopyPassword(item);
    Iconize();
    break;
  case PWSprefs::DoubleClickViewEdit:
    DoEdit(item);
    break;
  case PWSprefs::DoubleClickBrowsePlus:
    DoBrowse(item, true); //true => autotype
    break;
  case PWSprefs::DoubleClickRun:
    DoRun(item);
    break;
  case PWSprefs::DoubleClickSendEmail:
    DoEmail(item);
    break;
  default: {
    wxString action;
    action.Printf(_("Unknown code: %d"),
                  PWSprefs::GetInstance()->GetPref(PWSprefs::DoubleClickAction));
    wxMessageBox(action, _("Error"), wxOK|wxICON_ERROR, this);
    break;
  }
  }
}

static void FlattenTree(wxTreeItemId id, PWSTreeCtrl* tree, OrderedItemList& olist)
{
  wxTreeItemIdValue cookie;
  for (wxTreeItemId childId = tree->GetFirstChild(id, cookie); childId.IsOk();
                          childId = tree->GetNextChild(id, cookie)) {
    CItemData* item = tree->GetItem(childId);
    if (item)
      olist.push_back(*item);

    if (tree->HasChildren(childId))
      ::FlattenTree(childId, tree, olist);
  }
}

void PasswordSafeFrame::FlattenTree(OrderedItemList& olist)
{
  ::FlattenTree(m_tree->GetRootItem(), m_tree, olist);
}

///////////////////////////////////////////
// Handles right-click event forwarded by the tree and list views
// The logic is the same as DboxMain::OnContextMenu in src/ui/Windows/MainMenu.cpp
void PasswordSafeFrame::OnContextMenu(const CItemData* item)
{
  if (item == NULL) {
    wxMenu groupEditMenu;
    groupEditMenu.Append(wxID_ADD, _("Add &Entry"));
    groupEditMenu.Append(ID_ADDGROUP, _("Add &Group"));
    groupEditMenu.Append(ID_RENAME, _("&Rename Group"));
    groupEditMenu.Append(wxID_DELETE, _("&Delete Group"));
    if (m_currentView == TREE)
      m_tree->PopupMenu(&groupEditMenu);
  } else {
    wxMenu itemEditMenu;
    itemEditMenu.Append(ID_COPYUSERNAME,   _("Copy &Username to Clipboard"));
    itemEditMenu.Append(ID_COPYPASSWORD,   _("&Copy Password to Clipboard"));
    itemEditMenu.Append(ID_PASSWORDSUBSET, _("Display subset of Password"));
    itemEditMenu.Append(ID_COPYNOTESFLD,   _("Copy &Notes to Clipboard"));
    itemEditMenu.Append(ID_COPYURL,        _("Copy UR&L to Clipboard"));
    itemEditMenu.Append(ID_COPYEMAIL,      _("Copy email to Clipboard"));
    itemEditMenu.Append(ID_COPYRUNCOMMAND, _("Copy Run Command to Clipboard"));
    itemEditMenu.AppendSeparator();
    itemEditMenu.Append(ID_BROWSEURL,      _("&Browse to URL"));
    itemEditMenu.Append(ID_BROWSEURLPLUS,  _("Browse to URL + &Autotype"));
    itemEditMenu.Append(ID_SENDEMAIL,      _("Send &email"));
    itemEditMenu.Append(ID_RUNCOMMAND,     _("&Run Command"));
    itemEditMenu.Append(ID_AUTOTYPE,       _("Perform Auto &Type"));
    itemEditMenu.AppendSeparator();
    itemEditMenu.Append(ID_EDIT,           _("Edit/&View Entry..."));
    itemEditMenu.Append(ID_DUPLICATEENTRY, _("&Duplicate Entry"));
    itemEditMenu.Append(wxID_DELETE,       _("Delete Entry"));
    itemEditMenu.Append(ID_CREATESHORTCUT, _("Create &Shortcut"));
    itemEditMenu.Append(ID_GOTOBASEENTRY,  _("&Go to Base entry"));
    itemEditMenu.Append(ID_EDITBASEENTRY,  _("&Edit Base entry"));

    switch (item->GetEntryType()) {
      case CItemData::ET_NORMAL:
      case CItemData::ET_SHORTCUTBASE:
        itemEditMenu.Delete(ID_GOTOBASEENTRY);
        itemEditMenu.Delete(ID_EDITBASEENTRY);
        break;

      case CItemData::ET_ALIASBASE:
        itemEditMenu.Delete(ID_CREATESHORTCUT);
        itemEditMenu.Delete(ID_GOTOBASEENTRY);
        itemEditMenu.Delete(ID_EDITBASEENTRY);
        break;

      case CItemData::ET_ALIAS:
      case CItemData::ET_SHORTCUT:
        itemEditMenu.Delete(ID_CREATESHORTCUT);
        break;

      default:
        wxASSERT_MSG(false, wxT("Unexpected CItemData type"));
        break;
    }

    if (item->IsShortcut()) {
      item = m_core.GetBaseEntry(item);
    }

    if (item->IsUserEmpty())
      itemEditMenu.Delete(ID_COPYUSERNAME);

    if (item->IsNotesEmpty())
      itemEditMenu.Delete(ID_COPYNOTESFLD);

    if (item->IsEmailEmpty() && !item->IsURLEmail()) {
      itemEditMenu.Delete(ID_COPYEMAIL);
      itemEditMenu.Delete(ID_SENDEMAIL);
    }

    if ( item->IsURLEmpty()) {
      itemEditMenu.Delete(ID_COPYURL);
      itemEditMenu.Delete(ID_BROWSEURL);
      itemEditMenu.Delete(ID_BROWSEURLPLUS);
    }

    if (item->IsRunCommandEmpty()) {
      itemEditMenu.Delete(ID_COPYRUNCOMMAND);
      itemEditMenu.Delete(ID_RUNCOMMAND);
    }

    if ( m_currentView == TREE )
      m_tree->PopupMenu(&itemEditMenu);
    else
      m_grid->PopupMenu(&itemEditMenu);
  }
}

CItemData* PasswordSafeFrame::GetBaseOfSelectedEntry()
{
  CItemData* item = GetSelectedEntry();
  if (item && (item->IsShortcut() || item->IsAlias())) {
    item = m_core.GetBaseEntry(item);
  }
  return item;
}

////////////////////////////////////////////////////////
// This function is used for wxCommandUIEvent handling
// of all commands, to avoid scattering this stuff all
// over the place.  It is just a copy of the logic from
// DboxMain::OnUpdateMenuToolbar() function defined in
// src/ui/Windows/Dboxmain.cpp
//
void PasswordSafeFrame::OnUpdateUI(wxUpdateUIEvent& evt)
{
  switch (evt.GetId()) {
    case wxID_SAVE:
      evt.Enable(m_core.IsChanged() || m_core.HaveDBPrefsChanged());
      break;

    case ID_ADDGROUP:
    case ID_EXPANDALL:
    case ID_COLLAPSEALL:
      evt.Enable(m_currentView == TREE);
      break;

    case ID_RENAME:
      // only allowed if a GROUP item is selected in tree view
      evt.Enable(m_currentView == TREE && m_tree->GetSelection() != m_tree->GetRootItem() &&
                 m_tree->ItemIsGroup(m_tree->GetSelection()));
      break;

    case ID_BROWSEURL:
    case ID_BROWSEURLPLUS:
    case ID_COPYURL:
    {
      CItemData* item = GetBaseOfSelectedEntry();
      evt.Enable( item && !item->IsURLEmpty() && !item->IsURLEmail() );
      break;
    }
    case ID_SENDEMAIL:
    case ID_COPYEMAIL:
    {
      CItemData* item = GetBaseOfSelectedEntry();
      evt.Enable( item && !item->IsEmailEmpty() );
      break;
    }
    case ID_COPYUSERNAME:
    {
      CItemData* item = GetBaseOfSelectedEntry();
      evt.Enable(item && !item->IsUserEmpty());
      break;
    }
    case ID_COPYNOTESFLD:
    {
      CItemData* item = GetBaseOfSelectedEntry();
      evt.Enable(item && !item->IsNotesEmpty());
      break;
    }
    case ID_RUNCOMMAND:
    case ID_COPYRUNCOMMAND:
    {
      CItemData* item = GetBaseOfSelectedEntry();
      evt.Enable(item && !item->IsRunCommandEmpty());
      break;
    }
    case ID_CREATESHORTCUT:
    {
      CItemData* item = GetSelectedEntry();
      evt.Enable(item && (item->IsNormal() || item->IsShortcutBase()));
      break;
    }
    case ID_DUPLICATEENTRY:
    case ID_COPYPASSWORD:
    case ID_AUTOTYPE:
    case ID_EDIT:
    case ID_PASSWORDSUBSET:
      // not allowed if a group is selected in tree view
      evt.Enable(m_currentView == GRID || GetSelectedEntry() != NULL );
      break;

    case ID_GOTOBASEENTRY:
    case ID_EDITBASEENTRY:
    {
      const CItemData* item = GetSelectedEntry();
      evt.Enable(item != NULL && (item->IsShortcut() || item->IsAlias()));
      break;
    }

    case wxID_UNDO:
      evt.Enable(m_core.AnyToUndo());
      break;

    case wxID_REDO:
      evt.Enable(m_core.AnyToRedo());
      break;

    case ID_SYNCHRONIZE:
      evt.Enable(!m_core.IsReadOnly() && !m_core.GetCurFile().empty() && m_core.GetNumEntries() != 0);
      break;

    case wxID_DELETE:
      evt.Enable(GetSelectedEntry() != NULL);
      break;

    case ID_SHOWHIDE_UNSAVED:
      evt.Enable(!m_bShowExpiry);
      break;

    case ID_SHOW_ALL_EXPIRY:
      evt.Enable(!m_bShowUnsaved);
      break;

    default:
      break;
  }
}

bool PasswordSafeFrame::IsClosed() const
{
  return m_core.GetCurFile().empty() && m_core.GetNumEntries() == 0 && !m_core.IsChanged()
                    && !m_core.AnyToUndo() && !m_core.AnyToRedo();
}

// Implementation of UIinterface methods

void PasswordSafeFrame::DatabaseModified(bool modified)
{
  if (!modified)
    return;

  if (m_core.HaveDBPrefsChanged()) {
    wxCommandEvent evt(wxEVT_DB_PREFS_CHANGE, wxID_ANY);
    evt.ResumePropagation(wxEVENT_PROPAGATE_MAX); //let it propagate through the entire window tree
    if (m_tree) {
      m_tree->GetEventHandler()->AddPendingEvent(evt);
      evt.StopPropagation(); //or else it will come to the frame twice
    }
    if (m_grid) m_grid->GetEventHandler()->AddPendingEvent(evt);
  }
  else if (m_core.IsChanged()) {  //"else if" => both DB and it's prefs can't change at the same time
    if (m_search) m_search->Invalidate();
    if (m_currentView == TREE) {
      if (m_grid != NULL)
        m_grid->OnPasswordListModified();
    }
    else {
#if 0
    if (m_tree != NULL)
      m_tree->???
#endif
    }
  } else {
    wxFAIL_MSG(wxT("What changed in the DB if not entries or preferences?"));
  }
}

void PasswordSafeFrame::GUISetupDisplayInfo(CItemData &ci)
{
  UNREFERENCED_PARAMETER(ci);
  // XXX TBD
}

void PasswordSafeFrame::RebuildGUI(const int iView /*= iBothViews*/)
{
  // assumption: the view get updated on switching between each other,
  // so we don't need to update both at the same time
  if (IsTreeView() && (iView & iTreeOnly)) {
    m_guiInfo->Save(this);
    ShowTree();
    m_guiInfo->Restore(this);
  }
  else if (iView & iListOnly) {
    m_guiInfo->Save(this);
    ShowGrid();
    m_guiInfo->Restore(this);
  }
}

void PasswordSafeFrame::RefreshViews()
{
  m_guiInfo->Save(this);

  if (IsTreeView())
    ShowTree();
  else
    ShowGrid();

  m_guiInfo->Restore(this);
  UpdateStatusBar();
}

void PasswordSafeFrame::UpdateGUI(UpdateGUICommand::GUI_Action ga,
                                  const CUUID &entry_uuid,
                                  CItemData::FieldType ft,
                                  bool bUpdateGUI)
{
  // Callback from PWScore if GUI needs updating
  // Note: For some values of 'ga', 'ci' & ft are invalid and not used.

  // "bUpdateGUI" is only used by GUI_DELETE_ENTRY when called as part
  // of the Edit Entry Command where the entry is deleted and then added and
  // the GUI should not be updated until after the Add.

  // TODO: bUpdateGUI processing in PasswordSafeFrame::UpdateGUI
  UNREFERENCED_PARAMETER(ft);
  UNREFERENCED_PARAMETER(bUpdateGUI);

  CItemData *pci(NULL);

  ItemListIter pos = m_core.Find(entry_uuid);
  if (pos != m_core.GetEntryEndIter()) {
    pci = &pos->second;
  } else if (ga == UpdateGUICommand::GUI_ADD_ENTRY ||
             ga == UpdateGUICommand::GUI_REFRESH_ENTRYFIELD ||
             ga == UpdateGUICommand::GUI_REFRESH_ENTRYPASSWORD) {
    pws_os::Trace(wxT("Couldn't find uuid %ls"),
                  StringX(CUUID(entry_uuid)).c_str());
    return;
  }

#ifdef NOTYET
  PWSprefs *prefs = PWSprefs::GetInstance();
#endif
  switch (ga) {
    case UpdateGUICommand::GUI_ADD_ENTRY:
      ASSERT(pci != NULL);
      m_tree->AddItem(*pci);
      m_grid->AddItem(*pci);
      break;
    case UpdateGUICommand::GUI_DELETE_ENTRY:
      m_grid->Remove(entry_uuid);
      m_tree->Remove(entry_uuid);
      break;
    case UpdateGUICommand::GUI_REFRESH_TREE:
      // Caused by Database preference changed about showing username and/or
      // passwords in the Tree View
      RebuildGUI(iTreeOnly);
      break;
    case UpdateGUICommand::GUI_REDO_MERGESYNC:
    case UpdateGUICommand::GUI_UNDO_MERGESYNC:
    case UpdateGUICommand::GUI_REDO_IMPORT:
    case UpdateGUICommand::GUI_UNDO_IMPORT:
      // During these processes, many entries may be added/removed
      // To stop the UI going nuts, updates to the UI are suspended until
      // the action is complete - when these calls are then sent
      RebuildGUI();
      break;
#ifdef NOTYET
    case UpdateGUICommand::GUI_UPDATE_STATUSBAR:
      UpdateToolBarDoUndo();
      UpdateStatusBar();
      break;
#endif
    case UpdateGUICommand::GUI_REFRESH_ENTRYFIELD:
      ASSERT(pci != NULL);
      RefreshEntryFieldInGUI(*pci, ft);
      break;
    case UpdateGUICommand::GUI_REFRESH_ENTRYPASSWORD:
      ASSERT(pci != NULL);
      RefreshEntryPasswordInGUI(*pci);
      break;
    case UpdateGUICommand::GUI_DB_PREFERENCES_CHANGED:
    {
      wxCommandEvent evt(wxEVT_GUI_DB_PREFS_CHANGE, wxID_ANY);
      evt.ResumePropagation(wxEVENT_PROPAGATE_MAX); //let it propagate through the entire window tree
      if (m_tree) {
        m_tree->GetEventHandler()->AddPendingEvent(evt);
        evt.StopPropagation(); //or else it will come to the frame twice
      }
      if (m_grid) m_grid->GetEventHandler()->AddPendingEvent(evt);
      break;
    }
    default:
      break;
  }
}

void PasswordSafeFrame::RefreshEntryFieldInGUI(const CItemData& item, CItemData::FieldType ft)
{
  if (m_currentView == GRID) {
    m_grid->RefreshItemField(item.GetUUID(), ft);
  }
  else {
    //even though the sort order might have changed, don't change the position yet
    //as it could be too distracting, and the item may even move off the screen
    m_tree->UpdateItemField(item, ft);
  }
}

void PasswordSafeFrame::RefreshEntryPasswordInGUI(const CItemData& item)
{
  if (m_currentView == GRID) {
    RefreshEntryFieldInGUI(item, CItemData::PASSWORD);
    //TODO: Update password history
  }
  else {
    RefreshEntryFieldInGUI(item, CItemData::PASSWORD);
  }
}

void PasswordSafeFrame::GUIRefreshEntry(const CItemData& item)
{
  if (item.GetStatus() ==CItemData::ES_DELETED) {
    uuid_array_t uuid;
    item.GetUUID(uuid);
    if (m_currentView == TREE) { m_tree->Remove(uuid); }
    else { m_grid->Remove(uuid); }
  } else {
    if (m_currentView == TREE) { m_tree->UpdateItem(item); }
    else { m_grid->UpdateItem(item); }
  }
}

void PasswordSafeFrame::UpdateWizard(const stringT &)
{
  // Stub
}

/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for wxID_NEW
 */

void PasswordSafeFrame::OnNewClick( wxCommandEvent& /* evt */ )
{
  New();
}

/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for ID_MENU_CLEAR_MRU
 */

void PasswordSafeFrame::OnClearRecentHistory(wxCommandEvent& evt)
{
  UNREFERENCED_PARAMETER(evt);
  wxGetApp().recentDatabases().Clear();
}

void PasswordSafeFrame::OnUpdateClearRecentDBHistory(wxUpdateUIEvent& evt)
{
  evt.Enable(wxGetApp().recentDatabases().GetCount() > 0);
}

static void DisplayFileWriteError(int rc, const StringX &fname)
{
  ASSERT(rc != PWScore::SUCCESS);

  wxString cs_temp, cs_title(_("Write Error"));
  switch (rc) {
  case PWScore::CANT_OPEN_FILE:
    cs_temp = fname.c_str();
    cs_temp += wxT("\n\n");
    cs_temp += _("Could not open file for writing!");
    break;
  case PWScore::FAILURE:
    cs_temp =_("Write operation failed!\nFile may have been corrupted.\nTry saving in a different location");
    break;
  default:
    cs_temp = fname.c_str();
    cs_temp += wxT("\n\n");
    cs_temp += _("Unknown error");
    break;
  }
  wxMessageDialog(NULL, cs_temp, cs_title, wxOK | wxICON_ERROR);
}

void PasswordSafeFrame::Execute(Command *pcmd, PWScore *pcore /*= NULL*/)
{
  if (pcore == NULL)
    pcore = &m_core;
  pcore->Execute(pcmd);
}

int PasswordSafeFrame::New()
{
  int rc, rc2;

  if (!m_core.IsReadOnly() && m_core.IsChanged()) {
    wxString msg(_("Do you want to save changes to the password database: "));
    msg += m_core.GetCurFile().c_str();
    wxMessageDialog mbox(this, msg, GetTitle(), wxCANCEL | wxYES_NO | wxICON_QUESTION);
    rc = mbox.ShowModal();
    switch (rc) {
    case wxID_CANCEL:
      return PWScore::USER_CANCEL;
    case wxID_YES:
      rc2 = Save();
        /*
        Make sure that writing the file was successful
        */
        if (rc2 == PWScore::SUCCESS)
          break;
        else
          return PWScore::CANT_OPEN_FILE;
    case wxID_NO:
      // Reset changed flag
      SetChanged(Clear);
      break;
    default:
      ASSERT(0);
    }
  }

  StringX cs_newfile;
  rc = NewFile(cs_newfile);
  if (rc == PWScore::USER_CANCEL) {
    /*
    Everything stays as is...
    Worst case, they saved their file....
    */
    return PWScore::USER_CANCEL;
  }

  m_core.SetCurFile(cs_newfile);
  m_core.ClearFileUUID();

  rc = m_core.WriteCurFile();
  if (rc != PWScore::SUCCESS) {
    DisplayFileWriteError(rc, cs_newfile);
    return PWScore::USER_CANCEL;
  }
  m_core.ClearChangedNodes();

  SetLabel(PWSUtil::NormalizeTTT(wxT("Password Safe - ") + cs_newfile).c_str());

  m_sysTray->SetTrayStatus(SystemTray::TRAY_UNLOCKED);
  m_RUEList.ClearEntries();
  wxGetApp().recentDatabases().AddFileToHistory(towxstring(cs_newfile));
  // XXX TODO: Reset IdleLockTimer, as preference has reverted to default
  return PWScore::SUCCESS;
}


int PasswordSafeFrame::NewFile(StringX &fname)
{
  wxString cs_msg, cs_title, cs_temp;
  wxString cs_text(_("Please choose a name for the new database"));

  wxString cf(wxT("pwsafe")); // reasonable default for first time user
  wxString v3FileName = towxstring(PWSUtil::GetNewFileName(tostdstring(cf), DEFAULT_SUFFIX));
  wxString dir = towxstring(PWSdirs::GetSafeDir());
  int rc;

  while (1) {
    wxFileDialog fd(static_cast<wxWindow *>(this), cs_text, dir, v3FileName,
                    _("psafe3 files (*.psafe3)|*.psafe3|All files(*.*; *)|*.*;*"),
                    wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxFD_CHANGE_DIR);
    rc = fd.ShowModal();

    if (rc == wxID_OK) {
      fname = fd.GetPath().c_str();
      wxFileName wxfn(fname.c_str());
      if (wxfn.GetExt().empty()) {
        wxfn.SetExt(DEFAULT_SUFFIX);
        fname = wxfn.GetFullPath().c_str();
      }
      break;
    } else
      return PWScore::USER_CANCEL;
  }

  CSafeCombinationSetup dbox_pksetup(this);
  rc = dbox_pksetup.ShowModal();

  if (rc == wxID_CANCEL)
    return PWScore::USER_CANCEL;  //User cancelled password entry

  // Reset core
  m_core.ReInit(true);

  ClearData();
  PWSprefs::GetInstance()->SetDatabasePrefsToDefaults();
  const StringX &oldfilename = m_core.GetCurFile();
  // The only way we're the locker is if it's locked & we're !readonly
  if (!oldfilename.empty() &&
      !m_core.IsReadOnly() &&
      m_core.IsLockedFile(oldfilename.c_str()))
    m_core.UnlockFile(oldfilename.c_str());

  m_core.SetCurFile(fname);

  // Now lock the new file
  std::wstring locker(L""); // null init is important here
  m_core.LockFile(fname.c_str(), locker);

  m_core.SetReadOnly(false); // new file can't be read-only...
  m_core.NewFile(tostringx(dbox_pksetup.GetPassword()));
#ifdef notyet
  startLockCheckTimer();
#endif
  return PWScore::SUCCESS;
}

bool PasswordSafeFrame::SaveAndClearDatabase()
{
  //Save UI elements first
  PWSprefs::GetInstance()->SaveApplicationPreferences();
  PWSprefs::GetInstance()->SaveShortcuts();
  m_savedDBPrefs = towxstring(PWSprefs::GetInstance()->Store());

  //Save alerts the user
  if (!m_core.IsChanged() || Save() == PWScore::SUCCESS) {
    ClearData();
    return true;
  }
  return false;
}

bool PasswordSafeFrame::ReloadDatabase(const StringX& password)
{
  return Load(password) == PWScore::SUCCESS;
}

void PasswordSafeFrame::CleanupAfterReloadFailure(bool tellUser)
{
  //TODO: must clear db prefs, UI states, RUE items etc here
  if (tellUser) {
    wxMessageBox(wxString(_("Could not re-load database: ")) << towxstring(m_core.GetCurFile()),
                     _("Error re-loading last database"), wxOK|wxICON_ERROR, this);
  }
  m_sysTray->SetTrayStatus(SystemTray::TRAY_CLOSED);
}

/**
 * Unlock database
 * @param restoreUI restore opened windows after unlock
 * @param iconizeOnFailure will iconize if this parameters set to true and
 *   VerifySafeCombination() failed
*/
void PasswordSafeFrame::UnlockSafe(bool restoreUI, bool iconizeOnFailure)
{
  wxMutexTryLocker unlockMutex(m_dblockMutex);
  if (!unlockMutex.IsAcquired()){
    // Another (un)lock in progress, no need to process
    pws_os::Trace0(L"Skipped parallel attempt to unlock DB");
    return;
  }
  StringX password;
  if (m_sysTray->IsLocked()) {
    if (VerifySafeCombination(password)) {
      if (ReloadDatabase(password)) {
        m_sysTray->SetTrayStatus(SystemTray::TRAY_UNLOCKED);
      }
      else {
        CleanupAfterReloadFailure(true);
        return;
      }
    }
    else {
      if (!IsIconized() && iconizeOnFailure)
        Iconize();
      return;
    }
    if (m_savedDBPrefs != wxEmptyString) {
      const StringX savedPrefs = tostringx(m_savedDBPrefs);
      PWSprefs::GetInstance()->Load(savedPrefs);
      if (m_core.HaveHeaderPreferencesChanged(savedPrefs))
        m_core.SetDBPrefsChanged(true);
      m_savedDBPrefs = wxEmptyString;
    }
  }

  if (restoreUI) {
    if (!IsShown()) {
      ShowWindowRecursively(hiddenWindows);
    }
    if (IsIconized()) {
      Iconize(false);
    }
    Show(true); //show the grid/tree
    m_guiInfo->Restore(this);
    Raise();
    // Without this, modal dialogs like msgboxes lose focus and we end up in a different message loop than theirs.
    // See https://sourceforge.net/tracker/?func=detail&aid=3537985&group_id=41019&atid=429579
    wxSafeYield();
  }
  else if (IsShown()) { /* if it is somehow visible, show it correctly */
    Show(true);
    m_guiInfo->Restore(this);
  }
}

bool PasswordSafeFrame::VerifySafeCombination(StringX& password)
{
  CSafeCombinationPrompt scp(NULL, m_core, towxstring(m_core.GetCurFile()));
  if (scp.ShowModal() == wxID_OK) {
    password = scp.GetPassword();
    return true;
  }
  return false;
}

void PasswordSafeFrame::SetFocus()
{
  if (IsTreeView())
    m_tree->SetFocus();
  else
    m_grid->SetFocus();
}

void PasswordSafeFrame::OnIconize(wxIconizeEvent& evt) {
  const bool beingIconized =
#if wxCHECK_VERSION(2,9,0)
    evt.IsIconized();
#else
    evt.Iconized();
#endif
  pws_os::Trace(L"OnIconize: beingIconized=%d\n", beingIconized);
  // Because  LockDB and UnlockSafe hide/update main or "icon" window, they may
  // produce new iconize events before current processing finished, so to
  // prevent multiple calls we use CallAfter if available
  if (beingIconized) {
    const bool lockOnMinimize = PWSprefs::GetInstance()->GetPref(PWSprefs::DatabaseClear);
    // if not already locked, lock it if "lock on minimize" is set
    if (m_sysTray->GetTrayStatus() == SystemTray::TRAY_UNLOCKED && lockOnMinimize) {
      pws_os::Trace0(L"OnIconize: will LockDb()\n");
#if wxCHECK_VERSION(2,9,5)
      CallAfter(&PasswordSafeFrame::LockDb);
#else
      LockDb();
#endif
      if (PWSprefs::GetInstance()->GetPref(PWSprefs::ClearClipboardOnMinimize)) {
        PWSclipboard::GetInstance()->ClearData();
      }
    }
  }
  else{
#if wxCHECK_VERSION(2,9,5)
      CallAfter(&PasswordSafeFrame::UnlockSafe, true, true);
#else
      UnlockSafe(true, true);
#endif
  }
}


void PasswordSafeFrame::TryIconize(int attempts)
{
  while ( !IsIconized() && attempts-- ) {
    pws_os::Trace0(L"TryIconize attempt\n");
    //don't loop here infinitely while IsIconized
    //"The window manager may choose to ignore the [gdk_window_iconify] request, but normally will honor it."
    Iconize();
    wxSafeYield();
  }
}

void PasswordSafeFrame::HideUI(bool lock)
{
  wxMutexTryLocker hideMutex(m_hideUIMutex);
  if (!hideMutex.IsAcquired()) {
    // UI hide is in progress, no need to process
    pws_os::Trace0(L"Skipped parallel attempt to hide UI");
    return;
  }

  // As HideUI doesn't produce iconize event we need to process clear clipboard options
  if (PWSprefs::GetInstance()->GetPref(PWSprefs::ClearClipboardOnMinimize)) {
    PWSclipboard::GetInstance()->ClearData();
  }

  m_guiInfo->Save(this);
  wxGetApp().SaveFrameCoords();

  if (lock && m_sysTray->GetTrayStatus() == SystemTray::TRAY_UNLOCKED) {
    LockDb();
  }

  // Don't call (try)iconize() here, otherwise we'll have two iconization events
  // (iconize and restore few moments after) [wxgtk 3.0.2]
  // skipping wxEVT_ICONIZE while we are here doesn't help

  if (PWSprefs::GetInstance()->GetPref(PWSprefs::UseSystemTray)) {
    //We should not have to show up the icon manually if m_sysTray
    //can be notified of changes to PWSprefs::UseSystemTray
    m_sysTray->ShowIcon();
    hiddenWindows.clear();
    HideWindowRecursively(this, hiddenWindows);

  }
}

void PasswordSafeFrame::LockDb()
{
  wxMutexTryLocker lockMutex(m_dblockMutex);
  if (!lockMutex.IsAcquired()){
    // Another (un)lock in progress, no need to process
    pws_os::Trace0(L"Skipped parallel attempt to lock DB");
    return;
  }

  m_guiInfo->Save(this);
  if (SaveAndClearDatabase())
    m_sysTray->SetTrayStatus(SystemTray::TRAY_LOCKED);
}

void PasswordSafeFrame::SetTrayStatus(bool locked)
{
  m_sysTray->SetTrayStatus(locked ? SystemTray::TRAY_LOCKED : SystemTray::TRAY_UNLOCKED);
}

void PasswordSafeFrame::SetTrayClosed()
{
  m_sysTray->SetTrayStatus(SystemTray::TRAY_CLOSED);
}

void PasswordSafeFrame::ShowTrayIcon()
{
  if (m_sysTray)
    m_sysTray->ShowIcon();
}

void PasswordSafeFrame::OnOpenRecentDB(wxCommandEvent& evt)
{
  CRecentDBList& db = wxGetApp().recentDatabases();
  const size_t index = evt.GetId() - db.GetBaseId();
  const wxString dbfile = db.GetHistoryFile(index);
  switch(Open(dbfile))
  {
    case PWScore::SUCCESS:
      break;

    case PWScore::USER_CANCEL:
      //In case the file doesn't exist, user will have to cancel
      //the safe combination entry box.  In that call, fall through
      //to the default case of removing the file from history
      if (pws_os::FileExists(stringT(dbfile)))
        break;  //file exists.  don't remove it from history

      //fall through
    default:
      wxMessageBox(wxString(_("There was an error loading the database: ")) << dbfile,
                     _("Could not load database"), wxOK|wxICON_ERROR, this);
      db.RemoveFileFromHistory(index);
      break;
  }
}

//
// ---------- Import/Export ........................
//
void PasswordSafeFrame::OnImportText(wxCommandEvent& evt)
{
  UNREFERENCED_PARAMETER(evt);
  if (m_core.IsReadOnly()) {// disable in read-only mode
    wxMessageBox(_("The current database was opened in read-only mode.  You cannot import into it."),
                  _("Import text"), wxOK | wxICON_EXCLAMATION, this);
    return;
  }

  // Initialize set
  GTUSet setGTU;
  if (!m_core.GetUniqueGTUValidated() && !m_core.InitialiseGTU(setGTU)) {
    // Database is not unique to start with - tell user to validate it first
    wxMessageBox(wxString() << _("The database:") << wxT("\n\n") << m_core.GetCurFile() << wxT("\n\n")
                            << _("has duplicate entries with the same group/title/user combination.")
                            << _("  Please fix by validating database."),
                            _("Import Text failed"), wxOK | wxICON_ERROR, this);
    return;
  }

  CImportTextDlg dlg(this);
  if (dlg.ShowModal() != wxID_OK)
    return;

  StringX ImportedPrefix(dlg.groupName.c_str());
  TCHAR fieldSeparator = dlg.FieldSeparator();

  std::wstring strError;
  wxString TxtFileName = dlg.filepath;
  int numImported(0), numSkipped(0), numPWHErrors(0), numRenamed(0), numNoPolicyNames(0);
  wchar_t delimiter = dlg.strDelimiterLine[0];
  bool bImportPSWDsOnly = dlg.importPasswordsOnly;

  /* Create report as we go */
  CReport rpt;
  rpt.StartReport(_("Import_Text").c_str(), m_core.GetCurFile().c_str());
  wxString header;
  header.Printf(_("%ls file being imported: %ls"), _("Text"), TxtFileName.c_str());
  rpt.WriteLine(tostdstring(header));
  rpt.WriteLine();

  Command *pcmd = NULL;
  int rc = m_core.ImportPlaintextFile(ImportedPrefix, tostringx(TxtFileName), fieldSeparator,
                                  delimiter, bImportPSWDsOnly,
                                  strError,
                                  numImported, numSkipped, numPWHErrors, numRenamed, numNoPolicyNames,
                                  rpt, pcmd);

  wxString cs_title, cs_temp;

  switch (rc) {
    case PWScore::CANT_OPEN_FILE:
      cs_title = _("File Read Error");
      cs_temp << TxtFileName << wxT("\n\n") << _("Could not open file for reading!");
      delete pcmd;
      break;
    case PWScore::INVALID_FORMAT:
      cs_title = _("File Read Error");
      cs_temp << TxtFileName << wxT("\n\n") << _("Invalid format");
      delete pcmd;
      break;
    case PWScore::FAILURE:
      cs_title = _("Import Text failed");
      cs_temp = towxstring(strError);
      delete pcmd;
      break;
    case PWScore::SUCCESS:
    case PWScore::OK_WITH_ERRORS:
      // deliberate fallthrough
    default:
    {
      if (pcmd != NULL)
        Execute(pcmd);

      rpt.WriteLine();
      cs_temp << (bImportPSWDsOnly ? _("Updated ") : _("Imported "))
              << numImported << (numImported == 1? _(" entry") : _(" entries"));
      rpt.WriteLine(tostdstring(cs_temp));

      if (numSkipped != 0) {
        wxString cs_tmp;
        cs_tmp << wxT("\n") << _("Skipped ") << numSkipped << (numSkipped == 1? _(" entry") : _(" entries"));
        rpt.WriteLine(tostdstring(cs_tmp));
        cs_temp += cs_tmp;
      }

      if (numPWHErrors != 0) {
        wxString cs_tmp;
        cs_tmp << wxT("\n") << _("with Password History errors ") << numPWHErrors;
        rpt.WriteLine(tostdstring(cs_tmp));
        cs_temp += cs_tmp;
      }

      if (numRenamed != 0) {
        wxString cs_tmp;
        cs_tmp << wxT("\n") << _("Renamed ") << numRenamed << (numRenamed == 1? _(" entry") : _(" entries"));
        rpt.WriteLine(tostdstring(cs_tmp));
        cs_temp += cs_tmp;
      }

      cs_title = (rc == PWScore::SUCCESS ? _("Completed successfully") : _("Completed but ...."));

      RefreshViews();

      break;
    }
  } // switch

  // Finish Report
  rpt.EndReport();

  const int iconType = (rc == PWScore::SUCCESS ? wxICON_INFORMATION : wxICON_EXCLAMATION);
  cs_temp << wxT("\n\n") << _("Do you wish to see a detailed report?");
  if (wxMessageBox(cs_temp, cs_title, wxYES_NO | iconType, this) == wxYES) {
    ViewReport(rpt);
  }
}

void PasswordSafeFrame::OnImportKeePass(wxCommandEvent& evt)
{
  UNREFERENCED_PARAMETER(evt);
  if (m_core.IsReadOnly()) // disable in read-only mode
    return;

  wxFileDialog fd(this, _("Please Choose a KeePass Text File to Import"),
                  wxEmptyString, wxEmptyString,
                  _("Text files (*.txt)|*.txt|CSV files (*.csv)|*.csv|All files (*.*; *)|*.*;*"),
                  (wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_PREVIEW));

  if (fd.ShowModal() != wxID_OK )
    return;

  const wxString KPsFileName(fd.GetPath());
  CReport rpt;

  enum { KeePassCSV, KeePassTXT } ImportType = wxFileName(KPsFileName).GetExt() == wxT("csv")? KeePassCSV: KeePassTXT;

  if (ImportType == KeePassCSV)
    rpt.StartReport(_("Import_KeePassV1_CSV").c_str(), m_core.GetCurFile().c_str());
  else
    rpt.StartReport(_("Import_KeePassV1_TXT").c_str(), m_core.GetCurFile().c_str());

  rpt.WriteLine(wxString::Format(_("Text file being imported: %ls"), KPsFileName.c_str()));
  rpt.WriteLine();

  int numImported, numSkipped, numRenamed;
  unsigned int uiReasonCode = 0;
  int rc;
  Command *pcmd = NULL;

  if (ImportType == KeePassCSV)
    rc = m_core.ImportKeePassV1CSVFile(tostringx(KPsFileName), numImported, numSkipped, numRenamed,
                                       uiReasonCode, rpt, pcmd);
  else
    rc = m_core.ImportKeePassV1TXTFile(tostringx(KPsFileName), numImported, numSkipped, numRenamed,
                                       uiReasonCode, rpt, pcmd);
  switch (rc) {
    case PWScore::CANT_OPEN_FILE:
    {
      wxMessageBox( wxString::Format(_("%ls\n\nCould not open file for reading!"), KPsFileName.GetData()),
                    _("File open error"), wxOK | wxICON_ERROR, this);
      delete [] pcmd;
      break;
    }
    case PWScore::INVALID_FORMAT:
    case PWScore::FAILURE:
    {
      wxString msg;
      if (uiReasonCode > 0) {
        stringT s;
        LoadAString(s, uiReasonCode);
        msg = towxstring(s);
      }
      else
        msg = wxString::Format(_("%ls\n\nInvalid format"), KPsFileName.GetData());
      wxMessageBox(msg, _("Import failed"), wxOK | wxICON_ERROR, this);
      delete [] pcmd;
      break;
    }
    case PWScore::SUCCESS:
    default: // deliberate fallthrough
      if (pcmd != NULL)
        Execute(pcmd);
      RefreshViews();
#ifdef NOT_YET
      ChangeOkUpdate();
#endif
      rpt.WriteLine();
      wxString cs_type(numImported == 1 ? _("entry") : _("entries"));
      wxString cs_msg = wxString::Format(_("Imported %d %ls"), numImported, cs_type.GetData());
      rpt.WriteLine(static_cast<const TCHAR*>(cs_msg.c_str()));
      rpt.EndReport();
      wxString title(rc == PWScore::SUCCESS ? _("Completed successfully") : _("Completed but ...."));
      int icon = (rc == PWScore::SUCCESS ? wxICON_INFORMATION : wxICON_EXCLAMATION);
      cs_msg << wxT("\n\n") << _("Do you wish to see a detailed report?");
      if (wxMessageBox(cs_msg, title, icon|wxYES_NO, this) == wxYES)
        ViewReport(rpt);
      break;
  } // switch
}

void PasswordSafeFrame::OnImportXML(wxCommandEvent& evt)
{
  UNREFERENCED_PARAMETER(evt);
  if (m_core.IsReadOnly()) // disable in read-only mode
    return;

  // Initialize set
  GTUSet setGTU;
  if (!m_core.GetUniqueGTUValidated() && !m_core.InitialiseGTU(setGTU)) {
    // Database is not unique to start with - tell user to validate it first
    wxMessageBox(wxString::Format( _("The database:\n\n%ls\n\nhas duplicate entries with the same group/title/user combination. Please fix by validating database."),
                                    m_core.GetCurFile().c_str()), _("Import XML failed"), wxOK | wxICON_ERROR, this);
    return;
  }

  TCHAR XSDfn[] = wxT("pwsafe.xsd");
  wxFileName XSDFilename(towxstring(PWSdirs::GetXMLDir()), XSDfn);

#if USE_XML_LIBRARY == MSXML || USE_XML_LIBRARY == XERCES
  if (!XSDFilename.FileExists()) {
    wxString filepath(XSDFilename.GetFullPath());
    wxMessageBox(wxString::Format(_("Can't find XML Schema Definition file (%ls) in your PasswordSafe Application Directory.\nPlease copy it from your installation file, or re-install PasswordSafe."), filepath.c_str()),
                          wxString(_("Missing XSD File - ")) + wxSTRINGIZE_T(USE_XML_LIBRARY) + _(" Build"), wxOK | wxICON_ERROR, this);
    return;
  }
#endif

  CImportXMLDlg dlg(this);
  if (dlg.ShowModal() != wxID_OK)
    return;

  std::wstring ImportedPrefix(tostdstring(dlg.groupName));
  std::wstring strXMLErrors, strSkippedList, strPWHErrorList, strRenameList;
  wxString XMLFilename = dlg.filepath;
  int numValidated, numImported, numSkipped, numRenamed, numPWHErrors;
  int numRenamedPolicies, numNoPolicy;
  int numShortcutsRemoved;
  bool bImportPSWDsOnly = dlg.importPasswordsOnly;

  wxBeginBusyCursor();  // This may take a while!

  /* Create report as we go */
  CReport rpt;
  rpt.StartReport(_("Import_XML").c_str(), m_core.GetCurFile().c_str());
  rpt.WriteLine(tostdstring(wxString::Format(_("%ls file being imported: %ls"), _("XML"), XMLFilename.c_str())));
  rpt.WriteLine();
  std::vector<StringX> vgroups;
  Command *pcmd = NULL;

  int rc = m_core.ImportXMLFile(ImportedPrefix, std::wstring(XMLFilename),
                            tostdstring(XSDFilename.GetFullPath()), bImportPSWDsOnly,
                            strXMLErrors, strSkippedList, strPWHErrorList, strRenameList,
                            numValidated, numImported, numSkipped, numPWHErrors, numRenamed,
                            numNoPolicy, numRenamedPolicies, numShortcutsRemoved,
                            rpt, pcmd);
  wxEndBusyCursor();  // Restore normal cursor

  wxString cs_temp;
  wxString cs_title(_("Import XML failed"));

  std::wstring csErrors(wxEmptyString);
  switch (rc) {
    case PWScore::XML_FAILED_VALIDATION:
      rpt.WriteLine(strXMLErrors.c_str());
      cs_temp = wxString::Format(_("File: %ls failed validation against XML Schema:\n\n%ls"),
                                        dlg.filepath.c_str(), wxEmptyString);
      delete pcmd;
      break;
    case PWScore::XML_FAILED_IMPORT:
      rpt.WriteLine(strXMLErrors.c_str());
      cs_temp = wxString::Format(_("File: %ls passed Validation but had the following errors during import:\n\n%ls"),
                              dlg.filepath.c_str(), wxEmptyString);
      delete pcmd;
      break;
    case PWScore::SUCCESS:
    case PWScore::OK_WITH_ERRORS:
      cs_title = rc == PWScore::SUCCESS ? _("Completed successfully") :  _("Completed but ....");
      if (pcmd != NULL)
        Execute(pcmd);

      if (!strXMLErrors.empty() ||
          numRenamed > 0 || numPWHErrors > 0) {
        if (!strXMLErrors.empty())
          csErrors = strXMLErrors + wxT("\n");

        if (!csErrors.empty()) {
          rpt.WriteLine(csErrors.c_str());
        }

        wxString cs_renamed, cs_PWHErrors, cs_skipped;
        if (numSkipped > 0) {
          cs_skipped = _("The following records were skipped:");
          rpt.WriteLine(tostdstring(cs_skipped));
          cs_skipped.Printf(_(" / skipped %d"), numSkipped);
          rpt.WriteLine(strSkippedList.c_str());
          rpt.WriteLine();
        }
        if (numPWHErrors > 0) {
          cs_PWHErrors = _("The following records had errors in their Password History:");
          rpt.WriteLine(tostdstring(cs_PWHErrors));
          cs_PWHErrors.Printf(_(" / with Password History errors %d"), numPWHErrors);
          rpt.WriteLine(strPWHErrorList.c_str());
          rpt.WriteLine();
        }
        if (numRenamed > 0) {
          cs_renamed = _("The following records were renamed as an entry already exists in your database or in the Import file:");
          rpt.WriteLine(tostdstring(cs_renamed));
          cs_renamed.Printf(_(" / renamed %d"), numRenamed);
          rpt.WriteLine(strRenameList.c_str());
          rpt.WriteLine();
        }

        cs_temp.Printf(_("File: %ls was imported (entries validated %d / imported %d%ls%ls%ls). See report for details."),
                       dlg.filepath.c_str(), numValidated, numImported,
                       cs_skipped.c_str(), cs_renamed.c_str(), cs_PWHErrors.c_str());

      } else {
        const TCHAR* cs_validate = numValidated == 1 ? _("entry").c_str() : _("entries").c_str();
        const TCHAR* cs_imported = numImported == 1 ? _("entry").c_str() : _("entries").c_str();
        cs_temp.Printf(_("Validated %d %ls\n\nImported %d %ls"), numValidated, cs_validate, numImported, cs_imported);
      }

      RefreshViews();
      break;
    case PWScore::UNIMPLEMENTED:
      cs_temp = _("XML import not supported in this release");
      break;
    default:
      cs_temp.Printf(_("XML import: Unexpected return code(%d)"), rc);
      break;
  } // switch

  // Finish Report
  rpt.WriteLine(tostdstring(cs_temp));
  rpt.EndReport();

  const int iconType = (rc != PWScore::SUCCESS || !strXMLErrors.empty()) ? wxICON_EXCLAMATION : wxICON_INFORMATION;

  cs_temp << wxT("\n\n") << _("Do you wish to see a detailed report?");
  if ( wxMessageBox(cs_temp, cs_title, wxYES_NO | iconType, this) == wxYES) {
    ViewReport(rpt);
  }
}

void PasswordSafeFrame::ViewReport(CReport& rpt)
{
  CViewReport vr(this, &rpt);
  vr.ShowModal();
}

void PasswordSafeFrame::OnExportVx(wxCommandEvent& evt)
{
  int rc = PWScore::FAILURE;
  StringX newfile;
  wxString cs_fmt;
  PWSfile::VERSION ver = PWSfile::UNKNOWN_VERSION;
  stringT sfx = wxEmptyString;

  switch (evt.GetId()) {
    case ID_EXPORT2OLD1XFORMAT:
      ver =  PWSfile::V17; sfx = L"dat";
      cs_fmt = _("Password Safe Databases (*.dat)|*.dat|All files (*.*; *)|*.*;*");
      break;
    case ID_EXPORT2V2FORMAT:
      ver =  PWSfile::V20; sfx = L"dat";
      cs_fmt = _("Password Safe Databases (*.dat)|*.dat|All files (*.*; *)|*.*;*");
      break;
    case ID_EXPORT2V4FORMAT:
      ver =  PWSfile::V40; sfx = L"psafe4";
      cs_fmt =_("Password Safe Databases (*.psafe4)|*.psafe4|All files (*.*; *)|*.*;*");
      break;
    default:
      ver = PWSfile::UNKNOWN_VERSION; // internal error
      break;
  }

  if (ver != PWSfile::UNKNOWN_VERSION) {
    //SaveAs-type dialog box
    std::wstring OldFormatFileName = PWSUtil::GetNewFileName(m_core.GetCurFile().c_str(),
                                                             sfx);
    const wxString cs_text = _("Please name the exported database");

    //filename cannot have the path. Need to pass it separately
    wxFileName filename(towxstring(OldFormatFileName));
    wxString dir = filename.GetPath();
    if (dir.empty())
      dir = towxstring(PWSdirs::GetSafeDir());


    wxFileDialog fd(this, cs_text, dir, filename.GetFullName(), cs_fmt,
                    wxFD_SAVE | wxFD_OVERWRITE_PROMPT);

    if (fd.ShowModal() != wxID_OK)
      return;

    newfile = tostringx(fd.GetPath());
    rc = m_core.WriteFile(newfile, ver);
  } else { // internal error
    wxFAIL_MSG(_("Could not figure out why PasswordSafeFrame::OnExportVx was invoked"));
  }

  if (rc != PWScore::SUCCESS) {
    DisplayFileWriteError(rc, newfile);
  }
}

struct ExportFullText
{
  static wxString GetTitle() {return _("Export Text");}
  static void MakeOrderedItemList(PasswordSafeFrame* frame, OrderedItemList& olist) {
    frame->FlattenTree(olist);
  }
  static wxString GetFailureMsgTitle() {return _("Export Text failed"); }
  static stringT  FileExtension() { return wxT("txt"); }
  static wxString FileOpenPrompt() { return _("Please name the plaintext file"); }
  static wxString WildCards() {return _("Text files (*.txt)|*.txt|CSV files (*.csv)|*.csv|All files (*.*; *)|*.*;*"); }
  static int Write(PWScore& core, const StringX &filename, const CItemData::FieldBits &bsFields,
                          const stringT &subgroup_name, int subgroup_object,
                          int subgroup_function, TCHAR delimiter, int &numExported,
                          const OrderedItemList *il, CReport *rpt)
  {
    return core.WritePlaintextFile(filename, bsFields, subgroup_name, subgroup_object, subgroup_function,
                          delimiter, numExported, il, rpt);
  }
  static wxString GetAdvancedSelectionTitle() {
    return _("Advanced Text Export Options");
  }

  static bool IsMandatoryField(CItemData::FieldType /*field*/) {
    return false;
  }

  static bool IsPreselectedField(CItemData::FieldType /*field*/) {
    return true;
  }

  static bool IsUsableField(CItemData::FieldType /*field*/) {
    return true;
  }

  static bool ShowFieldSelection() {
    return true;
  }

  static wxString GetTaskWord() {
    return _("export");
  }
};

void PasswordSafeFrame::OnExportPlainText(wxCommandEvent& evt)
{
  UNREFERENCED_PARAMETER(evt);
  DoExportText<ExportFullText>();
}

struct ExportFullXml {
  static wxString GetTitle() {return _("Export XML");}
  static void MakeOrderedItemList(PasswordSafeFrame* frame, OrderedItemList& olist) {
    frame->FlattenTree(olist);
  }
  static wxString GetFailureMsgTitle() {return _("Export XML failed"); }
  static stringT  FileExtension() { return wxT("xml"); }
  static wxString FileOpenPrompt() { return _("Please name the XML file"); }
  static wxString WildCards() {return _("XML files (*.xml)|*.xml|All files (*.*; *)|*.*;*"); }
  static int Write(PWScore& core, const StringX &filename, const CItemData::FieldBits &bsFields,
                          const stringT &subgroup_name, int subgroup_object,
                          int subgroup_function, TCHAR delimiter, int &numExported,
                          const OrderedItemList *il, CReport *rpt)
  {
    bool bFilterActive = false;
    return core.WriteXMLFile(filename, bsFields, subgroup_name, subgroup_object, subgroup_function,
                          delimiter, numExported, il, bFilterActive, rpt);
  }
  static wxString GetAdvancedSelectionTitle() {
    return _("Advanced XML Export Options");
  }

  static bool IsMandatoryField(CItemData::FieldType field) {
    return field == CItemData::TITLE || field == CItemData::PASSWORD;
  }

  static bool IsPreselectedField(CItemData::FieldType /*field*/) {
    return true;
  }

  static bool IsUsableField(CItemData::FieldType /*field*/) {
    return true;
  }

  static bool ShowFieldSelection() {
    return true;
  }
  static wxString GetTaskWord() {
    return _("export");
  }
};

void PasswordSafeFrame::OnExportXml(wxCommandEvent& evt)
{
  UNREFERENCED_PARAMETER(evt);
  DoExportText<ExportFullXml>();
}

IMPLEMENT_CLASS_TEMPLATE( AdvancedSelectionDlg, wxDialog, ExportFullXml )
IMPLEMENT_CLASS_TEMPLATE( AdvancedSelectionDlg, wxDialog, ExportFullText )

template <class ExportType>
void PasswordSafeFrame::DoExportText()
{
  const wxString title(ExportType::GetTitle());

  const StringX sx_temp(m_core.GetCurFile());

  //MFC code doesn't do this for XML export, but it probably should, because it tries
  //to use core.GetcurFile() later
  if (sx_temp.empty()) {
    //  Database has not been saved - prompt user to do so first!
    wxMessageBox(_T("You must save this database before it can be exported."), title, wxOK|wxICON_EXCLAMATION, this);
    return;
  }

  CExportTextWarningDlg<ExportType> et(this);
  if (et.ShowModal() != wxID_OK)
    return;

  StringX newfile;
  StringX pw(et.passKey);
  if (m_core.CheckPasskey(sx_temp, pw) == PWScore::SUCCESS) {
    const CItemData::FieldBits bsExport = et.selCriteria->GetSelectedFields();
    const std::wstring subgroup_name = tostdstring(et.selCriteria->SubgroupSearchText());
    const int subgroup_object = et.selCriteria->SubgroupObject();
    const int subgroup_function = et.selCriteria->SubgroupFunctionWithCase();
    wchar_t delimiter = et.delimiter.IsEmpty()? wxT('\xbb') : et.delimiter[0];

    // Note: MakeOrderedItemList gets its members by walking the
    // tree therefore, if a filter is active, it will ONLY export
    // those being displayed.
    OrderedItemList orderedItemList;
    ExportType::MakeOrderedItemList(this, orderedItemList);

    /*
     * First parameter indicates whether or not the user has specified
     * 'Advanced' to filter the entries to be exported.
     * Effectively, subgroup_* parameters are ignored if 1st param is false.
     */
    int numExported(0);
    switch(m_core.TestSelection(false, subgroup_name, subgroup_object,
                                subgroup_function, &orderedItemList)) {
      case PWScore::SUCCESS:
      {
        // do the export
        // SaveAs-type dialog box
        wxFileName TxtFileName(towxstring(PWSUtil::GetNewFileName(sx_temp.c_str(), ExportType::FileExtension())));

        wxFileDialog fd(this, ExportType::FileOpenPrompt(), TxtFileName.GetPath(),
                        TxtFileName.GetFullName(), ExportType::WildCards(),
                        wxFD_SAVE | wxFD_OVERWRITE_PROMPT);

        if (fd.ShowModal() == wxID_OK) {
          newfile = fd.GetPath().c_str();
          CReport rpt;

          rpt.StartReport(ExportType::GetTitle().c_str(), sx_temp.c_str());
          rpt.WriteLine(tostdstring(wxString(_("Exporting database: ")) << towxstring(sx_temp) << wxT(" to ") << newfile<< wxT("\r\n")));

          int rc = ExportType::Write(m_core, newfile, bsExport, subgroup_name, subgroup_object,
                                      subgroup_function, delimiter, numExported, &orderedItemList, &rpt);

          rpt.EndReport();

          orderedItemList.clear(); // cleanup soonest

          if (rc != PWScore::SUCCESS) {
            DisplayFileWriteError(rc, newfile);
          }
          else {
            if ( wxMessageBox(_T("Export complete.  Do you wish to see a detailed report?"), ExportType::GetTitle(),
                              wxYES_NO|wxICON_QUESTION, this) == wxYES )
              ViewReport(rpt);
          }

        }
        break;
      }

      case PWScore::NO_ENTRIES_EXPORTED:
      {
        wxMessageBox(_("No entries satisfied your selection criteria and so none were exported!"),
                      ExportType::GetFailureMsgTitle(), wxOK | wxICON_WARNING, this);
        break;
      }

      default:
        break;

    } //switch

    orderedItemList.clear(); // cleanup soonest

  } else {
    wxMessageBox(_("Passkey incorrect"), title, wxOK|wxICON_ERROR, this);
    pws_os::sleep_ms(3000); // against automatic attacks
  }
}

//
// ----------  Merge, Synchronize and Compare ------------------
//
void PasswordSafeFrame::OnMergeAnotherSafe(wxCommandEvent& evt)
{
  UNREFERENCED_PARAMETER(evt);
  MergeDlg dlg(this, &m_core);
  if (dlg.ShowModal() == wxID_OK) {
    PWScore othercore; // NOT PWSAuxCore, as we handle db prefs explicitly
    // Reading a new file changes the preferences as they are instance dependent
    // not core dependent
    PWSprefs *prefs =  PWSprefs::GetInstance();

    const StringX sxSavePrefString(prefs->Store());
    const bool bSaveIfDBPrefsChanged = prefs->IsDBprefsChanged();

    // Save all the 'other core' preferences in the copy - to use for
    // 'other' default Password Policy when needed in Compare, Merge & Sync
    prefs->SetupCopyPrefs();

    int rc = ReadCore(othercore, dlg.GetOtherSafePath(),
                      dlg.GetOtherSafeCombination(), true, this);

    // Reset database preferences - first to defaults then add saved changes!
    prefs->Load(sxSavePrefString);
    prefs->SetDBprefsChanged(bSaveIfDBPrefsChanged);

    if (rc == PWScore::SUCCESS) {
        Merge(tostringx(dlg.GetOtherSafePath()), &othercore, dlg.GetSelectionCriteria());
    }
  }
}

void PasswordSafeFrame::Merge(const StringX &sx_Filename2, PWScore *pothercore, const SelectionCriteria& selection)
{
  /* Put up hourglass...this might take a while */
  ::wxBeginBusyCursor();

  /* Create report as we go */
  CReport rpt;

  rpt.StartReport(_("Merge").c_str(), m_core.GetCurFile().c_str());
  rpt.WriteLine(tostdstring(wxString(_("Merging database: ")) << towxstring(sx_Filename2) << wxT("\r\n")));

  stringT result = m_core.Merge(pothercore,
                                selection.HasSubgroupRestriction(),
                                tostdstring(selection.SubgroupSearchText()),
                                selection.SubgroupObject(),
                                selection.SubgroupFunction(),
                                &rpt);

  ::wxEndBusyCursor();

  rpt.EndReport();

  if (!result.empty() && wxMessageBox(towxstring(result) + wxT("\n\n") +
                                      _("Do you wish to see a detailed report?"),
                                      _("Merge Complete"), wxYES_NO|wxICON_QUESTION, this) == wxYES) {
    ViewReport(rpt);
  }
}


void PasswordSafeFrame::OnSynchronize(wxCommandEvent& /*evt*/)
{
  // disable in read-only mode or empty
  wxCHECK_RET(!m_core.IsReadOnly() && !m_core.GetCurFile().empty() && m_core.GetNumEntries() != 0,
                wxT("Synchronize menu enabled for empty or read-only database!"));

  PwsSyncWizard wiz(this, &m_core);
  wiz.RunWizard(wiz.GetFirstPage());

  if (wiz.GetNumUpdated() > 0)
    SetChanged(Data);

#ifdef NOT_YET
  ChangeOkUpdate();
#endif

  RefreshViews();

  if (wiz.ShowReport())
    ViewReport(*wiz.GetReport());
}

void PasswordSafeFrame::OnCompare(wxCommandEvent& /*evt*/)
{
  CompareDlg dlg(this, &m_core);
  dlg.ShowModal();
}

void PasswordSafeFrame::OnVisitWebsite(wxCommandEvent&)
{
  wxLaunchDefaultBrowser("https://pwsafe.org");
}

void PasswordSafeFrame::UpdateStatusBar()
{

  if (!m_core.GetCurFile().empty()) {
    wxString text;
    // SB_DBLCLICK pane is set per selected entry, not here

    //    m_statusBar->SetStatusText(m_lastclipboardaction, CPWStatusBar::SB_CLIPBOARDACTION);

    text = m_core.IsChanged() ? wxT("*") : wxT(" ");
    m_statusBar->SetStatusText(text, CPWStatusBar::SB_MODIFIED);

    text = m_core.IsReadOnly() ? wxT("R-O") : wxT("R/W");
    m_statusBar->SetStatusText(text, CPWStatusBar::SB_READONLY);

    text.Clear(); text <<  m_core.GetNumEntries();
    m_statusBar->SetStatusText(text, CPWStatusBar::SB_NUM_ENT);

    text = m_bFilterActive ? wxT("[F]") : wxT("   ");
    m_statusBar->SetStatusText(text, CPWStatusBar::SB_FILTER);
  } else { // no open file
    m_statusBar->SetStatusText(PWSprefs::GetDCAdescription(-1), CPWStatusBar::SB_DBLCLICK);
    m_statusBar->SetStatusText(wxEmptyString, CPWStatusBar::SB_CLIPBOARDACTION);
    m_statusBar->SetStatusText(wxEmptyString, CPWStatusBar::SB_MODIFIED);
    m_statusBar->SetStatusText(wxEmptyString, CPWStatusBar::SB_READONLY);
    m_statusBar->SetStatusText(wxEmptyString, CPWStatusBar::SB_NUM_ENT);
    m_statusBar->SetStatusText(wxEmptyString, CPWStatusBar::SB_FILTER);
  }
}

void PasswordSafeFrame::UpdateSelChanged(const CItemData *pci)
{
  int16 dca = -1;

  if (pci != NULL) {
    pci->GetDCA(dca);
    if (dca == -1)
      dca = PWSprefs::GetInstance()->GetPref(PWSprefs::DoubleClickAction);
  }
  m_statusBar->SetStatusText(PWSprefs::GetDCAdescription(dca), CPWStatusBar::SB_DBLCLICK);
}

//-----------------------------------------------------------------
// Remove all DialogBlock-generated stubs below this line, as we
// already have them implemented in main*.cpp
// (how to get DB to stop generating them??)
//-----------------------------------------------------------------


