/*
 * Copyright (c) 2003-2025 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>

#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif

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

#include <wx/clipbrd.h>
#include <wx/filename.h>
#include <wx/fontdlg.h>
#include <wx/numformatter.h>

#include "core/core.h"
#include "core/PWScore.h"
#include "core/PWSdirs.h"
#include "core/PWSprefs.h"
#include "core/XML/XMLDefs.h"  // Required if testing "USE_XML_LIBRARY"
#include "os/file.h"
#include "os/sleep.h"

#include "graphics/cpane.xpm"

#include "AboutDlg.h"
#include "Clipboard.h"
#include "DragBarCtrl.h"
#include "GridCtrl.h"
#include "GridShortcutsValidator.h"
#include "GridTable.h"
#include "GuiInfo.h"
#include "PasswordSafeFrame.h"
#include "PasswordSafeSearch.h"
#include "PWSafeApp.h"
#include "QRCodeDlg.h"
#include "SafeCombinationPromptDlg.h"
#include "SetDatabaseIdDlg.h"
#include "StatusBar.h"
#include "SystemTray.h"
#include "SystemTrayMenuId.h"
#include "ToolbarButtons.h"
#include "TreeCtrl.h"
#include "ViewReportDlg.h"
#include "wxUtilities.h"
#include "DnDFile.h"
#include "core/Report.h"

#include <algorithm>
#include <math.h>

////@begin XPM images
////@end XPM images

using pws_os::CUUID;

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

/*!
 * PasswordSafeFrame type definition
 */

IMPLEMENT_CLASS( PasswordSafeFrame, wxFrame )

DEFINE_EVENT_TYPE(wxEVT_GUI_DB_PREFS_CHANGE)

/*!
 * PasswordSafeFrame event table definition
 */

BEGIN_EVENT_TABLE( PasswordSafeFrame, wxFrame )

  EVT_CHAR_HOOK(                        PasswordSafeFrame::OnChar                        )
  EVT_CLOSE(                            PasswordSafeFrame::OnCloseWindow                 )
  EVT_ICONIZE(                          PasswordSafeFrame::OnIconize                     )

  ////////////////////////////////////////////////////////////////////////////////////////
  // Menu: "File"
  ////////////////////////////////////////////////////////////////////////////////////////

  // Connect event handlers
  EVT_MENU( wxID_NEW,                   PasswordSafeFrame::OnNewClick                    )
  EVT_MENU( wxID_OPEN,                  PasswordSafeFrame::OnOpenClick                   )
  EVT_MENU( wxID_CLOSE,                 PasswordSafeFrame::OnCloseClick                  )
  EVT_MENU( ID_LOCK_SAFE,               PasswordSafeFrame::OnLockSafe                    )
  EVT_MENU( ID_UNLOCK_SAFE,             PasswordSafeFrame::OnUnlockSafe                  )
  EVT_MENU( ID_MENU_CLEAR_MRU,          PasswordSafeFrame::OnClearRecentHistory          )
  EVT_MENU( wxID_SAVE,                  PasswordSafeFrame::OnSaveClick                   )
  EVT_MENU( wxID_SAVEAS,                PasswordSafeFrame::OnSaveAsClick                 )

  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( ID_IMPORT_PLAINTEXT,        PasswordSafeFrame::OnImportText                  )
  EVT_MENU( ID_IMPORT_XML,              PasswordSafeFrame::OnImportXML                   )
  EVT_MENU( ID_IMPORT_KEEPASS,          PasswordSafeFrame::OnImportKeePass               )

  EVT_MENU( ID_MERGE,                   PasswordSafeFrame::OnMergeAnotherSafe            )
  EVT_MENU( ID_COMPARE,                 PasswordSafeFrame::OnCompare                     )
  EVT_MENU( ID_SYNCHRONIZE,             PasswordSafeFrame::OnSynchronize                 )
  EVT_MENU( ID_CHANGEMODE,              PasswordSafeFrame::OnChangeMode                  )
  EVT_MENU( wxID_PROPERTIES,            PasswordSafeFrame::OnPropertiesClick             )
  EVT_MENU( wxID_EXIT,                  PasswordSafeFrame::OnExitClick                   )

  // Update of menu items
  EVT_UPDATE_UI( wxID_NEW,              PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( wxID_OPEN,             PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( wxID_CLOSE,            PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_LOCK_SAFE,          PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_UNLOCK_SAFE,        PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_MENU_CLEAR_MRU,     PasswordSafeFrame::OnUpdateClearRecentDBHistory  )
  EVT_UPDATE_UI( wxID_SAVE,             PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( wxID_SAVEAS,           PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_EXPORTMENU,         PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_IMPORTMENU,         PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_IMPORT_XML,         PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_MERGE,              PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_COMPARE,            PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_SYNCHRONIZE,        PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_CHANGEMODE,         PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( wxID_PROPERTIES,       PasswordSafeFrame::OnUpdateUI                    )
  
  ////////////////////////////////////////////////////////////////////////////////////////
  // Menu: "Edit"
  ////////////////////////////////////////////////////////////////////////////////////////
  
  // Connect event handlers
  EVT_MENU( wxID_ADD,                   PasswordSafeFrame::OnAddClick                    )
  EVT_MENU( ID_EDIT,                    PasswordSafeFrame::OnEditClick                   )
  EVT_MENU( wxID_DELETE,                PasswordSafeFrame::OnDeleteClick                 )
  EVT_MENU( wxID_FIND,                  PasswordSafeFrame::OnFindClick                   )
  EVT_MENU( ID_DUPLICATEENTRY,          PasswordSafeFrame::OnDuplicateEntry              )
  EVT_MENU( ID_PROTECT,                 PasswordSafeFrame::OnProtectUnprotectClick       )

  EVT_MENU( wxID_UNDO,                  PasswordSafeFrame::OnUndo                        )
  EVT_MENU( wxID_REDO,                  PasswordSafeFrame::OnRedo                        )
  EVT_MENU( ID_CLEARCLIPBOARD,          PasswordSafeFrame::OnClearClipboardClick         )
  EVT_MENU( ID_COPYPASSWORD,            PasswordSafeFrame::OnCopyPasswordClick           )
  EVT_MENU( ID_COPYUSERNAME,            PasswordSafeFrame::OnCopyUsernameClick           )
  EVT_MENU( ID_COPYAUTHCODE,            PasswordSafeFrame::OnCopyAuthCodeClick           )
  EVT_MENU( ID_SHOWAUTHCODE,            PasswordSafeFrame::OnShowAuthCodeClick           )
  EVT_MENU( ID_COPYNOTESFLD,            PasswordSafeFrame::OnCopyNotesFieldClick         )
  EVT_MENU( ID_COPYURL,                 PasswordSafeFrame::OnCopyUrlClick                )
  EVT_MENU( ID_BROWSEURL,               PasswordSafeFrame::OnBrowseUrl                   )
  EVT_MENU( ID_AUTOTYPE,                PasswordSafeFrame::OnAutoType                    )
  EVT_MENU( ID_VIEWATTACHMENT,          PasswordSafeFrame::OnViewAttachment              )
  EVT_MENU( ID_GOTOBASEENTRY,           PasswordSafeFrame::OnGotoBase                    )

  // Update menu items
  EVT_UPDATE_UI( wxID_ADD,              PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_EDIT,               PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( wxID_DELETE,           PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( wxID_FIND,             PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_RENAME,             PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_DUPLICATEENTRY,     PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_PROTECT,            PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_ADDGROUP,           PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( wxID_UNDO,             PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( wxID_REDO,             PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_CLEARCLIPBOARD,     PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_COPYPASSWORD,       PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_COPYUSERNAME,       PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_COPYAUTHCODE,       PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_SHOWAUTHCODE,       PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_COPYNOTESFLD,       PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_COPYURL,            PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_BROWSEURL,          PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_AUTOTYPE,           PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_VIEWATTACHMENT,     PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_GOTOBASEENTRY,      PasswordSafeFrame::OnUpdateUI                    )

  ////////////////////////////////////////////////////////////////////////////////////////
  // Menu: "View"
  ////////////////////////////////////////////////////////////////////////////////////////

  // Connect event handlers
  EVT_MENU( ID_LIST_VIEW,               PasswordSafeFrame::OnListViewClick               )
  EVT_MENU( ID_TREE_VIEW,               PasswordSafeFrame::OnTreeViewClick               )
  EVT_MENU( ID_SORT_TREE_BY_GROUP,      PasswordSafeFrame::OnSortByGroupClick            )
  EVT_MENU( ID_SORT_TREE_BY_NAME,       PasswordSafeFrame::OnSortByNameClick             )
  EVT_MENU( ID_SORT_TREE_BY_DATE,       PasswordSafeFrame::OnSortByDateClick             )
  EVT_MENU( ID_SHOWHIDE_TOOLBAR,        PasswordSafeFrame::OnShowHideToolBar             )
  EVT_MENU( ID_TOOLBAR_NEW,             PasswordSafeFrame::OnChangeToolbarType           )
  EVT_MENU( ID_TOOLBAR_CLASSIC,         PasswordSafeFrame::OnChangeToolbarType           )
  EVT_MENU( ID_SHOWHIDE_DRAGBAR,        PasswordSafeFrame::OnShowHideDragBar             )
  EVT_MENU( ID_EXPANDALL,               PasswordSafeFrame::OnExpandAll                   )
  EVT_MENU( ID_COLLAPSEALL,             PasswordSafeFrame::OnCollapseAll                 )
  EVT_MENU( ID_SHOWHIDE_UNSAVED,        PasswordSafeFrame::OnShowUnsavedEntriesClick     )
  EVT_MENU( ID_SHOW_ALL_EXPIRY,         PasswordSafeFrame::OnShowAllExpiryClick          )
  EVT_MENU( ID_SHOW_LAST_FIND_RESULTS,  PasswordSafeFrame::OnShowLastFindClick           )
  EVT_MENU( ID_EDITFILTER,              PasswordSafeFrame::OnEditFilter                  )
  EVT_MENU( ID_APPLYFILTER,             PasswordSafeFrame::OnApplyFilter                 )
  EVT_MENU( ID_MANAGEFILTERS,           PasswordSafeFrame::OnManageFilters               )
  EVT_MENU( ID_SHOW_EMPTY_GROUP_IN_FILTER, PasswordSafeFrame::OnShowGroupInFilterClick   )
  EVT_MENU( ID_CHANGETREEFONT,          PasswordSafeFrame::OnChangeTreeFont              )
  EVT_MENU( ID_CHANGEADDEDITFONT,       PasswordSafeFrame::OnChangeAddEditFont           )
  EVT_MENU( ID_CHANGEPSWDFONT,          PasswordSafeFrame::OnChangePasswordFont          )
  EVT_MENU( ID_CHANGENOTESFONT,         PasswordSafeFrame::OnChangeNotesFont             )
  EVT_MENU( ID_CHANGEVKBFONT,           PasswordSafeFrame::OnChangeVirtualKeyboardFont   )
  EVT_MENU( ID_REPORT_COMPARE,          PasswordSafeFrame::OnShowReportCompare           )
  EVT_MENU( ID_REPORT_SYNCHRONIZE,      PasswordSafeFrame::OnShowReportSynchronize       )
  EVT_MENU( ID_REPORT_MERGE,            PasswordSafeFrame::OnShowReportMerge             )
  EVT_MENU( ID_REPORT_IMPORTTEXT,       PasswordSafeFrame::OnShowReportImportText        )
  EVT_MENU( ID_REPORT_IMPORTXML,        PasswordSafeFrame::OnShowReportImportXML         )
  EVT_MENU( ID_REPORT_IMPORTKEEPASS_TXT, PasswordSafeFrame::OnShowReportImportKeePassV1_TXT )
  EVT_MENU( ID_REPORT_IMPORTKEEPASS_CSV, PasswordSafeFrame::OnShowReportImportKeePassV1_CSV )
  EVT_MENU( ID_REPORT_EXPORTTEXT,       PasswordSafeFrame::OnShowReportExportText        )
  EVT_MENU( ID_REPORT_EXPORTXML,        PasswordSafeFrame::OnShowReportExportXML         )
  EVT_MENU( ID_REPORT_EXPORT_DB,        PasswordSafeFrame::OnShowReportExportDB          )
  EVT_MENU( ID_REPORT_FIND,             PasswordSafeFrame::OnShowReportFind              )
  EVT_MENU( ID_REPORT_VALIDATE,         PasswordSafeFrame::OnShowReportValidate          )
  // Update menu items
  EVT_UPDATE_UI( ID_LIST_VIEW,          PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_TREE_VIEW,          PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_SORT_TREE_MENU,     PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_SORT_TREE_BY_GROUP, PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_SORT_TREE_BY_NAME,  PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_SORT_TREE_BY_DATE,  PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_SHOWHIDE_TOOLBAR,   PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_SHOWHIDE_DRAGBAR,   PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_SUBVIEWSMENU,       PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_SHOWHIDE_UNSAVED,   PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_SHOW_ALL_EXPIRY,    PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_SHOW_LAST_FIND_RESULTS, PasswordSafeFrame::OnUpdateUI                )
  EVT_UPDATE_UI( ID_COLLAPSEALL,        PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_EXPANDALL,          PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_FILTERMENU,         PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_EDITFILTER,         PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_APPLYFILTER,        PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_MANAGEFILTERS,      PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_SHOW_EMPTY_GROUP_IN_FILTER, PasswordSafeFrame::OnUpdateUI            )
  EVT_UPDATE_UI( ID_CHANGETREEFONT,     PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_CHANGEADDEDITFONT,  PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_CHANGEPSWDFONT,     PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_CHANGENOTESFONT,    PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_CHANGEVKBFONT,      PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_CHANGEFONTMENU,     PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_CUSTOMIZETOOLBAR,   PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_REPORTSMENU,        PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_REPORT_COMPARE,     PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_REPORT_SYNCHRONIZE, PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_REPORT_MERGE,       PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_REPORT_IMPORTTEXT,  PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_REPORT_IMPORTXML,   PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_REPORT_IMPORTKEEPASS_TXT, PasswordSafeFrame::OnUpdateUI              )
  EVT_UPDATE_UI( ID_REPORT_IMPORTKEEPASS_CSV, PasswordSafeFrame::OnUpdateUI              )
  EVT_UPDATE_UI( ID_REPORT_EXPORTTEXT,  PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_REPORT_EXPORTXML,   PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_REPORT_EXPORT_DB,   PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_REPORT_FIND,        PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_REPORT_VALIDATE,    PasswordSafeFrame::OnUpdateUI                    )

  ////////////////////////////////////////////////////////////////////////////////////////
  // Menu: "Manage"
  ////////////////////////////////////////////////////////////////////////////////////////

  // Connect event handlers
  EVT_MENU( ID_CHANGECOMBO,             PasswordSafeFrame::OnChangePasswordClick         )
  EVT_MENU( ID_BACKUP,                  PasswordSafeFrame::OnBackupSafe                  )
  EVT_MENU( ID_RESTORE,                 PasswordSafeFrame::OnRestoreSafe                 )
  EVT_MENU( wxID_PREFERENCES,           PasswordSafeFrame::OnPreferencesClick            )
  EVT_MENU( ID_PWDPOLSM,                PasswordSafeFrame::OnPwdPolsMClick               )
  EVT_MENU( ID_GENERATEPASSWORD,        PasswordSafeFrame::OnGeneratePassword            )
#ifndef NO_YUBI
  EVT_MENU( ID_YUBIKEY_MNG,             PasswordSafeFrame::OnYubikeyMngClick             )
#endif
  EVT_MENU_RANGE( ID_LANGUAGE_BEGIN, ID_LANGUAGE_END, PasswordSafeFrame::OnLanguageClick )

  // Update menu elements
  EVT_UPDATE_UI( ID_CHANGECOMBO,        PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_BACKUP,             PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_RESTORE,            PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( wxID_PREFERENCES,      PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_PWDPOLSM,           PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_GENERATEPASSWORD,   PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_SETDATABASEID,      PasswordSafeFrame::OnUpdateUI                    )
#ifndef NO_YUBI
  EVT_UPDATE_UI( ID_YUBIKEY_MNG,        PasswordSafeFrame::OnUpdateUI                    )
#endif

  ////////////////////////////////////////////////////////////////////////////////////////
  // Menu: "Help"
  ////////////////////////////////////////////////////////////////////////////////////////

  // Connect event handlers
  EVT_MENU( ID_VISITWEBSITE,            PasswordSafeFrame::OnVisitWebsite                )
  EVT_MENU( wxID_ABOUT,                 PasswordSafeFrame::OnAboutClick                  )

  ////////////////////////////////////////////////////////////////////////////////////////
  // Context Menu
  ////////////////////////////////////////////////////////////////////////////////////////

  // Connect event handlers
  EVT_MENU( ID_PASSWORDSUBSET,          PasswordSafeFrame::OnPasswordSubset              )
  EVT_MENU( ID_PASSWORDQRCODE,          PasswordSafeFrame::OnPasswordQRCode              )
  EVT_MENU( ID_COPYEMAIL,               PasswordSafeFrame::OnCopyEmailClick              )
  EVT_MENU( ID_COPYRUNCOMMAND,          PasswordSafeFrame::OnCopyRunCmd                  )
  EVT_MENU( ID_BROWSEURLPLUS,           PasswordSafeFrame::OnBrowseUrlAndAutotype        )
  EVT_MENU( ID_SENDEMAIL,               PasswordSafeFrame::OnSendEmail                   )
  EVT_MENU( ID_RUNCOMMAND,              PasswordSafeFrame::OnRunCommand                  )
  EVT_MENU( ID_CREATESHORTCUT,          PasswordSafeFrame::OnCreateShortcut              )
  EVT_MENU( ID_EDITBASEENTRY,           PasswordSafeFrame::OnEditBase                    )

  // Update menu elements
  EVT_UPDATE_UI( ID_PASSWORDSUBSET,     PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_PASSWORDQRCODE,     PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_COPYEMAIL,          PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_BROWSEURLPLUS,      PasswordSafeFrame::OnUpdateUI                    )
  EVT_UPDATE_UI( ID_SENDEMAIL,          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_EDITBASEENTRY,      PasswordSafeFrame::OnUpdateUI                    )

  ////////////////////////////////////////////////////////////////////////////////////////
  // Search Control
  ////////////////////////////////////////////////////////////////////////////////////////

  // Connect event handlers
  EVT_MENU( ID_EDITMENU_FIND_NEXT,      PasswordSafeFrame::OnFindNext                    )
  EVT_MENU( ID_EDITMENU_FIND_PREVIOUS,  PasswordSafeFrame::OnFindPrevious                )

END_EVENT_TABLE()

int PasswordSafeFrame::s_TotpCountdownInterval = 1000; // ms
int PasswordSafeFrame::s_TotpCalculationInterval = 30; // s

/*!
 * PasswordSafeFrame constructors
 */

PasswordSafeFrame::PasswordSafeFrame(PWScore &core)
: m_core(core), m_currentView(ViewType::GRID), m_search(nullptr), m_sysTray(new SystemTray(this)),
  m_bRestoredDBUnsaved(false),
  m_RUEList(core), m_guiInfo(new GuiInfo), m_bTSUpdated(false), m_savedDBPrefs(wxEmptyString),
  m_CurrentPredefinedFilter(NONE), m_bFilterActive(false), m_InitialTreeDisplayStatusAtOpen(true),
  m_LastClipboardAction(wxEmptyString), m_LastAction(CItem::FieldType::START)
{
  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(ViewType::GRID), m_search(nullptr), m_sysTray(new SystemTray(this)),
    m_bRestoredDBUnsaved(false),
    m_RUEList(core), m_guiInfo(new GuiInfo), m_bTSUpdated(false), m_savedDBPrefs(wxEmptyString),
    m_CurrentPredefinedFilter(NONE), m_bFilterActive(false), m_InitialTreeDisplayStatusAtOpen(true),
    m_LastClipboardAction(wxEmptyString), m_LastAction(CItem::FieldType::START)
{
  Init();

  if (PWSprefs::GetInstance()->GetPref(PWSprefs::AlwaysOnTop)) {
    style |= wxSTAY_ON_TOP;
  }

  RegisterLanguageMenuItems();
  Create( parent, id, caption, pos, size, style );

  // Register all observers for notifications at the core
  m_core.RegisterObserver(this);

  if (IsTreeView()) {
    m_core.RegisterObserver(m_tree);
  }
  else {
    m_core.RegisterObserver(m_grid);
  }
  SetDropTarget(new DnDFile(this));
  SetFocus();
}

/*!
 * 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 );
  m_AuiManager.SetManagedWindow(this);
  CreateMenubar();
  CreateControls();
  SetIcon(GetIconResource(L"graphics/cpane.xpm"));
  Centre();
////@end PasswordSafeFrame creation
  CreateMainToolbar();
  CreateDragBar();
  CreateTotpBar();
  CreateSearchBar();
  CreateStatusBar();

  if (!LoadLayoutPreferences()) {
    pws_os::Trace(L"The AUI manager failed to load the layout preferences.");
  }

  GetTotpBarPane().Hide();
  UpdateSearchBarVisibility();
  m_AuiManager.Update();
  return true;
}

/*!
 * PasswordSafeFrame destructor
 */

PasswordSafeFrame::~PasswordSafeFrame()
{
////@begin PasswordSafeFrame destruction
////@end PasswordSafeFrame destruction
  if (m_core.IsDbFileSet())
    SaveIfChanged(); // moved here from PWSafeApp::OnExit(), where it's called too late.

  m_AuiManager.UnInit();

  delete m_sysTray;
  m_sysTray = nullptr;

  delete m_guiInfo;
  m_guiInfo = nullptr;

  StopTotpDisplayAuthCode();
  StopTotpCopyAuthCode();

  m_core.ClearDBData();
  m_core.UnregisterObserver(this);
}

/*!
 * Member initialisation
 */

void PasswordSafeFrame::Init()
{
  m_currentView = (PWSprefs::GetInstance()->GetPref(PWSprefs::LastView) == _T("list")) ? ViewType::GRID : ViewType::TREE;

  if (PWSprefs::GetInstance()->GetPref(PWSprefs::TreeSort) == _T("date")) {
    SetTreeSortType(TreeSortType::DATE);
  } else if (PWSprefs::GetInstance()->GetPref(PWSprefs::TreeSort) == _T("name")) {
    SetTreeSortType(TreeSortType::NAME);
  } else {
    SetTreeSortType(TreeSortType::GROUP);
  }

  m_RUEList.SetMax(PWSprefs::GetInstance()->PWSprefs::MaxREItems);
////@begin PasswordSafeFrame member initialisation
  m_Toolbar = nullptr;
  m_Dragbar = nullptr;
  m_grid = nullptr;
  m_tree = nullptr;
  m_statusBar = nullptr;
  m_bShowEmptyGroupsInFilter = false;
  m_ApplyClearFilter = nullptr;
  m_selectedfiltername = _T("");
////@end PasswordSafeFrame member initialisation
}

/**
 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_HUNGARIAN, wxLANGUAGE_HUNGARIAN, L"Hungarian"  );  /* code: 'hu' */
  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_SLOVENIAN, wxLANGUAGE_SLOVENIAN, L"Slovenian"  );  /* code: 'sl' */
  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()
{
  auto menuBar = GetMenuBar();

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

  menuBar->Freeze();

  // Remove any prior menu references from the recent DB list
  auto& rdb = wxGetApp().recentDatabases();
  auto menuVect = rdb.GetMenus().AsVector<wxMenu *>();
  for (auto item : menuVect) {
    rdb.RemoveMenu(item);
  }

  // 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

  /////////////////////////////////////////////////////////////////////////////
  // Menu: "File"
  /////////////////////////////////////////////////////////////////////////////

  auto menuFile = new wxMenu;
  menuFile->Append(wxID_NEW, _("&New..."), wxEmptyString, wxITEM_NORMAL);
  menuFile->Append(wxID_OPEN, _("&Open..."), wxEmptyString, wxITEM_NORMAL);
  menuFile->Append(wxID_CLOSE, _("&Close"), wxEmptyString, wxITEM_NORMAL);

  // Added for window managers which have no iconization concept
  if (m_sysTray->GetTrayStatus() == SystemTray::TrayStatus::LOCKED) {
    menuFile->Append(ID_UNLOCK_SAFE, _("&Unlock\tCtrl+I"), wxEmptyString, wxITEM_NORMAL);
  }
  else {
    menuFile->Append(ID_LOCK_SAFE, _("&Lock\tCtrl+J"), wxEmptyString, wxITEM_NORMAL);
  }

  if (rdb.GetCount() > 0) {
    // Most recently used DBs listed directly on File menu
    if (PWSprefs::GetInstance()->GetPref(PWSprefs::MRUOnFileMenu)) {
      rdb.UseMenu(menuFile);
      rdb.AddFilesToMenu(menuFile);
    }
    // Most recently used DBs listed as submenu of File menu
    else {
      auto recentDatabasesMenu = new wxMenu;
      rdb.UseMenu(recentDatabasesMenu);
      rdb.AddFilesToMenu(recentDatabasesMenu);
      menuFile->AppendSeparator();
      menuFile->Append(ID_RECENTSAFES, _("&Recent Databases..."), recentDatabasesMenu);
    }
  }
  else {
    menuFile->AppendSeparator();
  }

  menuFile->Append(ID_MENU_CLEAR_MRU, _("Clear Recently Opened List"), wxEmptyString, wxITEM_NORMAL);
  menuFile->AppendSeparator();
  menuFile->Append(wxID_SAVE, _("&Save..."), wxEmptyString, wxITEM_NORMAL);
  menuFile->Append(wxID_SAVEAS, _("Save &As..."), wxEmptyString, wxITEM_NORMAL);
  menuFile->AppendSeparator();

  auto menuExport = new wxMenu;
  menuExport->Append(ID_EXPORT2OLD1XFORMAT, _("v&1.x format..."), wxEmptyString, wxITEM_NORMAL);
  menuExport->Append(ID_EXPORT2V2FORMAT, _("v&2 format..."), wxEmptyString, wxITEM_NORMAL);
  menuExport->Append(ID_EXPORT2V4FORMAT, _("v&4 format (EXPERIMENTAL)..."), wxEmptyString, wxITEM_NORMAL);
  menuExport->Append(ID_EXPORT2PLAINTEXT, _("&Plain Text (tab separated)..."), wxEmptyString, wxITEM_NORMAL);
  menuExport->Append(ID_EXPORT2XML, _("&XML format..."), wxEmptyString, wxITEM_NORMAL);
  menuFile->Append(ID_EXPORTMENU, _("Export &To"), menuExport);

  auto menuImport = new wxMenu;
  menuImport->Append(ID_IMPORT_PLAINTEXT, _("&Plain Text..."), wxEmptyString, wxITEM_NORMAL);
  menuImport->Append(ID_IMPORT_XML, _("&XML format..."), wxEmptyString, wxITEM_NORMAL);
  menuImport->Append(ID_IMPORT_KEEPASS, _("&KeePass..."), wxEmptyString, wxITEM_NORMAL);
  menuFile->Append(ID_IMPORTMENU, _("Import &From"), menuImport);

  menuFile->Append(ID_MERGE, _("&Merge..."), wxEmptyString, wxITEM_NORMAL);
  menuFile->Append(ID_COMPARE, _("&Compare..."), wxEmptyString, wxITEM_NORMAL);
  menuFile->Append(ID_SYNCHRONIZE, _("S&ynchronize..."), wxEmptyString, wxITEM_NORMAL);
  menuFile->Append(ID_CHANGEMODE, _("Change R/W Mode"), wxEmptyString, wxITEM_NORMAL);
  menuFile->AppendSeparator();
  menuFile->Append(wxID_PROPERTIES, _("&Properties"), wxEmptyString, wxITEM_NORMAL);
  menuFile->AppendSeparator();
  menuFile->Append(wxID_EXIT, _("E&xit"), wxEmptyString, wxITEM_NORMAL);
  menuBar->Append(menuFile, _("&File"));

  /////////////////////////////////////////////////////////////////////////////
  // Menu: "Edit"
  /////////////////////////////////////////////////////////////////////////////

  auto menuEdit = new wxMenu;
  menuEdit->Append(wxID_ADD, _("&Add Entry...\tCtrl+A"), wxEmptyString, wxITEM_NORMAL);
  if (m_core.IsReadOnly()) {
    menuEdit->Append(ID_EDIT, _("&View Entry...\tCtrl+Enter"), wxEmptyString, wxITEM_NORMAL);
  }
  else {
    menuEdit->Append(ID_EDIT, _("Edit Entry...\tCtrl+Enter"), wxEmptyString, wxITEM_NORMAL);
  }
  menuEdit->Append(wxID_DELETE, _("&Delete Entry\tDel"), wxEmptyString, wxITEM_NORMAL);
  menuEdit->Append(ID_RENAME, _("Rename Entry\tF2"), wxEmptyString, wxITEM_NORMAL);
  menuEdit->Append(wxID_FIND, _("&Find Entry...\tCtrl+F"), wxEmptyString, wxITEM_NORMAL);
  menuEdit->Append(ID_DUPLICATEENTRY, _("&Duplicate Entry\tCtrl+D"), wxEmptyString, wxITEM_NORMAL);
  menuEdit->Append(ID_PROTECT, _("Protect Entry"), wxEmptyString, wxITEM_CHECK);
  menuEdit->AppendSeparator();
  menuEdit->Append(ID_ADDGROUP, _("Add Group"), wxEmptyString, wxITEM_NORMAL);
  menuEdit->AppendSeparator();
  menuEdit->Append(wxID_UNDO);
  menuEdit->Append(wxID_REDO);
  menuEdit->Append(ID_CLEARCLIPBOARD, _("C&lear Clipboard\tCtrl+Del"), wxEmptyString, wxITEM_NORMAL);
  menuEdit->AppendSeparator();
  menuEdit->Append(ID_COPYPASSWORD, _("&Copy Password to Clipboard\tCtrl+C"), wxEmptyString, wxITEM_NORMAL);
  menuEdit->Append(ID_COPYUSERNAME, _("Copy &Username to Clipboard\tCtrl+U"), wxEmptyString, wxITEM_NORMAL);
  menuEdit->Append(ID_PASSWORDSUBSET, _("Display subset of Password\tCtrl+B"), wxEmptyString, wxITEM_NORMAL);
  if (HasQRCode()) {
    menuEdit->Append(ID_PASSWORDQRCODE, _("Display Password as &QR code"), wxEmptyString, wxITEM_NORMAL);
  }
  menuEdit->Append(ID_COPYAUTHCODE, _("Copy Aut&h Code to Clipboard\tAlt+2"), wxEmptyString, wxITEM_NORMAL);
  menuEdit->Append(ID_SHOWAUTHCODE, _("Display Auth Code\tAlt+Ctrl+2"), wxEmptyString, wxITEM_NORMAL);
  menuEdit->Append(ID_COPYNOTESFLD, _("Copy &Notes to Clipboard\tCtrl+G"), wxEmptyString, wxITEM_NORMAL);
  menuEdit->Append(ID_COPYURL, _("Copy URL to Clipboard\tCtrl+Alt+L"), wxEmptyString, wxITEM_NORMAL);
  menuEdit->Append(ID_BROWSEURL, _("&Browse to URL\tCtrl+L"), wxEmptyString, wxITEM_NORMAL);
  menuEdit->Append(ID_AUTOTYPE, _("Perform Auto&type\tCtrl+T"), wxEmptyString, wxITEM_NORMAL);
  menuEdit->Append(ID_CREATESHORTCUT, _("Create &Shortcut"), wxEmptyString, wxITEM_NORMAL);
  menuEdit->Append(ID_GOTOBASEENTRY, _("Go to Base entry"), wxEmptyString, wxITEM_NORMAL);
  if (m_core.GetReadFileVersion() == PWSfile::V40) {
    menuEdit->Append(ID_VIEWATTACHMENT, _("View Attachment"), wxEmptyString, wxITEM_NORMAL);
  }
  menuBar->Append(menuEdit, _("&Edit"));

  /////////////////////////////////////////////////////////////////////////////
  // Menu: "View"
  /////////////////////////////////////////////////////////////////////////////

  auto menuView = new wxMenu;
  menuView->Append(ID_LIST_VIEW, _("Flattened &List"), wxEmptyString, wxITEM_RADIO);
  menuView->Append(ID_TREE_VIEW, _("Nested &Tree"), wxEmptyString, wxITEM_RADIO);
  menuView->AppendSeparator();
  
  auto menuSortTree = new wxMenu;
  menuSortTree->Append(ID_SORT_TREE_BY_GROUP, _("Sort by Group"), wxEmptyString, wxITEM_CHECK);
  menuSortTree->Append(ID_SORT_TREE_BY_NAME, _("Sort by Name"), wxEmptyString, wxITEM_CHECK);
  menuSortTree->Append(ID_SORT_TREE_BY_DATE, _("Sort by Date"), wxEmptyString, wxITEM_CHECK);
  menuView->Append(ID_SORT_TREE_MENU, _("Tree Sort"), menuSortTree);
  menuView->AppendSeparator();
  
  menuView->Append(ID_SHOWHIDE_TOOLBAR, _("Toolbar &visible"), wxEmptyString, wxITEM_CHECK);
  menuView->AppendRadioItem(ID_TOOLBAR_NEW, _("&New Toolbar"));
  menuView->AppendRadioItem(ID_TOOLBAR_CLASSIC, _("&Classic Toolbar"));
  menuView->Append(ID_SHOWHIDE_DRAGBAR, _("&Dragbar visible"), wxEmptyString, wxITEM_CHECK);
  menuView->AppendSeparator();
  menuView->Append(ID_EXPANDALL, _("Expand All"), wxEmptyString, wxITEM_NORMAL);
  menuView->Append(ID_COLLAPSEALL, _("Collapse All"), wxEmptyString, wxITEM_NORMAL);

  auto menuSubViews = new wxMenu;
  menuSubViews->Append(ID_SHOWHIDE_UNSAVED, _("&Unsaved Changes"), wxEmptyString, wxITEM_CHECK);
  menuSubViews->Append(ID_SHOW_ALL_EXPIRY, _("Entries with E&xpiry Dates"), wxEmptyString, wxITEM_CHECK);
  menuSubViews->Append(ID_SHOW_LAST_FIND_RESULTS, _("Last Find results"), wxEmptyString, wxITEM_CHECK);
  menuView->Append(ID_SUBVIEWSMENU, _("Subviews"), menuSubViews);
  
  auto menuFilters = new wxMenu;
  menuFilters->Append(ID_EDITFILTER, _("&New/Edit Filter..."), wxEmptyString, wxITEM_NORMAL);
  m_ApplyClearFilter = menuFilters->Append(ID_APPLYFILTER, _("&Apply current"), wxEmptyString, wxITEM_NORMAL);
  menuFilters->Append(ID_MANAGEFILTERS, _("&Manage..."), wxEmptyString, wxITEM_NORMAL);
  menuFilters->Append(ID_SHOW_EMPTY_GROUP_IN_FILTER, _("Empty Group visible in Filter"), wxEmptyString, wxITEM_CHECK);
  menuView->Append(ID_FILTERMENU, _("&Filters"), menuFilters);
  menuView->AppendSeparator();
  menuView->Append(ID_CUSTOMIZETOOLBAR, _("Customize &Main Toolbar..."), wxEmptyString, wxITEM_NORMAL);

  auto menuFonts = new wxMenu;
  menuFonts->Append(ID_CHANGETREEFONT, _("&Tree/List Font"), wxEmptyString, wxITEM_NORMAL);
  menuFonts->Append(ID_CHANGEADDEDITFONT, _("&Add/Edit Font"), wxEmptyString, wxITEM_NORMAL);
  menuFonts->Append(ID_CHANGEPSWDFONT, _("&Password Font"), wxEmptyString, wxITEM_NORMAL);
  menuFonts->Append(ID_CHANGENOTESFONT, _("&Notes Font"), wxEmptyString, wxITEM_NORMAL);
  //TODO: Applying font to virtual keyboard needs to be implemented; Event handler for menu item is already prepared
  //menuFonts->Append(ID_CHANGEVKBFONT, _("&Virtual Keyboard Font"), wxEmptyString, wxITEM_NORMAL);
  menuView->Append(ID_CHANGEFONTMENU, _("Change &Font"), menuFonts);

  auto menuReports = new wxMenu;
  menuReports->Append(ID_REPORT_COMPARE, _("&Compare"), wxEmptyString, wxITEM_NORMAL);
  menuReports->Append(ID_REPORT_FIND, _("&Find"), wxEmptyString, wxITEM_NORMAL);
  menuReports->Append(ID_REPORT_IMPORTTEXT, _("Import &Text"), wxEmptyString, wxITEM_NORMAL);
  menuReports->Append(ID_REPORT_IMPORTXML, _("Import &XML"), wxEmptyString, wxITEM_NORMAL);
  menuReports->Append(ID_REPORT_IMPORTKEEPASS_TXT, _("Import &KeePass V1 Text"), wxEmptyString, wxITEM_NORMAL);
  menuReports->Append(ID_REPORT_IMPORTKEEPASS_CSV, _("Import Kee&Pass V1 CSV"), wxEmptyString, wxITEM_NORMAL);
  menuReports->Append(ID_REPORT_EXPORTTEXT, _("&Export Text"), wxEmptyString, wxITEM_NORMAL);
  menuReports->Append(ID_REPORT_EXPORTXML, _("Export XM&L"), wxEmptyString, wxITEM_NORMAL);
  menuReports->Append(ID_REPORT_EXPORT_DB, _("Export Current &DB"), wxEmptyString, wxITEM_NORMAL); // TODO: Fill export file "Export DB Report.txt" with REPORT_EXPORT_DB_NAME
  menuReports->Append(ID_REPORT_MERGE, _("&Merge"), wxEmptyString, wxITEM_NORMAL);
  menuReports->Append(ID_REPORT_SYNCHRONIZE, _("&Synchronize"), wxEmptyString, wxITEM_NORMAL);
  menuReports->Append(ID_REPORT_VALIDATE, _("&Validate"), wxEmptyString, wxITEM_NORMAL);
  menuView->Append(ID_REPORTSMENU, _("Reports"), menuReports);
  menuBar->Append(menuView, _("&View"));

  /////////////////////////////////////////////////////////////////////////////
  // Menu: "Manage"
  /////////////////////////////////////////////////////////////////////////////

  auto menuManage = new wxMenu;
  menuManage->Append(ID_CHANGECOMBO, _("&Change Master Password..."), wxEmptyString, wxITEM_NORMAL);
  menuManage->AppendSeparator();
  menuManage->Append(ID_BACKUP, _("Make &Backup...\tCtrl+B"), wxEmptyString, wxITEM_NORMAL);
  menuManage->Append(ID_RESTORE, _("&Restore from Backup...\tCtrl+R"), wxEmptyString, wxITEM_NORMAL);
  menuManage->AppendSeparator();
  menuManage->Append(wxID_PREFERENCES, _("&Options...\tCtrl+M"), wxEmptyString, wxITEM_NORMAL);
  menuManage->Append(ID_PWDPOLSM, _("Password Policies..."), wxEmptyString, wxITEM_NORMAL);
  menuManage->AppendSeparator();
  menuManage->Append(ID_GENERATEPASSWORD, _("Generate &Password...\tCtrl+P"), _("Generate Password"), wxITEM_NORMAL);
#ifndef NO_YUBI
  menuManage->Append(ID_YUBIKEY_MNG, _("YubiKey..."), _("Configure and backup YubiKeys"), wxITEM_NORMAL);
#endif
  menuManage->AppendSeparator();
  AddLanguageMenu( menuManage );
  menuBar->Append(menuManage, _("&Manage"));

  /////////////////////////////////////////////////////////////////////////////
  // Menu: "Help"
  /////////////////////////////////////////////////////////////////////////////

  auto menuHelp = new wxMenu;
  menuHelp->Append(wxID_HELP, _("Get &Help"), wxEmptyString, wxITEM_NORMAL);
  menuHelp->Append(ID_VISITWEBSITE, _("Visit Password Safe &website..."), wxEmptyString, wxITEM_NORMAL);
  menuHelp->Append(wxID_ABOUT, _("&About Password Safe..."), wxEmptyString, wxITEM_NORMAL);
  menuBar->Append(menuHelp, _("&Help"));
  this->SetMenuBar(menuBar);

////@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
  menuBar->Check((IsTreeView()) ? ID_TREE_VIEW : ID_LIST_VIEW, true);
  menuBar->Check(PWSprefs::GetInstance()->GetPref(PWSprefs::UseNewToolbar) ? ID_TOOLBAR_NEW : ID_TOOLBAR_CLASSIC, true);
  menuBar->Check(ID_SHOW_EMPTY_GROUP_IN_FILTER, m_bShowEmptyGroupsInFilter);
  UpdateTreeSortMenu();
  // Refresh is done on UpdateTreeSortMenu()
}

/**
 * Update check for sorting and enable or disable the menu bar for orting
 */
void PasswordSafeFrame::UpdateTreeSortMenu()
{
  auto menuBar = GetMenuBar();
  
  menuBar->Check(ID_SORT_TREE_BY_GROUP, IsTreeSortGroup());
  menuBar->Check(ID_SORT_TREE_BY_NAME, IsTreeSortName());
  menuBar->Check(ID_SORT_TREE_BY_DATE, IsTreeSortDate());
  menuBar->Refresh();
}

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

  auto panel = new wxPanel(this);
  wxBoxSizer* itemBoxSizer83 = new wxBoxSizer(wxHORIZONTAL);
  panel->SetSizer(itemBoxSizer83);

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

  m_tree = new TreeCtrl( panel, m_core, ID_TREECTRL, wxDefaultPosition,
                            wxDefaultSize,
                            wxTR_EDIT_LABELS|wxTR_HAS_BUTTONS |wxTR_HIDE_ROOT|wxTR_SINGLE );
  wxASSERT(m_tree);
  if(IsTreeSortGroup()) {
    m_tree->SetSortingGroup();
    m_tree->SetShowGroup(false);
  } else if(IsTreeSortName()) {
    m_tree->SetSortingName();
    m_tree->SetShowGroup(true);
  } else if(IsTreeSortDate()) {
    m_tree->SetSortingDate();
    m_tree->SetShowGroup(true);
  }
  SetBackgroundColour(CurrentBackgroundColor2);

  // let the tree ctrl handle ID_ADDGROUP & ID_RENAME all by itself
  Connect(ID_ADDGROUP, wxEVT_COMMAND_MENU_SELECTED,
                       wxCommandEventHandler(TreeCtrl::OnAddGroup), nullptr, m_tree);
  Connect(ID_RENAME, wxEVT_COMMAND_MENU_SELECTED,
                       wxCommandEventHandler(TreeCtrl::OnRenameGroup), nullptr, m_tree);

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

  itemBoxSizer83->Layout();

  const RecentDbList& rdb = wxGetApp().recentDatabases();
  if (rdb.GetMaxFiles() > 0) {
    Connect(rdb.GetBaseId(), rdb.GetBaseId() + rdb.GetMaxFiles() - 1, wxEVT_COMMAND_MENU_SELECTED,
            wxCommandEventHandler(PasswordSafeFrame::OnOpenRecentDB));
  }

  m_AuiManager.AddPane(panel, wxAuiPaneInfo().
    Name(wxT("mainview")).Caption(wxT("Main View")).
    CenterPane().Resizable().Show()
  );
}

/**
 * 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()
{
  m_Toolbar = new wxAuiToolBar(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
    wxAUI_TB_DEFAULT_STYLE|wxAUI_TB_GRIPPER|wxAUI_TB_OVERFLOW|(PWSprefs::GetInstance()->GetPref(PWSprefs::ToolbarShowText) ? wxAUI_TB_TEXT : 0)
  );

  m_Toolbar->SetToolBorderPadding(5);

  RefreshToolbarButtons();

  const bool showToolbar = PWSprefs::GetInstance()->GetPref(PWSprefs::ShowToolbar);

  GetMenuBar()->Check(ID_SHOWHIDE_TOOLBAR, showToolbar);

  m_AuiManager.AddPane(m_Toolbar, wxAuiPaneInfo().
    Name(wxT("maintoolbar")).Caption(wxT("Main Toolbar")).
    ToolbarPane().Top().Row(0).Layer(1).
    Dockable(true).Floatable(false).Gripper(true).
    Show(showToolbar).MinSize(-1, 25)
  );
}

/**
 * Recreates the main toolbar's items based on <code>PwsToolbarButtons</code>.
 *
 * 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.
 *
 * @see ToolbarButtons.h
 */
void PasswordSafeFrame::RefreshToolbarButtons()
{
  auto pref = PWSprefs::GetInstance();
  wxASSERT(pref);
  auto toolbar = GetToolBar();
  wxASSERT(toolbar);

  if (toolbar->GetToolCount() > 0) {
    toolbar->ClearTools();
  }
  bool toolbarShowText = pref->GetPref(PWSprefs::ToolbarShowText);
  bool showMenuSeprator = pref->GetPref(PWSprefs::ShowMenuSeparator);

  for (const auto & PwsToolbarButton : PwsToolbarButtons) {
    if (PwsToolbarButton.IsSeparator()) {
      if (showMenuSeprator)
        toolbar->AddSeparator()->SetId(PwsToolbarButton.id);
    }
    else {
      if ((PwsToolbarButton.id == ID_LOCK_SAFE) && m_sysTray->IsLocked())
        continue; // When database is locked don't add the lock icon...
      if ((PwsToolbarButton.id == ID_UNLOCK_SAFE) && !m_sysTray->IsLocked())
        continue; // accordingly, don't add the unlock icon when database is unlocked
      if(toolbarShowText)
        toolbar->AddTool(
          PwsToolbarButton.id,
          wxGetTranslation(PwsToolbarButton.toollabel),
          PwsToolbarButton.GetBitmapForEnabledButton(),
          PwsToolbarButton.GetBitmapForDisabledButton(),
          wxITEM_NORMAL,
          wxGetTranslation(PwsToolbarButton.tooltip),
          wxEmptyString,
          nullptr);
      else
        toolbar->AddTool(
          PwsToolbarButton.id,
          wxGetTranslation(PwsToolbarButton.toollabel),
          PwsToolbarButton.GetBitmapForEnabledButton(),
          PwsToolbarButton.GetBitmapForDisabledButton(),
          wxITEM_NORMAL,
          wxEmptyString,
          wxEmptyString,
          nullptr);
    }
  }

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

/**
 * Updates the bitmaps of the main toolbar after changing the toolbar type.
 */
void PasswordSafeFrame::UpdateMainToolbarBitmaps()
{
  auto toolbar = GetToolBar();
  wxASSERT(toolbar);

  for (const auto & PwsToolbarButton : PwsToolbarButtons) {
    if (PwsToolbarButton.IsSeparator())
      continue;

    auto tool = toolbar->FindTool(PwsToolbarButton.id);
    if (tool) {
      tool->SetBitmap(PwsToolbarButton.GetBitmapForEnabledButton());
      tool->SetDisabledBitmap(PwsToolbarButton.GetBitmapForDisabledButton());
    }
  }

  toolbar->Realize();
}

/**
 * Inserts or deletes the separator elements of the main toolbar.
 *
 * @param insert if true separators are added, otherwise deleted.
 */
void PasswordSafeFrame::UpdateMainToolbarSeparators(bool insert)
{
  Freeze();

  if(insert) {
    RefreshToolbarButtons();
  }
  else {
    DeleteMainToolbarSeparators();
  }

  Thaw();
  DoLayout();
  SendSizeEvent();
}

/**
 * Deletes all separator elements from the main toolbar.
 */
void PasswordSafeFrame::DeleteMainToolbarSeparators()
{
  auto toolbar = GetToolBar();
  wxASSERT(toolbar);
  wxASSERT(toolbar->GetToolCount() > 0);

  for (size_t pos = 0; pos < toolbar->GetToolCount(); ++pos) {
    if(toolbar->FindToolByIndex(static_cast<int>(pos))->GetId() == ID_SEPARATOR)
      toolbar->DeleteByIndex(static_cast<int>(pos));
  }

  toolbar->Realize();
}

/**
 * Provides the pane on which the main tool bar is located.
 * @return the <code>wxAuiPaneInfo</code>
 */
wxAuiPaneInfo& PasswordSafeFrame::GetMainToolbarPane()
{
  return m_AuiManager.GetPane(wxT("maintoolbar"));
}

/**
 * Creates the dragbar.
 */
void PasswordSafeFrame::CreateDragBar()
{
  m_Dragbar = new DragBarCtrl(this);

  wxCHECK_RET(m_Dragbar, wxT("Could not create dragbar"));

  m_Dragbar->SetToolBorderPadding(5);

  const bool showToolbar = PWSprefs::GetInstance()->GetPref(PWSprefs::ShowDragbar);

  GetMenuBar()->Check(ID_SHOWHIDE_DRAGBAR, showToolbar);

  m_AuiManager.AddPane(m_Dragbar, wxAuiPaneInfo().
    Name(wxT("dragbar")).Caption(wxT("Dragbar")).
    ToolbarPane().Top().Row(1).Layer(0).
    Dockable(true).Floatable(false).Gripper(true).
    Show(showToolbar).MinSize(-1, 25)
  );
}

/**
 * Updates the dragbar's tooltips after changing the language.
 */
void PasswordSafeFrame::UpdateDragbarTooltips()
{
  auto dragbar = GetDragBar();
  wxCHECK_RET(dragbar, wxT("Couldn't find dragbar"));
  dragbar->UpdateTooltips();
}

/**
 * Provides the pane on which the drag bar is located.
 * @return the <code>wxAuiPaneInfo</code>
 */
wxAuiPaneInfo& PasswordSafeFrame::GetDragBarPane()
{
  return m_AuiManager.GetPane(wxT("dragbar"));
}

/**
 * Creates the TOTP-Bar.
 */
void PasswordSafeFrame::CreateTotpBar()
{
  m_TotpStaticText = new wxStaticText(this, wxID_STATIC, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
  m_TotpStaticText->SetFont((m_TotpStaticText->GetFont()).MakeLarger());

  m_AuiManager.AddPane(m_TotpStaticText, wxAuiPaneInfo().
    Name(wxT("totpbar")).Caption(wxT("TOTP Toolbar")).
    ToolbarPane().Bottom().Row(0).Layer(1).
    Dockable(false).Floatable(false).Gripper(false).
    MinSize(-1, 25).Hide()
  );
}

/**
 * Provides the pane on which the totp bar is located.
 * @return the <code>wxAuiPaneInfo</code>
 */
wxAuiPaneInfo& PasswordSafeFrame::GetTotpBarPane()
{
  return m_AuiManager.GetPane(wxT("totpbar"));
}

/**
 * Creates an instance of <code>PasswordSafeSearch</code> without creating the search bar related controls.
 * This is done when 'Find' is issued by the user for the first time.
 * @see PasswordSafeFrame::OnFindClick
 */
void PasswordSafeFrame::CreateSearchBar()
{
  m_search = new PasswordSafeSearch(this);
  m_search->SetGripperVisible(false); // since it is not dockable and movable there is no need for a gripper

  m_AuiManager.AddPane(m_search, wxAuiPaneInfo().
    Name(wxT("searchbar")).Caption(wxT("Searchbar")).
    ToolbarPane().Bottom().Row(1).Layer(0).
    Dockable(false).Floatable(false).Gripper(false).
    MinSize(-1, 35).Hide()
  );
}

/**
 * Provides the pane on which the search bar is located.
 * @return the <code>wxAuiPaneInfo</code>
 */
wxAuiPaneInfo& PasswordSafeFrame::GetSearchBarPane()
{
  return m_AuiManager.GetPane(wxT("searchbar"));
}

/**
 * Shows the search bar.
 */
void PasswordSafeFrame::ShowSearchBar()
{
  GetSearchBarPane().Show();
  m_AuiManager.Update();
  PWSprefs::GetInstance()->SetPref(PWSprefs::FindToolBarActive, true);
}

/**
 * Hides the search bar.
 */
void PasswordSafeFrame::HideSearchBar()
{
  GetSearchBarPane().Hide();
  m_AuiManager.Update();
  SetFocus();
  PWSprefs::GetInstance()->SetPref(PWSprefs::FindToolBarActive, false);
}

/**
 * Shows the TOTP bar.
 */
void PasswordSafeFrame::ShowTotpBar()
{
  GetTotpBarPane().Show();
  m_AuiManager.Update();
  StartTotpDisplayAuthCode();
}

/**
 * Hides the TOTP bar.
 */
void PasswordSafeFrame::HideTotpBar()
{
  GetTotpBarPane().Hide();
  m_AuiManager.Update();
  SetFocus();
  StopTotpDisplayAuthCode();
}

/**
 * Update the search bar visibility based on preference.
 */
void PasswordSafeFrame::UpdateSearchBarVisibility()
{
  const auto showSearchBar = PWSprefs::GetInstance()->GetPref(PWSprefs::FindToolBarActive);
  if (showSearchBar) {
    m_search->Activate();
    GetSearchBarPane().Show();
  }
  else {
    GetSearchBarPane().Hide();
  }
}

/**
 * Creates the statusbar.
 */
void PasswordSafeFrame::CreateStatusBar()
{
  m_statusBar = new StatusBar(this, ID_STATUSBAR, wxST_SIZEGRIP|wxNO_BORDER);
  m_statusBar->Setup();
  SetStatusBar(m_statusBar);
}

/*!
 * 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 == L"graphics/cpane.xpm")
  {
    wxIcon icon(cpane_xpm);
    return icon;
  }
  return wxNullIcon;
////@end PasswordSafeFrame icon retrieval
}

void PasswordSafeFrame::SetTitle(const wxString& title)
{
  wxString newtitle = _T("Password Safe");
  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::TrayStatus::UNLOCKED);
    m_core.ResumeOnDBNotification();
  } else {
    SetTitle(wxEmptyString);
    m_sysTray->SetTrayStatus(SystemTray::TrayStatus::CLOSED);
  }
  UpdateStatusBar();
  UpdateMenuBar();
  return status;
}

bool PasswordSafeFrame::Show(bool show)
{
  ShowGrid(show && IsGridView());
  ShowTree(show && IsTreeView());
  
  SetFocus();

  return wxFrame::Show(show);
}

void PasswordSafeFrame::ShowGrid(bool show)
{
  if (show) {
    m_grid->SetTable(new GridTable(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++);
    }
    
    if(PWSprefs::GetInstance()->GetPref(PWSprefs::AutoAdjColWidth)) {
      m_grid->AutoSizeColumns(false);
      m_grid->Layout();
    }
    
    m_grid->UpdateSorting();

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

  // Workaround for reported issue GH#829 as wxGrid does not honor dark themes like wxTree does.
  // See https://github.com/pwsafe/pwsafe/issues/829
#if wxVERSION_NUMBER >= 3103
  if (show && m_tree && m_grid && wxSystemSettings::GetAppearance().IsDark()) {
#else
  if (show && m_tree && m_grid) {
#endif
    auto gridBackgroundColor = m_grid->GetDefaultCellBackgroundColour();
    auto treeBackgroundColor = m_tree->GetBackgroundColour();

    auto gridTextColor = m_grid->GetDefaultCellTextColour();
    auto treeTextColor = m_tree->GetForegroundColour();

    if ((gridBackgroundColor != treeBackgroundColor) || (gridTextColor != treeTextColor)) {
      m_grid->SetDefaultCellBackgroundColour(treeBackgroundColor);
      m_grid->SetDefaultCellTextColour(treeTextColor);
      m_grid->Refresh();
    }
  }

  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);
    }

    if(IsTreeSortGroup() && (!m_bFilterActive || m_bShowEmptyGroupsInFilter || (m_CurrentPredefinedFilter == UNSAVED))) {
      // Empty groups need to be added separately
      typedef std::vector<StringX> StringVectorX;
      const StringVectorX& emptyGroups = (m_bFilterActive && (m_CurrentPredefinedFilter == UNSAVED) && !m_bShowEmptyGroupsInFilter) ?  m_core.GetModifiedEmptyGroups() : m_core.GetEmptyGroups();
      for (const auto & emptyGroup : emptyGroups)
        m_tree->AddEmptyGroup(emptyGroup);
    }

    if (m_tree->HasItems()) {// avoid assertion!
      m_tree->SortChildrenRecursively(m_tree->GetRootItem());
    }

    if (m_InitialTreeDisplayStatusAtOpen) {
      m_InitialTreeDisplayStatusAtOpen = false;

      switch (PWSprefs::GetInstance()->GetPref(PWSprefs::TreeDisplayStatusAtOpen)) {
        case PWSprefs::AllCollapsed:
          m_tree->SetGroupDisplayStateAllCollapsed();
          break;
        case PWSprefs::AllExpanded:
          m_tree->SetGroupDisplayStateAllExpanded();
          break;
        case PWSprefs::AsPerLastSave:
          m_tree->RestoreGroupDisplayState();
          break;
        default:
          m_tree->SetGroupDisplayStateAllCollapsed();
      }

      m_guiInfo->SaveTreeViewInfo(m_tree);
    }
    else {
      m_guiInfo->RestoreTreeViewInfo(m_tree);
    }
    
    // Only update of red colour is needed, when filter is selected, as black colour is standard
    if(m_bFilterActive)
      m_tree->SetFilterState(m_bFilterActive);
  }
  else {
    m_guiInfo->SaveTreeViewInfo(m_tree);
  }

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

void PasswordSafeFrame::ClearAppData()
{
  m_grid->Clear();
  m_tree->Clear();
  ResetFilters();
}

bool PasswordSafeFrame::IsEntryMarked()
{
  if (m_tree->IsShown()) {
    wxTreeItemId item = m_tree->GetSelection();
    if(item == m_tree->GetRootItem()) return false;
    m_tree->SetDndEntry(item); // Mark DnD Item
    return item.IsOk() ? true : false;
  }
  return false;
}

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 nullptr;
}

// Following is "generalized" GetSelectedEntry to support section via RUE
CItemData *PasswordSafeFrame::GetSelectedEntry(const wxCommandEvent& evt, CItemData &rueItem) const
{
  if (!IsRUEEvent(evt))
    return GetSelectedEntry();
  else
    return m_RUEList.GetPWEntry(GetEventRUEIndex(evt), rueItem)? &rueItem: nullptr;
}

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;

  // Save the current file name so we can unlock it later.
  stringT oldfn = GetCurrentFile().c_str();

  // prompt for password, try to Load.
  DestroyWrapper<SafeCombinationPromptDlg> pwdpromptWrapper(this, m_core, fname);
  SafeCombinationPromptDlg* pwdprompt = pwdpromptWrapper.Get();

  if (pwdprompt->ShowModal() == wxID_OK) {
    m_core.SetCurFile(tostringx(fname));
    StringX password = pwdprompt->GetPassword();
    int retval = Load(password);
    if (retval == PWScore::SUCCESS) {
      m_InitialTreeDisplayStatusAtOpen = true;
      Show();
      wxGetApp().recentDatabases().AddFileToHistory(fname);

      // The new file is open.  Clear the lock on the old file, if any.
      m_core.SafeUnlockFile(oldfn);
    }
    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
  }

  // Reset core and clear ALL associated data
  m_core.ReInit();

  // clear the application data before loading the new file
  ClearAppData();

  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(nullptr);
  m_core.SetReporter(nullptr);
  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;
  if(m_tree)
    m_tree->SetFilterActive(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
}

/*!
 * wxEVT_CHAR_HOOK event handler for WXK_ESCAPE
 */

void PasswordSafeFrame::OnChar( wxKeyEvent& evt )
{
  if ((evt.GetKeyCode() == WXK_ESCAPE) && PWSprefs::GetInstance()->GetPref(PWSprefs::EscExits)) {

    Close();
  }

  evt.Skip();
}

/*!
 * 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 or systray 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 (IsCloseInProgress() || !systrayEnabled) {
    if (evt.CanVeto()) {
      int rc = SaveIfChanged();
      if (rc == PWScore::USER_CANCEL) {
        evt.Veto();
        return;
      }
    }

    if (PWSprefs::GetInstance()->GetPref(PWSprefs::ClearClipboardOnExit)) {
      Clipboard::GetInstance()->ClearCBData();
    }

    // Don't leave dangling locks!
    m_core.SafeUnlockCurFile();

    SaveSettings();

    // Reset core and clear ALL associated data
    m_core.ReInit();

    // clear the application data before ending
    ClearAppData();

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

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

/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for wxID_ABOUT
 */

void PasswordSafeFrame::OnAboutClick(wxCommandEvent& WXUNUSED(evt))
{
  ShowModalAndGetResult<AboutDlg>(this);
}

/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for ID_SENDEMAIL
 */

void PasswordSafeFrame::OnSendEmail(wxCommandEvent& evt)
{
  CItemData rueItem;
  CItemData* item = GetSelectedEntry(evt, rueItem);
  if (item)
    DoEmail(*item);
}

/*!
 * wxEVT_COMMAND_MENU_SELECTED event handler for ID_RUNCOMMAND
 */

void PasswordSafeFrame::OnRunCommand(wxCommandEvent& evt)
{
  CItemData rueItem;
  CItemData* item = GetSelectedEntry(evt, rueItem);
  if (item)
    DoRun(*item);
}

void PasswordSafeFrame::OnEditBase(wxCommandEvent& WXUNUSED(evt))
{
  CallAfter(&PasswordSafeFrame::DoEditBase);
}

void PasswordSafeFrame::DoEditBase()
{
  CItemData* item = GetSelectedEntry();
  if (item && item->IsDependent()) {
    item = m_core.GetBaseEntry(item);
    ASSERT(item != nullptr);
    DoEdit(*item);
    UpdateAccessTime(*item);
  }
}

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

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

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();
    UpdateStatusBar();
  }
}

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;

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

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

  int16 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, TreeCtrl* tree, OrderedItemList& olist)
{
  wxTreeItemIdValue cookie;

  if (!id.IsOk()) {
    return;
  }

  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 == nullptr) {
    if (IsTreeView()) {
      wxMenu groupEditMenu;

      groupEditMenu.Append(wxID_ADD, _("Add &Entry"));
      
      if (IsTreeSortGroup()) {
        groupEditMenu.Append(ID_ADDGROUP, _("Add &Group"));

        if (m_tree->IsGroupSelected()) {
          groupEditMenu.Append(ID_RENAME, _("&Rename Group"));
          groupEditMenu.Append(wxID_DELETE, _("&Delete Group"));
        }
      }

      m_tree->PopupMenu(&groupEditMenu);
    }
    else {
      wxMenu contextMenu;

      contextMenu.Append(wxID_ADD, _("Add &Entry"));

      m_grid->PopupMenu(&contextMenu);
    }
  }
  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"));
    if (HasQRCode()) {
      itemEditMenu.Append(ID_PASSWORDQRCODE, _("Display Password as &QR code"));
    }
    itemEditMenu.Append(ID_COPYAUTHCODE,   _("Copy Aut&h Code to Clipboard"));
    itemEditMenu.Append(ID_SHOWAUTHCODE,   _("Display Auth Code"));
    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();
    if (m_core.IsReadOnly()) {
      itemEditMenu.Append(ID_EDIT,         _("&View Entry..."));
    }
    else {
      itemEditMenu.Append(ID_EDIT,         _("Edit Entry..."));
    }
    itemEditMenu.Append(ID_DUPLICATEENTRY, _("&Duplicate Entry"));
    itemEditMenu.Append(wxID_DELETE,       _("Delete Entry"));
    itemEditMenu.Append(ID_CREATESHORTCUT, _("Create &Shortcut"));

    UUIDVector shortcutUUIDs;
    m_core.GetAllDependentEntries(item->GetUUID(), shortcutUUIDs, CItemData::EntryType::ET_SHORTCUT);

    if (!shortcutUUIDs.empty()) {

      auto shortcutsMenu = new wxMenu;

      for (auto const& shortcutUUID : shortcutUUIDs) {

        auto entryIterator = m_core.Find(shortcutUUID);

        if (entryIterator != m_core.GetEntryEndIter()) {
          auto shortcutItem = m_core.GetEntry(entryIterator);

          wxString group = wxT("N/A");
          if (!shortcutItem.GetGroup().empty()) {
            group = stringx2std(shortcutItem.GetGroup());
          }

          wxString title = wxT("N/A");
          if (!shortcutItem.GetTitle().empty()) {
            title = stringx2std(shortcutItem.GetTitle());
          }

          wxString username  = wxT("N/A");
          if (!shortcutItem.GetUser().empty()) {
            username = stringx2std(shortcutItem.GetUser());
          }

          auto menuID = wxNewId();
          shortcutsMenu->Append(
            menuID,
            wxT("<") + group + wxT("> / <") + title + wxT("> / <") + username + wxT(">")
          );

          Bind(wxEVT_MENU, [&](wxCommandEvent& event) { SelectItem(shortcutUUID); }, menuID);
        }
      }

      itemEditMenu.AppendSubMenu(shortcutsMenu, _("Goto &Shortcuts..."));
    }

    itemEditMenu.Append(ID_GOTOBASEENTRY,  _("&Go to Base entry"));
    if (m_core.IsReadOnly()) {
      itemEditMenu.Append(ID_EDITBASEENTRY,  _("View &Base entry"));
    }
    else {
      itemEditMenu.Append(ID_EDITBASEENTRY,  _("&Edit Base entry"));
    }
    if (item->HasAttRef()) {
      itemEditMenu.Append(ID_VIEWATTACHMENT, _("View Attachment"));
    }
    if (!item->IsShortcut()) {
      itemEditMenu.AppendCheckItem(ID_PROTECT,  _("Protect Entry"));
      itemEditMenu.Check(ID_PROTECT, item->IsProtected());
    }

    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 (IsTreeView()) {
      m_tree->PopupMenu(&itemEditMenu);
    }
    else {
      m_grid->PopupMenu(&itemEditMenu);
    }
  }
}

CItemData* PasswordSafeFrame::GetBaseEntry(const CItemData *item) const
{
  if (item && item->IsDependent()) {
    return m_core.GetBaseEntry(item);
  }
  return nullptr;
}

///////////////////////////////////////////////////////////////////////////////
// TOTP Begin

const CItemData* PasswordSafeFrame::GetTotpItem(const CItemData *item) const
{
  if (item == nullptr) {
    // GetBaseEntry doesn't like nullptr
    return nullptr;
  }
  if (item->IsNormal() || item->IsBase()) {
    return item;
  }
  return m_core.GetBaseEntry(item);
}

bool PasswordSafeFrame::IsItemNormalOrBase(const CItemData *item) const
{
  return item == nullptr ? false : item->IsNormal() || item->IsBase();
}

bool PasswordSafeFrame::HasItemTwoFactorKey(const CItemData *item) const
{
  auto totpItem = GetTotpItem(item);
  return (totpItem == nullptr) ? false : totpItem->IsTotpActive();
}

std::pair<StringX, StringX> PasswordSafeFrame::GetTotpData(const CItemData *item)
{
  if (item == nullptr) {
    return std::make_pair(tostringx(wxT("n/a")), tostringx(wxT("n/a")));
  }
  StringX totp;
  double ratio;
  CItemData ciTemp(*GetTotpItem(item));
  auto r = GetTwoFactorAuthenticationCode(ciTemp, totp, &ratio);
  if (r != PWSTotp::Success) {
    return std::make_pair(tostringx(wxT("n/a")), tostringx(wxT("n/a")));
  }
  return std::make_pair(
    totp,
    tostringx(wxNumberFormatter::ToString(
      ceil(s_TotpCalculationInterval - s_TotpCalculationInterval * ratio), 0)
    )
  );
}

PWSTotp::TOTP_Result PasswordSafeFrame::GetTwoFactorAuthenticationCode(const CItemData& ci, StringX& sxAuthCode, double* pRatio)
{
  sxAuthCode.clear();

  if (ci.GetTwoFactorKey().empty()) {
    wxMessageBox(
      _("Missing authentication secret key."),
      _("Authentication Code Error"),
      wxOK|wxCENTER|wxICON_INFORMATION, this
    );
    return PWSTotp::TotpKeyNotFound;
  }

  PWSTotp::TOTP_Result r = PWSTotp::GetNextTotpAuthCodeString(ci, sxAuthCode, nullptr, pRatio);
  if (r != PWSTotp::Success) {
    wxMessageBox(
      _("An error occurred obtaining the authentication code."),
      _("Authentication Code Error"),
      wxOK|wxCENTER|wxICON_INFORMATION, this
    );
    sxAuthCode.clear();
  }
  return r;
}

/// wxEVT_TIMER_EVENT event handler for ID_TIMER_DISPLAY_TOTP
void PasswordSafeFrame::OnTotpCountdownTimer(wxTimerEvent& WXUNUSED(event))
{
  auto item = GetSelectedEntry();
  // No item selected or item with
  // no TOTP configuration selected
  if (item == nullptr || !HasItemTwoFactorKey(item)) {
    m_TotpStaticText->SetLabel(wxEmptyString);
    return;
  }
  UpdateTotpDisplayOnBar(item);
}

/// wxEVT_TIMER_EVENT event handler for ID_TIMER_COPY_TOTP
void PasswordSafeFrame::OnTotpCopyAuthCodeTimer(wxTimerEvent& WXUNUSED(event))
{
  static StringX s_LatestAuthCode(L"");
  auto isAuthCodeInClipboard = Clipboard::GetInstance()->HasData(s_LatestAuthCode);
  auto item = GetSelectedEntry();
  // No item selected or item with
  // no TOTP configuration selected
  // or new item selected then stop
  // updating the auth code in clipboard.
  // Stop also updating the auth code in
  // clipboard if a code was ever copied
  // (s_LatestAuthCode is not empty) and
  // if this data is no longer present in
  // the clipboard.
  if (
    (item == nullptr || !HasItemTwoFactorKey(item) || (m_TotpLastSelectedItem != item))
    ||
    (!isAuthCodeInClipboard && !s_LatestAuthCode.empty())) {
    m_TotpLastSelectedItem = nullptr;
    s_LatestAuthCode.clear();
    StopTotpCopyAuthCode();
    return;
  }
  // Update the auth code in the clipboard
  auto totpData = GetTotpData(item);
  if (s_LatestAuthCode != totpData.first) {
    s_LatestAuthCode = totpData.first;
    DoCopyAuthCode(item);
    return;
  }
}

void PasswordSafeFrame::StartTotpDisplayAuthCode()
{
  if (m_TotpCountdownTimer && m_TotpCountdownTimer->IsRunning()) {
    return;
  }
  m_TotpCountdownTimer = new wxTimer(this, ID_TIMER_DISPLAY_TOTP);
  Bind(wxEVT_TIMER, &PasswordSafeFrame::OnTotpCountdownTimer, this, m_TotpCountdownTimer->GetId());
  m_TotpCountdownTimer->Start(GetTotpCountdownInterval());
}

void PasswordSafeFrame::StopTotpDisplayAuthCode()
{
  if (m_TotpCountdownTimer) {
    Unbind(wxEVT_TIMER, &PasswordSafeFrame::OnTotpCountdownTimer, this, m_TotpCountdownTimer->GetId());
  }
  // The wxTimer destructor stops the timer if it is running.
  delete m_TotpCountdownTimer;
  m_TotpCountdownTimer = nullptr;
}

void PasswordSafeFrame::StartTotpCopyAuthCode()
{
  if (m_TotpCopyAuthCodeTimer && m_TotpCopyAuthCodeTimer->IsRunning()) {
    return;
  }
  m_TotpCopyAuthCodeTimer = new wxTimer(this, ID_TIMER_COPY_TOTP);
  Bind(wxEVT_TIMER, &PasswordSafeFrame::OnTotpCopyAuthCodeTimer, this, m_TotpCopyAuthCodeTimer->GetId());
  m_TotpCopyAuthCodeTimer->Start(GetTotpCountdownInterval());
}

void PasswordSafeFrame::StopTotpCopyAuthCode()
{
  if (m_TotpCopyAuthCodeTimer) {
    Unbind(wxEVT_TIMER, &PasswordSafeFrame::OnTotpCopyAuthCodeTimer, this, m_TotpCopyAuthCodeTimer->GetId());
  }
  // The wxTimer destructor stops the timer if it is running.
  delete m_TotpCopyAuthCodeTimer;
  m_TotpCopyAuthCodeTimer = nullptr;
}

void PasswordSafeFrame::UpdateTotpDisplayOnBar(const CItemData *item)
{
  auto totpData = GetTotpData(item);
  auto totpString = wxString::Format(
    _("\tAuthentication code %ls is valid for %ls seconds"),
    towxstring(totpData.first), towxstring(totpData.second)
  );
  wxScreenDC dc;
  m_TotpStaticText->SetLabel(
    wxControl::Ellipsize(
      totpString, dc, wxEllipsizeMode::wxELLIPSIZE_END,
      /* The limiting width for the text is the main frame's width. */
      GetSize().GetWidth() - 80
    )
  );
}

// TOTP End
///////////////////////////////////////////////////////////////////////////////

bool PasswordSafeFrame::CheckReportPresent(int iAction)
{
  if(m_core.IsDbFileSet()) {
    CReport rpt;
    rpt.StartReport(iAction, m_core.GetCurFile().c_str(), false);
    return rpt.ReportExistsOnDisk();
  }
  return false;
}

////////////////////////////////////////////////////////
// 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)
{
  const CItemData *pci(nullptr), *pbci(nullptr);

  const bool isFileReadOnly          = m_core.IsReadOnly();
  const bool isTreeView              = IsTreeView();
  const bool isTreeViewGroupSelected = isTreeView && m_tree->IsGroupSelected();
  const bool isTreeViewEmpty         = isTreeView && !m_tree->HasItems(); // excludes the invisible root item
  const bool isTreeViewItemSelected  = isTreeView && m_tree->HasSelection();
  const bool isWayland               = (wxUtilities::WhatWindowSystem() == wxUtilities::Wayland);
  const bool isUnlocked              = !IsLocked();

  pci = GetSelectedEntry();

  if (pci) {
    if (pci->IsDependent()) {
      pbci = m_core.GetBaseEntry(pci);
    }
  }

  switch (evt.GetId()) {
    case wxID_NEW:
    case wxID_OPEN:
    case ID_SHOWAUTHCODE:
    case ID_CHANGEFONTMENU:
      evt.Enable(isUnlocked);
      break;

    case wxID_SAVE:
      evt.Enable(isUnlocked && m_core.IsDbFileSet() && !isFileReadOnly && (m_core.HasDBChanged() || m_core.HaveDBPrefsChanged()));
      break;

    case wxID_SAVEAS:
    case wxID_PREFERENCES:
    case wxID_PROPERTIES:
    case ID_CLEARCLIPBOARD:
    case ID_LIST_VIEW:
    case ID_TREE_VIEW:
    case ID_REPORTSMENU:
    case ID_BACKUP:
    case ID_RESTORE:
    case ID_GENERATEPASSWORD:
#ifndef NO_YUBI
    case ID_YUBIKEY_MNG:
#endif
      evt.Enable(isUnlocked && m_core.IsDbFileSet());
      break;

    case ID_REPORT_SYNCHRONIZE:
      evt.Enable(isUnlocked && CheckReportPresent(IDSC_RPTSYNCH));
      break;

    case ID_REPORT_COMPARE:
      evt.Enable(isUnlocked && CheckReportPresent(IDSC_RPTCOMPARE));
      break;

    case ID_REPORT_MERGE:
      evt.Enable(isUnlocked && CheckReportPresent(IDSC_RPTMERGE));
      break;

    case ID_REPORT_IMPORTTEXT:
      evt.Enable(isUnlocked && CheckReportPresent(IDSC_RPTIMPORTTEXT));
      break;

    case ID_REPORT_IMPORTXML:
      evt.Enable(isUnlocked && CheckReportPresent(IDSC_RPTIMPORTXML));
      break;

    case ID_REPORT_IMPORTKEEPASS_TXT:
      evt.Enable(isUnlocked && CheckReportPresent(IDSC_RPTIMPORTKPV1TXT));
      break;

    case ID_REPORT_IMPORTKEEPASS_CSV:
      evt.Enable(isUnlocked && CheckReportPresent(IDSC_RPTIMPORTKPV1CSV));
      break;

    case ID_REPORT_EXPORTTEXT:
      evt.Enable(isUnlocked && CheckReportPresent(IDSC_RPTEXPORTTEXT));
      break;

    case ID_REPORT_EXPORTXML:
      evt.Enable(isUnlocked && CheckReportPresent(IDSC_RPTEXPORTXML));
      break;

    case ID_REPORT_EXPORT_DB:
      evt.Enable(isUnlocked && CheckReportPresent(IDSC_RPTEXPORTDB));
      break;

    case ID_REPORT_FIND:
      evt.Enable(isUnlocked && CheckReportPresent(IDSC_RPTFIND));
      break;

    case ID_REPORT_VALIDATE:
      evt.Enable(isUnlocked && CheckReportPresent(IDSC_RPTVALIDATE));
      break;

    case ID_SORT_TREE_MENU:
    case ID_SORT_TREE_BY_GROUP:
    case ID_SORT_TREE_BY_NAME:
    case ID_SORT_TREE_BY_DATE:
      evt.Enable(isUnlocked && m_core.IsDbFileSet() && isTreeView);
      break;

    case ID_EXPORTMENU:
    case ID_COMPARE:
      evt.Enable(isUnlocked && m_core.IsDbFileSet() && m_core.GetNumEntries() != 0);
      break;

    case ID_ADDGROUP:
      evt.Enable(isUnlocked && (isTreeViewGroupSelected || isTreeViewEmpty || !isTreeViewItemSelected) && !isFileReadOnly && IsTreeSortGroup() && m_core.IsDbFileSet());
      break;

    case ID_EXPANDALL:
    case ID_COLLAPSEALL:
      evt.Enable(isUnlocked && !isTreeViewEmpty && m_core.IsDbFileSet());
      break;

    case ID_RENAME:
      // only allowed if a GROUP item is selected in tree view
      evt.Enable(isUnlocked && isTreeViewGroupSelected && !isFileReadOnly && IsTreeSortGroup());
      break;

    case ID_BROWSEURL:
    case ID_BROWSEURLPLUS:
    case ID_COPYURL:
      evt.Enable(isUnlocked && !isTreeViewGroupSelected && pci && !pci->IsFieldValueEmpty(CItemData::URL, pbci));
      break;

    case ID_SENDEMAIL:
    case ID_COPYEMAIL:
      evt.Enable(isUnlocked && !isTreeViewGroupSelected && pci &&
          (!pci->IsFieldValueEmpty(CItemData::EMAIL, pbci) ||
          (!pci->IsFieldValueEmpty(CItemData::URL, pbci) && pci->IsURLEmail(pbci))));
      break;

    case ID_COPYUSERNAME:
      evt.Enable(isUnlocked && !isTreeViewGroupSelected && pci && !pci->IsFieldValueEmpty(CItemData::USER, pbci));
      break;

    case ID_COPYNOTESFLD:
      evt.Enable(isUnlocked && !isTreeViewGroupSelected && pci && !pci->IsFieldValueEmpty(CItemData::NOTES, pbci));
      break;

    case ID_RUNCOMMAND:
    case ID_COPYRUNCOMMAND:
      evt.Enable(isUnlocked && !isTreeViewGroupSelected && pci && !pci->IsFieldValueEmpty(CItemData::RUNCMD, pbci));
      break;

    case ID_CREATESHORTCUT:
      evt.Enable(isUnlocked && !isTreeViewGroupSelected && !isFileReadOnly && pci &&
          (pci->IsNormal() || pci->IsShortcutBase()));
      break;

    case ID_AUTOTYPE:
      evt.Enable(isUnlocked && !isWayland && !isTreeViewGroupSelected && pci);
      break;

    case ID_EDIT:
    case ID_COPYPASSWORD:
    case ID_PASSWORDSUBSET:
    case ID_PASSWORDQRCODE:
      evt.Enable(isUnlocked && !isTreeViewGroupSelected && pci);
      break;

    case ID_COPYAUTHCODE:
      evt.Enable(isUnlocked && !isTreeViewGroupSelected && ((pci && pci->IsTotpActive()) || (pbci && pbci->IsTotpActive())));
      break;

    case ID_VIEWATTACHMENT:
      evt.Enable(isUnlocked && pci && pci->HasAttRef());
      break;

    case ID_GOTOBASEENTRY:
    case ID_EDITBASEENTRY:
      evt.Enable(isUnlocked && !isTreeViewGroupSelected && pci && (pci->IsShortcut() || pci->IsAlias()));
      break;

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

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

    case ID_SYNCHRONIZE:
      evt.Enable(isUnlocked && !isFileReadOnly && m_core.IsDbFileSet() && m_core.GetNumEntries() != 0);
      break;

    case ID_CHANGECOMBO:
      evt.Enable(isUnlocked && !isFileReadOnly && m_core.IsDbFileSet());
      break;

    case wxID_FIND:
      evt.Enable(isUnlocked && m_core.IsDbFileSet() && m_core.GetNumEntries() != 0);
      break;

    case wxID_ADD:
      evt.Enable(isUnlocked && !isFileReadOnly && m_core.IsDbFileSet());
      break;

    case wxID_DELETE:
      evt.Enable(isUnlocked && !isFileReadOnly && ((pci && !pci->IsProtected()) || isTreeViewGroupSelected));
      break;

    case ID_DUPLICATEENTRY:
      evt.Enable(isUnlocked && !isFileReadOnly && pci);
      break;

    case ID_SHOWHIDE_UNSAVED:
      evt.Enable(isUnlocked && ((m_CurrentPredefinedFilter == UNSAVED) || ((m_CurrentPredefinedFilter == NONE) && m_core.IsDbFileSet() && !isFileReadOnly && m_core.HasDBChanged())));
      evt.Check(m_CurrentPredefinedFilter == UNSAVED);
      break;

    case ID_SHOW_ALL_EXPIRY:
      evt.Enable(isUnlocked && ((m_CurrentPredefinedFilter == EXPIRY) || ((m_CurrentPredefinedFilter == NONE) &&
       m_core.IsDbFileSet() &&
       m_core.GetExpirySize() != 0)));
      evt.Check(m_CurrentPredefinedFilter == EXPIRY);
      break;

    case ID_SHOW_LAST_FIND_RESULTS:
      evt.Enable(isUnlocked && ((m_CurrentPredefinedFilter == LASTFIND) || ((m_CurrentPredefinedFilter == NONE) &&
                  m_core.IsDbFileSet() &&
                  m_FilterManager.GetFindFilterSize() != 0)));
      evt.Check(m_CurrentPredefinedFilter == LASTFIND);
      break;

    case ID_MERGE:
    case ID_IMPORTMENU:
      evt.Enable(isUnlocked && !isFileReadOnly && m_core.IsDbFileSet());
      break;

    case ID_IMPORT_XML:
#if (!defined(_WIN32) && USE_XML_LIBRARY == MSXML)
      evt.Enable(false);
#else
      evt.Enable(isUnlocked && !isFileReadOnly && m_core.IsDbFileSet());
#endif
      break;

    case ID_PROTECT:
      evt.Enable(isUnlocked && !isFileReadOnly && pci && !pci->IsShortcut());
      evt.Check(pci && pci->IsProtected());
      break;

    case wxID_CLOSE:
      evt.Enable(m_sysTray->GetTrayStatus() != SystemTray::TrayStatus::CLOSED);
      break;

    case ID_PWDPOLSM:
    case ID_LOCK_SAFE:
    case ID_SETDATABASEID:
      evt.Enable(isUnlocked && m_core.IsDbFileSet());
      break;

    case ID_UNLOCK_SAFE:
      evt.Enable(!isUnlocked && m_core.IsDbFileSet());
      break;

    case ID_FILTERMENU:
      evt.Enable(isUnlocked && m_core.IsDbFileSet());
      break;

    case ID_EDITFILTER:
      evt.Enable(isUnlocked && m_core.IsDbFileSet() && m_CurrentPredefinedFilter == NONE); // Mark unimplemented
      break;

    case ID_APPLYFILTER:
      evt.Enable(isUnlocked && m_core.IsDbFileSet() && (m_bFilterActive || CurrentFilter().IsActive()));
      if(m_bFilterActive) {
        m_ApplyClearFilter->SetItemLabel(_("&Clear current"));
      }
      else {
        m_ApplyClearFilter->SetItemLabel(_("&Apply current"));
      }
      break;

    case ID_MANAGEFILTERS:
      evt.Enable(isUnlocked && m_core.IsDbFileSet() && m_CurrentPredefinedFilter == NONE); // Mark unimplemented
      break;

    case ID_SHOW_EMPTY_GROUP_IN_FILTER:
      evt.Enable(isUnlocked && m_core.IsDbFileSet() && isTreeView && m_bFilterActive);
      break;

    case ID_SUBVIEWSMENU:
      evt.Enable(isUnlocked && m_core.IsDbFileSet());
      break;

    case ID_CUSTOMIZETOOLBAR:
      evt.Enable(false); // Mark unimplemented
      break;

    case ID_SHOWHIDE_TOOLBAR:
      evt.Check(GetMainToolbarPane().IsShown());
      break;

    case ID_SHOWHIDE_DRAGBAR:
      evt.Check(GetDragBarPane().IsShown());
      break;

    case ID_CHANGEMODE:
    {
      bool bFileIsReadOnly = true;
      if(m_core.IsDbFileSet()) {
        pws_os::FileExists(m_core.GetCurFile().c_str(), bFileIsReadOnly);
      }
      evt.Enable(isUnlocked && m_core.IsDbFileSet() && !bFileIsReadOnly);
      break;
    }
    default:
      break;
  }
}

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

bool PasswordSafeFrame::IsLocked() const
{
  return m_sysTray && m_sysTray->IsLocked();
}

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();
}


/**
 * Implements Observer::DatabaseModified(bool)
 */
void PasswordSafeFrame::DatabaseModified(bool modified)
{
  if (!modified)
    return;

  if (m_core.HaveDBPrefsChanged()) {
    // TODO: Anything that needs to be handled here?
  }
  else if (m_core.HasDBChanged()) {  //"else if" => both DB and it's prefs can't change at the same time
    // TODO: Anything that needs to be handled here?
  } else {
    wxFAIL_MSG(wxT("What changed in the DB if not entries or preferences?"));
  }

  // Save Immediately if user requested it
  if (PWSprefs::GetInstance()->GetPref(PWSprefs::SaveImmediately)) {
    SaveImmediately();
  }
}

/**
 * Implements Observer::UpdateGUI(UpdateGUICommand::GUI_Action, const pws_os::CUUID&, CItemData::FieldType)
 */
void PasswordSafeFrame::UpdateGUI(UpdateGUICommand::GUI_Action ga, const CUUID & WXUNUSED(entry_uuid), CItemData::FieldType WXUNUSED(ft))
{
  // Callback from PWScore if GUI needs updating

  // "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

#ifdef NOTYET
  PWSprefs *prefs = PWSprefs::GetInstance();
#endif
  switch (ga) {
    case UpdateGUICommand::GUI_ADD_ENTRY:
      // Handled by individual views.
    case UpdateGUICommand::GUI_DELETE_ENTRY:
      // Handled by individual views.
    case UpdateGUICommand::GUI_REFRESH_ENTRY:
      // Handled by individual views.
    case UpdateGUICommand::GUI_REFRESH_ENTRYFIELD:
      // Handled by individual views.
    case UpdateGUICommand::GUI_REFRESH_ENTRYPASSWORD:
      // Handled by individual views.
      
      // But on Filter active the GUI must be updated
      if(m_bFilterActive) {
        RebuildGUI();
      }
      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;
    case UpdateGUICommand::GUI_UPDATE_STATUSBAR:
      // TODO: UpdateToolBarDoUndo();
      UpdateStatusBar();
      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
      GetEventHandler()->ProcessEvent(evt);
      RefreshViews();
      break;
    }
    default:
      break;
  }
}

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

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

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

  //Save alerts the user
  if (!m_core.HasDBChanged() || Save() == PWScore::SUCCESS) {
    // Do NOT call PWScore::ReInit as it will clear commands preventing the
    // user from undoing commands after unlocking DB

    // Clear all internal variables EXCEPT command and DB state vectors
    m_core.ClearDBData();

    // clear the application data before locking
    ClearAppData();
    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::TrayStatus::CLOSED);
}

/**
 * Unlock database
 * @param restoreUI restore opened windows after unlock
 * @param iconizeOnCancel will iconize if this parameters set to true and
 *   user canceled dialog
*/
void PasswordSafeFrame::UnlockSafe(bool restoreUI, bool iconizeOnCancel)
{
  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;
  }

  if (m_sysTray->IsLocked()) {
    DestroyWrapper<SafeCombinationPromptDlg> scpWrapper(this, m_core, towxstring(m_core.GetCurFile()));
    SafeCombinationPromptDlg* scp = scpWrapper.Get();

    switch (scp->ShowModal()) {
      case (wxID_OK):
      {
        if (ReloadDatabase(scp->GetPassword())) {
          m_sysTray->SetTrayStatus(SystemTray::TrayStatus::UNLOCKED);
        }
        else {
          // With modal dialog open we now have a problem, no data base open, but a modal dialog is open that might show an entry
          CleanupAfterReloadFailure(true);
          if(!m_hiddenWindows.empty()) {
            CloseAllWindows(&TimedTaskChain::CreateTaskChain([](){}), static_cast<CloseFlags>(CloseFlags::CLOSE_FORCED|CloseFlags::LEAVE_MAIN), nullptr);
          }
          return;
        }
        break;
      }
      case (wxID_CANCEL):
      {
        if (!IsIconized() && iconizeOnCancel) {
          Iconize();
        }
        return; // allow to cancel dialog and left locked in any case
      }
      default:
        wxASSERT_MSG(false, wxT("Unexpected SafeCombinationPromptDlg result"));
        break;
    }

    if (m_savedDBPrefs != wxEmptyString) {
      PWSprefs::GetInstance()->Load(tostringx(m_savedDBPrefs));
      m_savedDBPrefs = wxEmptyString;
    }
  }

  if (restoreUI) {
    ShowHiddenWindows(true);

    if (IsIconized()) {
      Iconize(false);
    }
    Show(true); //show the grid/tree
    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();
    // for some reason, we have to restore main frame's position after Yeild, otherwise it could be restored at wrong position when window was moved before lock
    wxGetApp().RestoreFrameCoords();
  }
  else if (IsShown()) { /* if it is somehow visible, show it correctly */
    Show(true);
  }

  CreateMenubar(); // Recreate menubar to replace menu item 'Unlock' by 'Lock'
  RefreshToolbarButtons();
  UpdateSearchBarVisibility();
  m_AuiManager.Update();
}

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

void PasswordSafeFrame::OnIconize(wxIconizeEvent& evt) {

  // If database was closed than there is nothing to do
  if (!m_core.IsDbFileSet()) {
    return;
  }

  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::TrayStatus::UNLOCKED && lockOnMinimize) {
      pws_os::Trace0(L"OnIconize: will LockDb()\n");
#if wxCHECK_VERSION(2,9,5)
      CallAfter(&PasswordSafeFrame::LockDb);
#else
      LockDb();
#endif
    }
    else {
      m_guiInfo->Save(this);
    }
    if (PWSprefs::GetInstance()->GetPref(PWSprefs::ClearClipboardOnMinimize)) {
      Clipboard::GetInstance()->ClearCBData();
    }
  }
  else{
      CallAfter(&PasswordSafeFrame::UnlockSafe, true, true);
  }
}

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)) {
    Clipboard::GetInstance()->ClearCBData();
  }

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

  if (lock && m_sysTray->GetTrayStatus() == SystemTray::TrayStatus::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();
    HideTopLevelWindows();
  }
}

void PasswordSafeFrame::IconizeOrHideAndLock()
{
  CloseAllWindows(&TimedTaskChain::CreateTaskChain([](){}),
    static_cast<CloseFlags>(CloseFlags::CLOSE_FORCED|CloseFlags::LEAVE_MAIN),
    [this](bool success) {
      if (PWSprefs::GetInstance()->GetPref(PWSprefs::UseSystemTray)) {
        if (!m_sysTray->IsLocked()) {
          HideUI(true);
        }
      }
      else {
        TryIconize();
      }

      // If not already locked by HideUI or OnIconize due to user preference than do it now
      if (m_sysTray->GetTrayStatus() == SystemTray::TrayStatus::UNLOCKED) {
        LockDb();
      }
    }
  );
}

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 (SaveAndClearDatabaseOnLock()) {
    m_sysTray->SetTrayStatus(SystemTray::TrayStatus::LOCKED);

    CreateMenubar(); // Recreate menubar to replace menu item 'Lock' by 'Unlock'
    RefreshToolbarButtons();
  }

  // Hide search bar to not populate any search results (see GitHub issue 375)
  GetSearchBarPane().Hide();
  m_AuiManager.Update();
}

void PasswordSafeFrame::SetTrayStatus(bool locked)
{
  m_sysTray->SetTrayStatus(locked ? SystemTray::TrayStatus::LOCKED : SystemTray::TrayStatus::UNLOCKED);
}

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

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

void PasswordSafeFrame::OnOpenRecentDB(wxCommandEvent& evt)
{
  RecentDbList& db = wxGetApp().recentDatabases();
  const size_t index = evt.GetId() - db.GetBaseId();
  const wxString dbfile = db.GetHistoryFile(index);
  switch(Open(dbfile))
  {
    case PWScore::SUCCESS:
      m_core.ResumeOnDBNotification();
      CreateMenubar();  // Recreate the menu with updated list of most recently used DBs
      break;

    case PWScore::ALREADY_OPEN:
      break;            // An already open DB doesn't need to be removed from history

    case PWScore::USER_CANCEL:
      //In case the file doesn't exist, user will have to cancel
      //the master password entry box.  In that call, fall through
      //to the default case of removing the file from history
      if (pws_os::FileExists(stringT(dbfile)))
        break;          // An existing file doesn't need to be removed from history

      //[[fallthrough]];
    default:
      wxMessageBox(wxString(_("There was an error loading the database: ")) << dbfile,
                     _("Could not load database"), wxOK|wxICON_ERROR, this);
      db.RemoveFileFromHistory(index);
      CreateMenubar();  // Update menu so that not existing file doesn't appear on File menu anymore
      break;
  }
}

void PasswordSafeFrame::ViewReport(CReport& rpt)
{
  ShowModalAndGetResult<ViewReportDlg>(this, &rpt);
}

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

void PasswordSafeFrame::UpdateStatusBar()
{
  wxMenuBar* menuBar = GetMenuBar();
  wxMenuItem *menu = nullptr;
  if(menuBar != nullptr) {
    menu = menuBar->FindItem(ID_CHANGEMODE);
  }
  if (m_core.IsDbFileSet()) {
    wxString text;
    // SB_DBLCLICK pane is set per selected entry, not here

    m_statusBar->SetStatusText(m_LastClipboardAction, StatusBar::Field::CLIPBOARDACTION);

    text  = m_core.HasDBChanged()       ? wxT("*") : wxT(" ");
    text += m_core.HaveDBPrefsChanged() ? wxT("°") : wxT(" ");
    m_statusBar->SetStatusText(text, StatusBar::Field::MODIFIED);

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

    text.Clear(); text <<  m_core.GetNumEntries();
    m_statusBar->SetStatusText(text, StatusBar::Field::NUM_ENT);

    text = m_bFilterActive ? wxT("[F]") : wxT("   ");
    m_statusBar->SetStatusText(text, StatusBar::Field::FILTER);
    
    // Update Menu text
    if(menu != nullptr)
      menu->SetItemLabel(m_core.IsReadOnly() ? _("Change to R/W") : _("Change to R-O"));
  }
  else { // no open file
    m_statusBar->SetStatusText(_(PWSprefs::GetDCAdescription(-1)), StatusBar::Field::DOUBLECLICK);
    m_statusBar->SetStatusText(wxEmptyString, StatusBar::Field::CLIPBOARDACTION);
    m_statusBar->SetStatusText(wxEmptyString, StatusBar::Field::MODIFIED);
    m_statusBar->SetStatusText(wxEmptyString, StatusBar::Field::READONLY);
    m_statusBar->SetStatusText(wxEmptyString, StatusBar::Field::NUM_ENT);
    m_statusBar->SetStatusText(wxEmptyString, StatusBar::Field::FILTER);
    // Update Menu text
    if(menu != nullptr)
      menu->SetItemLabel(_("Change R/W Mode"));
  }
}

void PasswordSafeFrame::UpdateMenuBar()
{
  // Add code here for more complex update logic on menu items, otherwise use OnUpdateUI
  
  // Menu Item ID_CHANGEMODE "Change R/W Mode" is updated in UpdateStatusBar(), as always synchonized with open/closed and read-mode
}

void PasswordSafeFrame::UpdateLastClipboardAction(const CItemData::FieldType field)
{
  // Note use of CItemData::RESERVED for indicating in the
  // Status bar that an old password has been copied
  m_LastClipboardAction = wxEmptyString;
  switch (field) {
    case CItemData::FieldType::GROUP:
      m_LastClipboardAction = _("Group copied ") + wxDateTime::Now().FormatTime();
      break;
    case CItemData::FieldType::TITLE:
      m_LastClipboardAction = _("Title copied ") + wxDateTime::Now().FormatTime();
      break;
    case CItemData::FieldType::USER:
      m_LastClipboardAction = _("User copied ") + wxDateTime::Now().FormatTime();
      break;
    case CItemData::FieldType::PASSWORD:
      m_LastClipboardAction = _("Pswd copied ") + wxDateTime::Now().FormatTime();
      break;
    case CItemData::FieldType::TOTPCONFIG:
      m_LastClipboardAction = _("Auth Code copied ") + wxDateTime::Now().FormatTime();
      break;
    case CItemData::FieldType::NOTES:
      m_LastClipboardAction = _("Notes copied ") + wxDateTime::Now().FormatTime();
      break;
    case CItemData::FieldType::URL:
      m_LastClipboardAction = _("URL copied ") + wxDateTime::Now().FormatTime();
      break;
    case CItemData::FieldType::AUTOTYPE:
      m_LastClipboardAction = _("Autotype copied ") + wxDateTime::Now().FormatTime();
      break;
    case CItemData::FieldType::RUNCMD:
      m_LastClipboardAction = _("RunCmd copied ") + wxDateTime::Now().FormatTime();
      break;
    case CItemData::FieldType::EMAIL:
      m_LastClipboardAction = _("Email copied ") + wxDateTime::Now().FormatTime();
      break;
    case CItemData::FieldType::PWHIST:
      m_LastClipboardAction = _("Password History copied " ) + wxDateTime::Now().FormatTime();
      break;
    case CItemData::FieldType::RESERVED:
      m_LastClipboardAction = _("Old Pswd copied ") + wxDateTime::Now().FormatTime();
      break;
    case CItemData::FieldType::END:
      m_LastClipboardAction = wxEmptyString;
      break;
    default:
      ASSERT(0);
      return;
  }

  m_LastAction = field;
  UpdateStatusBar();
}

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

  if (pci != nullptr) {
    pci->GetDCA(dca);
    if (dca == -1)
      dca = PWSprefs::GetInstance()->GetPref(PWSprefs::DoubleClickAction);
  }
  m_statusBar->SetStatusText(_(PWSprefs::GetDCAdescription(dca)), StatusBar::Field::DOUBLECLICK);
}

void PasswordSafeFrame::ChangeFontPreference(const PWSprefs::StringPrefs fontPreference)
{
  wxFont currentFont(towxstring(PWSprefs::GetInstance()->GetPref(fontPreference)));
  wxFont newFont;

  switch (fontPreference)
  {
  case (PWSprefs::StringPrefs::TreeFont):
  {
    if (!currentFont.IsOk()) {
      currentFont = IsTreeView() ? m_tree->GetFont() : m_grid->GetDefaultCellFont();
    }

    newFont = ::wxGetFontFromUser(this, currentFont, _("Select Tree/List display font"));

    if (newFont.IsOk()) {
      if (IsTreeView()) {
        m_tree->SetFont(newFont);
        m_tree->Refresh(); // Updates the tree items font
      }
      else {
        m_grid->SetDefaultCellFont(newFont);
        m_grid->Refresh(); // Updates the grid items font
      }
    }
  }
  break;

  case (PWSprefs::StringPrefs::AddEditFont):
    newFont = ::wxGetFontFromUser(this, currentFont.IsOk() ? currentFont : GetFont(), _("Select Add/Edit display font"));
    break;

  case (PWSprefs::StringPrefs::PasswordFont):
    newFont = ::wxGetFontFromUser(this, currentFont.IsOk() ? currentFont : GetFont(), _("Select Password display font"));
    break;

  case (PWSprefs::StringPrefs::NotesFont):
    newFont = ::wxGetFontFromUser(this, currentFont.IsOk() ? currentFont : GetFont(), _("Select Notes display font"));
    break;

  case (PWSprefs::StringPrefs::VKeyboardFontName):
    newFont = ::wxGetFontFromUser(this, currentFont.IsOk() ? currentFont : GetFont(), _("Select Virtual Keyboard font"));
    break;

  default:
    return;
  }

  if (newFont.IsOk())
  {
    PWSprefs::GetInstance()->SetPref(fontPreference, tostringx(newFont.GetNativeFontInfoDesc()));
  }
}

void PasswordSafeFrame::SaveLayoutPreferences()
{
  /*
    The AUI manager provides a string with positional, size and docking
    information for each pane that is managed by the AUI manager.
    See the following for managed panes:
      - PasswordSafeFrame::CreateControls()
      - PasswordSafeFrame::CreateDragBar()
      - PasswordSafeFrame::CreateMainToolbar()
      - PasswordSafeFrame::CreateSearchBar()
  */
  auto layoutPreferences = m_AuiManager.SavePerspective();

  /*
    Example AUI layout preferences string with added line breaks at each occurence of the '|' character:
    layout2|
    name=mainview;caption=Main View;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1|
    name=maintoolbar;caption=Toolleiste;state=2106044;dir=2;layer=10;row=0;pos=0;prop=100000;bestw=114;besth=358;minw=-1;minh=25;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1|
    name=dragbar;caption=Dragleiste;state=2106044;dir=4;layer=10;row=0;pos=0;prop=100000;bestw=26;besth=207;minw=-1;minh=25;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1|
    name=searchbar;caption=Suchleiste;state=2105984;dir=3;layer=1;row=0;pos=0;prop=100000;bestw=10;besth=35;minw=-1;minh=35;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1|
    dock_size(5,0,0)=22|
    dock_size(4,10,0)=28|
    dock_size(2,10,0)=116|
    dock_size(3,1,0)=37|
  */
  layoutPreferences.Replace("|", "|\n");
  PWSprefs::GetInstance()->SetPrefLayout(tostdstring(layoutPreferences));
}

bool PasswordSafeFrame::LoadLayoutPreferences()
{
  auto layoutPreferences = PWSprefs::GetInstance()->GetPrefLayout();

  if (layoutPreferences.empty()) {
    // If there are no layout preferences in the configuration file 
    // we continue with the default settings/behavior of wxAUI.
    return true;
  }

  wxString auiLayoutPreferences = towxstring(layoutPreferences);
  auiLayoutPreferences.Replace("|\n", "|");
  return m_AuiManager.LoadPerspective(auiLayoutPreferences);
}

void PasswordSafeFrame::SetFilterFindEntries(UUIDVector *pvFoundUUIDs)
{
  // If the "Show entries from last Find" is active, we should not change this
  // as it will override the results - say if the user maximizes the PWS window
  // and the view is Refreshed using these entries rather than the original list
  // used for the filter.
  if (!(m_bFilterActive && m_CurrentPredefinedFilter == LASTFIND))
    m_FilterManager.SetFilterFindEntries(pvFoundUUIDs);
}

void PasswordSafeFrame::ResetFilters()
{ // Tidy up filters
  CurrentFilter().Empty();
  m_bFilterActive = false;
  if(m_tree)
    m_tree->SetFilterActive(false);
  m_CurrentPredefinedFilter = NONE;
  m_FilterManager.SetFindFilter(false);
  m_FilterManager.SetFilterFindEntries(nullptr);
}

/**
 Close all visible top level windows
 
 @param taskChain chain to use when closing multiple dialogs
 @param flags close options
 @param onFinish if set, will be called after closing all windows
 
 Here we have to use taskChain, because when closing multiple dialogs, 
 CallAfter is not enough to allow dialog to finish result processing and break event loop
*/
void PasswordSafeFrame::CloseAllWindows(TimedTaskChain* taskChain, CloseFlags flags, std::function<void(bool success)> onFinish)
{
  // we should show windows before processing
  bool delayClose = false;
  if (!m_hiddenWindows.empty()) {
    ShowHiddenWindows(false);
    flags = static_cast<CloseFlags>(flags | HIDE_ON_VETO);
    delayClose = true;
  }

  if (!m_closeDisabler) {
    m_closeDisabler = new wxWindowDisabler();
    m_pengingCloseWindow = nullptr;
    if (delayClose) {
      // some windows (such as wxHtmlHelpDialog need more cycles for activation), delay close processing after show
      taskChain->then([taskChain, flags, onFinish, this]{
        CloseAllWindows(taskChain, flags, onFinish);
      });
      return; // exit here without unlocking disabler
    }
  }
  
  auto tlvList = GetTopLevelWindowsList();
  auto itr = tlvList.rbegin();
  const auto endItr = ((flags & CloseFlags::LEAVE_MAIN) == CloseFlags::LEAVE_MAIN) ? --tlvList.rend() : tlvList.rend();
  const auto lastWindowItr = --tlvList.rend();
  bool vetoed = false;
  while (itr != endItr) {
    wxTopLevelWindow* win = *itr;
    // In case we got here via the About Dialog window, don't close it here. It will get special handling later.
    if (win && (dynamic_cast<AboutDlg *>(win) == nullptr)) {
      if (win->IsShown()) {
        if (win == m_pengingCloseWindow) { // close already sheduled, but still not done
          pws_os::Trace(L"Waiting for window close <%ls> (%ls), flags=%d\n", ToStr(win->GetTitle()), ToStr(win->GetName()), flags);
          taskChain->then([taskChain, flags, onFinish, this]{
            CloseAllWindows(taskChain, flags, onFinish);
          });
          return; // exit here without unlocking disabler
        }

        pws_os::Trace(L"Closing <%ls> (%ls), flags=%d\n", ToStr(win->GetTitle()), ToStr(win->GetName()), flags);
        m_pengingCloseWindow = win;
        if (win->Close((flags & CloseFlags::CLOSE_FORCED) == CloseFlags::CLOSE_FORCED)) {
          if (itr == lastWindowItr) {
            // main windows closed, no need to schedule new check
            break;
          }
          else {
            // if main window still alive, captured "this" should be valid
            taskChain->then([taskChain, flags, onFinish, this]{
              CloseAllWindows(taskChain, flags, onFinish);
            });
            return; // exit here without unlocking disabler
          }
        }
        else {
          pws_os::Trace(L"Close for <%ls> (%ls) vetoed, flags=%d\n", ToStr(win->GetTitle()), ToStr(win->GetName()), flags);
          vetoed = true;
          break;
        }
      }
      else {
        pws_os::Trace(L"Skip closing of hidden window <%ls> (%ls)\n", ToStr(win->GetTitle()), ToStr(win->GetName()));
      }
    }
    ++itr;
  }
  delete m_closeDisabler;
  m_closeDisabler = nullptr;
  m_pengingCloseWindow = nullptr;
  
  if (vetoed && (flags & CloseFlags::HIDE_ON_VETO) == CloseFlags::HIDE_ON_VETO) {
    HideTopLevelWindows();
  }
  
  if (onFinish) {
    onFinish(!vetoed);
  }
}

bool PasswordSafeFrame::IsCloseInProgress() const
{
  return !!m_closeDisabler;
}


void PasswordSafeFrame::CloseDB(std::function<void(bool)> callback)
{
  PWSprefs *prefs = PWSprefs::GetInstance();

  // Save Application related preferences
  prefs->SaveApplicationPreferences();
  if( m_core.IsDbFileSet() ) {
    int rc = SaveIfChanged();
    if (rc != PWScore::SUCCESS) {
      if (callback != nullptr)
        CallAfter([callback]() {callback(false);});
      return;
    }

    // Force close all active dialogs
    CloseAllWindows(&TimedTaskChain::CreateTaskChain([](){}),
      CloseFlags::LEAVE_MAIN,
      [this, callback](bool success) { // we don't close main window, so `this` will be valid
        if (success) {
          m_core.SafeUnlockCurFile();
          m_core.SetCurFile(wxEmptyString);
          // Reset core and clear ALL associated data
          m_core.ReInit();
          // clear the application data before ending
          ClearAppData();
          SetTitle(wxEmptyString);
          m_sysTray->SetTrayStatus(SystemTray::TrayStatus::CLOSED);
          // Preserve user preference, which gets overwritten by closing the search bar.
          const auto showSearchBar = PWSprefs::GetInstance()->GetPref(PWSprefs::FindToolBarActive);
          wxCommandEvent dummyEv;
          m_search->OnSearchClose(dummyEv); // fix github issue 375
          m_core.SetReadOnly(false);
          UpdateStatusBar();
          UpdateMenuBar();
          PWSprefs::GetInstance()->SetPref(PWSprefs::FindToolBarActive, showSearchBar);
        }
        else {
          wxMessageBox(_("Can't close database. There are unsaved changes in opened dialogs."), wxTheApp->GetAppName(), wxOK | wxICON_WARNING, this);
        }
        if (callback) {
          CallAfter([callback, success]() {callback(success);});
        }
      });
  }
  else {
    if (callback) {
      CallAfter([callback]() {callback(true);});
    }
  }
}

std::vector<wxTopLevelWindow*> PasswordSafeFrame::GetTopLevelWindowsList() const
{
  std::vector<wxTopLevelWindow*> hiddenWindows;
  hiddenWindows.reserve(wxTopLevelWindows.size());
  // wxTopLevelWindows is global exported list of top level windows
  for (const auto tlv : wxTopLevelWindows) {
    auto* win = wxDynamicCast(tlv, wxTopLevelWindow);
    if (win && win->IsShown()) {
      hiddenWindows.push_back(win);
    }
  }
  // but some dialogs aren't registered in wxTopLevelWindows, so get them from dialog tracker
  // it's O(n^2), but usually we have less than 10
  for (const auto& dialog : m_shownDialogs) {
    if (!dialog) {
      continue;
    }
    bool shown = dialog->IsShown();
#if defined(__WXGTK__)
    if (!shown && wxDynamicCast(dialog, wxMessageDialog)) {
      // wxMessageDialog don't set needed m_shown and m_modalShowing flags in ShowModal, so just log it
      // (we even can't close them, because m_modalShowing isn't set and Close event isn't processed)
      pws_os::Trace(L"Message dialog <%ls> (%ls) can't be hidden/closed\n", ToStr(dialog->GetTitle()), ToStr(dialog->GetName()));
      continue;
    }
#endif
    if (shown) {
      bool registered = std::find(hiddenWindows.begin(), hiddenWindows.end(), dialog) != hiddenWindows.end();
      if (!registered) {
        pws_os::Trace(L"Dialog not registered in wxTopLevelWindows <%ls> (%ls)\n", ToStr(dialog->GetTitle()), ToStr(dialog->GetName()));
        // attempt to register window after it's parent
        const auto parent = dialog->GetParent();
        auto parentIter = std::find(hiddenWindows.begin(), hiddenWindows.end(), parent);
        if (parentIter != hiddenWindows.end()) {
          ++parentIter;
        }
        hiddenWindows.insert(parentIter, dialog);
      }
    }
  }
  return hiddenWindows;
}

void PasswordSafeFrame::HideTopLevelWindows()
{
  auto windows = GetTopLevelWindowsList();
  // hiding windows in reverse order
  for (auto itr = windows.rbegin(); itr != windows.rend(); ++itr) {
    wxTopLevelWindow* win = *itr;
    //Don't call Hide() here, which just calls Show(false), which is overridden in
    //derived classes, and wxDialog actually cancels the modal loop and closes the window
    pws_os::Trace(L"Hide window <%ls> (%ls)\n", ToStr(win->GetTitle()), ToStr(win->GetName()));
    win->ClearBackground();
    win->Show(false);
  }
  m_hiddenWindows = std::move(windows);
}

void PasswordSafeFrame::ShowHiddenWindows(bool raise)
{
  for(const auto win : m_hiddenWindows) {
    pws_os::Trace(L"Show window <%ls>\n", ToStr(win->GetTitle()));
    win->Show(true);
    if (raise) {
      win->Raise();
    }
    win->Update();
  }
  m_hiddenWindows.clear();
}

wxTopLevelWindow* PasswordSafeFrame::GetTopWindow() const {
  if (!m_hiddenWindows.empty()) {
    return m_hiddenWindows.back();
  }
  else {
    auto windows = GetTopLevelWindowsList();
    if (!windows.empty()) {
      return windows.back();
    }
  }
  return nullptr;
}

int PasswordSafeFrame::Enter(wxDialog* dialog)
{
  if (dialog) {
    m_shownDialogs.push_back(dialog);
  }
  return wxID_NONE;
}

void PasswordSafeFrame::Exit(wxDialog* dialog)
{
  // modal dilaogs should be closed in reverse show order
  if (!m_shownDialogs.empty() && m_shownDialogs.back() == dialog) {
    m_shownDialogs.pop_back();
  }
  else {
    wxASSERT_MSG(false, wxT("Closed dialog isn't on top of the list "));
    auto iter = std::find(m_shownDialogs.begin(), m_shownDialogs.end(), dialog);
    if (iter != m_shownDialogs.end()) {
      m_shownDialogs.erase(iter);
    }
  }
}

/**
@return true, when opened dialogs can be safely closed
*/
bool PasswordSafeFrame::CanCloseDialogs() const
{
#if defined(__WXGTK__)
    for (const auto& dialog : m_shownDialogs) {
      if (!dialog) {
        continue;
      }
      if (!dialog->IsShown() && wxDynamicCast(dialog, wxMessageDialog)) {
        // we can't close wxMessageDialog in wx3.0.5 GTK, see details in GetTopLevelWindowsList
        pws_os::Trace(L"Message dialog <%ls> (%ls) can't be hidden/closed\n", ToStr(dialog->GetTitle()), ToStr(dialog->GetName()));
        return false;
      }
    }
#endif
  return true;
}

  //-----------------------------------------------------------------
  // 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??)
  //-----------------------------------------------------------------
