//////////////////////////////////////////////////////////////////////////
//
// pgAdmin III - PostgreSQL Tools
//
// Copyright (C) 2002 - 2016, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// frmMain.cpp - The main form
//
// Note: Due to the size of frmMain, event handler, browser and statistics
//       functions are in events.cpp.
//
//////////////////////////////////////////////////////////////////////////

#include "pgAdmin3.h"

#ifndef WIN32
#include <unistd.h>
#endif

// wxWindows headers
#include <wx/wx.h>
#include <wx/settings.h>
#include <wx/treectrl.h>
#include <wx/listctrl.h>
#include <wx/notebook.h>
#include <wx/toolbar.h>
#include <wx/imaglist.h>
#include <wx/busyinfo.h>
#include <wx/sysopt.h>
#include <wx/textbuf.h>
#include <wx/clipbrd.h>

// wxAUI
#include <wx/aui/aui.h>

// App headers
#include "utils/misc.h"
#include "frm/menu.h"
#include "utils/pgfeatures.h"
#include "debugger/debugger.h"
#include "frm/frmMain.h"
#include "ctl/ctlMenuToolbar.h"
#include "ctl/ctlSQLBox.h"
#include "db/pgConn.h"
#include "db/pgSet.h"
#include "agent/pgaJob.h"
#include "schema/pgDatabase.h"
#include "schema/pgServer.h"
#include "schema/pgObject.h"
#include "schema/pgCollection.h"
#include "frm/frmOptions.h"
#include "frm/frmAbout.h"
#include "frm/frmHint.h"
#include "frm/frmGrantWizard.h"
#include "frm/frmMainConfig.h"
#include "frm/frmHbaConfig.h"
#include "frm/frmPgpassConfig.h"
#include "frm/frmBackup.h"
#include "frm/frmBackupGlobals.h"
#include "frm/frmBackupServer.h"
#include "frm/frmRestore.h"
#include "frm/frmReport.h"
#include "frm/frmMaintenance.h"
#include "frm/frmStatus.h"
#include "frm/frmLog.h"
#include "frm/frmAwr.h"
#include "frm/frmPassword.h"
#ifdef DATABASEDESIGNER
#include "frm/frmDatabaseDesigner.h"
#endif
#include "frm/frmQuery.h"
#include "frm/frmEditGrid.h"
#include "frm/frmImport.h"
#include "dlg/dlgServer.h"
#include "dlg/dlgDatabase.h"
#include "dlg/dlgSearchObject.h"
#include "schema/pgTable.h"
#include "schema/pgView.h"
#include "schema/pgFunction.h"
#include "schema/pgIndex.h"
#include "schema/pgTrigger.h"
#include "pro_scheduler/pgproJob.h"
#include "schema/pgRole.h"
#include "schema/pgRule.h"
#include "schema/pgServer.h"
#include "schema/pgTablespace.h"
#include "slony/slCluster.h"
#include "slony/slSet.h"
#include "schema/pgForeignKey.h"
#include "schema/pgCheck.h"
#include "schema/pgDomain.h"
#include "schema/pgEventTrigger.h"
#include "ctl/ctlShortCut.h"


#ifdef WIN32
#include <wx/msw/ole/automtn.h>
#endif
#include "utils/utffile.h"

#if defined(HAVE_OPENSSL_CRYPTO) || defined(HAVE_GCRYPT)
#include "utils/sshTunnel.h"
#endif

#if wxDIALOG_UNIT_COMPATIBILITY
#error wxWindows must be compiled with wxDIALOG_UNIT_COMPATIBILITY=0!
#endif

#if defined(HAVE_OPENSSL_CRYPTO) || defined(HAVE_GCRYPT)
DEFINE_EVENT_TYPE(SSH_TUNNEL_ERROR_EVENT);
#endif

frmMain::frmMain(const wxString &title)
	: pgFrame((wxFrame *)NULL, title)
{
	msgLevel = 0;
	lastPluginUtility = NULL;
	pluginUtilityCount = 0;
	m_refreshing = false;
	Logfrm = NULL;
	dlgName = wxT("frmMain");
	SetMinSize(wxSize(600, 450));
	RestorePosition(50, 50, 750, 550, 600, 450);

	SetFont(settings->GetSystemFont());

	{
		wxLogInfo(wxT("Using fontmetrics %d/%d, %d Point"), GetCharWidth(), GetCharHeight(), GetFont().GetPointSize());
		wxLogInfo(wxT("Native Description '%s'"), GetFont().GetNativeFontInfoDesc().c_str());
		wxWindowDC dc(this);
		dc.SetFont(GetFont());

		wxCoord w, h, d, e;

		dc.GetTextExtent(wxT("M"), &w, &h, &d, &e);
		wxLogInfo(wxT("Draw size of 'M': w=%d, h=%d, descent %d, external lead %d."), w, h, d, e);

		dc.GetTextExtent(wxT("g"), &w, &h, &d, &e);
		wxLogInfo(wxT("Draw size of 'g': w=%d, h=%d, descent %d, external lead %d."), w, h, d, e);

		dc.GetTextExtent(wxT("Mg"), &w, &h, &d, &e);
		wxLogInfo(wxT("Draw size of 'Mg': w=%d, h=%d, descent %d, external lead %d."), w, h, d, e);
	}

	// Current database
	denyCollapseItem = wxTreeItemId();
	currentObject = 0;

	appearanceFactory->SetIcons(this);

	// notify wxAUI which frame to use
	manager.SetManagedWindow(this);
	manager.SetFlags(wxAUI_MGR_DEFAULT | wxAUI_MGR_TRANSPARENT_DRAG | wxAUI_MGR_ALLOW_ACTIVE_PANE);

	// wxGTK needs this deferred
	pgaFactory::RealizeImages();

	CreateMenus();

	// Setup the object browser
	browser = new ctlTree(this, CTL_BROWSER, wxDefaultPosition, wxDefaultSize, wxTR_HAS_BUTTONS | wxSIMPLE_BORDER);
	browser->SetImageList(imageList);

	// Setup the listview
	listViews = new ctlAuiNotebook(this, CTL_NOTEBOOK, wxDefaultPosition, wxDefaultSize, wxAUI_NB_TOP | wxAUI_NB_TAB_SPLIT | wxAUI_NB_TAB_MOVE | wxAUI_NB_TAB_EXTERNAL_MOVE | wxAUI_NB_SCROLL_BUTTONS | wxAUI_NB_WINDOWLIST_BUTTON);

	// Switch to the generic list control. Native doesn't play well with
	// multi-row select on Mac.
#ifdef __WXMAC__
	wxSystemOptions::SetOption(wxT("mac.listctrl.always_use_generic"), true);
#endif

	properties = new ctlListView(listViews, CTL_PROPVIEW, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER);
	statistics = new ctlListView(listViews, CTL_STATVIEW, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER);
	dependencies = new ctlListView(listViews, CTL_DEPVIEW, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER);
	dependents = new ctlListView(listViews, CTL_REFVIEW, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER);
#if !defined(NO_WXJSON_GIT)
#if wxUSE_WEBREQUEST

	git = NULL;
	wxJSONValue cfg=ctlGitPanel::GetConfig();
	if (!cfg.IsNull()) git = new ctlGitPanel(listViews, this,cfg);
#endif
#endif


	// Switch back to the native list control.
#ifdef __WXMAC__
	wxSystemOptions::SetOption(wxT("mac.listctrl.always_use_generic"), false);
#endif

	listViews->AddPage(properties, _("Properties"));	// NBP_PROPERTIES
	listViews->AddPage(statistics, _("Statistics"));	// NBP_STATISTICS
	listViews->AddPage(dependencies, _("Dependencies"));    // NBP_DEPENDENCIES
	listViews->AddPage(dependents, _("Dependents"));	// NBP_DEPENDENTS
#if !defined(NO_WXJSON_GIT)
#if wxUSE_WEBREQUEST
	if (git) {
		listViews->AddPage(git, _("Git"));	// 
	}
#endif
#endif
	properties->SetImageList(imageList, wxIMAGE_LIST_SMALL);
	statistics->SetImageList(imageList, wxIMAGE_LIST_SMALL);
	dependencies->SetImageList(imageList, wxIMAGE_LIST_SMALL);
	dependents->SetImageList(imageList, wxIMAGE_LIST_SMALL);

	wxColour background;
	background = wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE);
	statistics->SetBackgroundColour(background);
	dependencies->SetBackgroundColour(background);
	dependents->SetBackgroundColour(background);

	// Setup the SQL pane
	sqlPane = new ctlSQLBox(this, CTL_SQLPANE, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxSIMPLE_BORDER | wxTE_READONLY | wxTE_RICH2);
	sqlPane->SetBackgroundColour(background);

	// Setup menus
	pgaFactory::RegisterMenu(this, wxCommandEventHandler(frmMain::OnNew));
	menuFactories->RegisterMenu(this, wxCommandEventHandler(frmMain::OnAction));
	menuFactories->CheckMenu(0, menuBar, toolBar);
	wxSize toolw = toolBar->GetBestSize();
	// Kickstart wxAUI
	manager.AddPane(browser, wxAuiPaneInfo().Name(wxT("objectBrowser")).Caption(_("Object browser")).Left().MinSize(wxSize(100, 200)).BestSize(wxSize(200, 450)));
	manager.AddPane(listViews, wxAuiPaneInfo().Name(wxT("listViews")).Caption(_("Info pane")).Center().CaptionVisible(false).CloseButton(false).MinSize(wxSize(200, 100)).BestSize(wxSize(400, 200)));
	manager.AddPane(sqlPane, wxAuiPaneInfo().Name(wxT("sqlPane")).Caption(_("SQL pane")).Bottom().MinSize(wxSize(200, 100)).BestSize(wxSize(400, 200)));
	manager.AddPane(toolBar, wxAuiPaneInfo().Name(wxT("toolBar")).Caption(_("Tool bar")).ToolbarPane().Top().LeftDockable(false).RightDockable(false));

	// Now load the layout
	wxString perspective;
	settings->Read(wxT("frmMain/Perspective-") + wxString(FRMMAIN_PERSPECTIVE_VER), &perspective, FRMMAIN_DEFAULT_PERSPECTIVE);
	manager.LoadPerspective(perspective, true);

	// and reset the captions for the current language
	manager.GetPane(wxT("objectBrowser")).Caption(_("Object browser"));
	manager.GetPane(wxT("listViews")).Caption(_("Info pane"));
	manager.GetPane(wxT("sqlPane")).Caption(_("SQL pane"));
	manager.GetPane(wxT("toolBar")).Caption(_("Tool bar"));
	manager.GetPane(wxT("toolBar")).BestSize(toolw);

	// Sync the View menu options
	viewMenu->Check(MNU_SQLPANE, manager.GetPane(wxT("sqlPane")).IsShown());
	viewMenu->Check(MNU_OBJECTBROWSER, manager.GetPane(wxT("objectBrowser")).IsShown());
	viewMenu->Check(MNU_TOOLBAR, manager.GetPane(wxT("toolBar")).IsShown());

	ResetLists();

	// tell the manager to "commit" all the changes just made
	manager.Update();

	// Add the root node
	serversObj = new pgServerCollection(serverFactory.GetCollectionFactory());
	wxTreeItemId root = browser->AddRoot(_("Server Groups"), serversObj->GetIconId(), -1, serversObj);

	// Work around a bug in the generic tree control in wxWidgets,
	// Per http://trac.wxwidgets.org/ticket/10085
	browser->SetItemText(root, _("Server Groups"));

	// Load servers
	RetrieveServers();
#ifndef WIN32
	if (!currentObject) currentObject=serversObj;
#endif
	browser->Expand(root);
	browser->SortChildren(root);
	browser->SetFocus();
	wxString selServerName=settings->Read(wxT("Servers/SelectItem"), "");
	if (selServerName.Len()>0) {
		wxTreeItemId sel=browser->FindItem(root,selServerName,false);
		if (sel.IsOk())  {
			browser->SelectItem(sel);
			currentObject=browser->GetObject(sel);
			//execSelChange(sel, true);
		}
	}


}


frmMain::~frmMain()
{
	// Store the servers, to ensure we store the last database/schema etc
	if (settings) {
		StoreServers();

		settings->Write(wxT("frmMain/Perspective-") + wxString(FRMMAIN_PERSPECTIVE_VER), manager.SavePerspective());

	}
	manager.UnInit();

	// Clear the treeview
	browser->DeleteAllItems();

	if (treeContextMenu)
		delete treeContextMenu;

#if defined(HAVE_OPENSSL_CRYPTO) || defined(HAVE_GCRYPT)
	if(pgadminTunnelThread && pgadminTunnelThread->IsAlive())
	{
		pgadminTunnelThread->Cleanup();
		pgadminTunnelThread = NULL;
	}
#endif
}
#include <wx/hashmap.h>
WX_DECLARE_HASH_MAP(wxString, wxBitmapBundle, wxStringHash, wxStringEqual, MyHash1);
MyHash1 bundleHash;

wxBitmapBundle GetBundleSVG(wxBitmap* std, wxString name, wxSize sz) {
	wxBitmapBundle bb;
	wxBitmapBundle *b=NULL;
	bb = bundleHash[name];
	if (bb.IsOk()) return bb;
	extern wxString dataDir;
	wxString fullpath=dataDir+ sepPath +"svg" + sepPath + name; // 1. local data application
	if (!wxFile::Exists(fullpath)) fullpath=loadPath + sepPath +"svg" + sepPath + name; // 2. execute path
	if (wxFile::Exists(fullpath)) { // name only filename.svg
		bb=wxBitmapBundle::FromSVGFile(fullpath, sz);
	}
	else if (wxFile::Exists(name)) { // name = fullpath 
		if (name.AfterLast('.') == "png") {
			wxBitmap bitmap(name, wxBITMAP_TYPE_PNG);
			if (!bitmap.Ok())
			{
				wxMessageBox(wxT("Sorry, could not load png file.\n") + name);
			}
			else {
				bb = wxBitmapBundle::FromBitmap(bitmap);
			}

		}
		else if (name.AfterLast('.') == "svg") {
			bb = wxBitmapBundle::FromSVGFile(name, sz);
		}
		
	}

	if (!bb.IsOk() && std){
		// file not found use standart bitmap
		bb = wxBitmapBundle::FromBitmap(*std);
	}
	
	if (bb.IsOk()) bundleHash[name] = bb; else {
		wxLogError("Empty bundle for %s",name);
	}
	return bb;
}

void frmMain::CreateMenus()
{
	// to add a new menu or context menu to the main window, i.e. define a possible
	// action on a pgObject, everything has to go into this method. Doing menu related
	// stuff elsewhere is plain wrong!
	// Create a proper actionFactory  (or contextActionFactory) for each of your new actions
	// in the new frmXXX.cpp and register it here.

	fileMenu = new wxMenu();
	pluginsMenu = new wxMenu();
	viewMenu = new wxMenu();
	editMenu = new wxMenu();
	newMenu = new wxMenu();
	toolsMenu = new wxMenu();
	slonyMenu = new wxMenu();
	scriptingMenu = new wxMenu();
	viewDataMenu = new wxMenu();
	debuggingMenu = new wxMenu();
	serverMenu = new wxMenu();
	reportMenu = new wxMenu();
	wxMenu *cfgMenu = new wxMenu();
	helpMenu = new wxMenu();
	newContextMenu = new wxMenu();

	toolBar = new ctlMenuToolbar(this, -1, wxDefaultPosition, wxDefaultSize, wxTB_FLAT | wxTB_NODIVIDER );
	//toolBar->SetToolBitmapSize(wxSize(32, 32));
	menuFactories = new menuFactoryList();

	// Load plugins - must do this after creating the menus and the factories
	LoadPluginUtilities();

	//--------------------------
	fileMenu->Append(MNU_SAVEDEFINITION, _("&Save Definition..."), _("Save the SQL definition of the selected object."));
	fileMenu->AppendSeparator();
	new addServerFactory(menuFactories, fileMenu, toolBar);

	viewMenu->Append(MNU_OBJECTBROWSER, _("&Object browser\tCtrl-Alt-O"),     _("Show or hide the object browser."), wxITEM_CHECK);
	viewMenu->Append(MNU_SQLPANE, _("&SQL pane\tCtrl-Alt-S"),     _("Show or hide the SQL pane."), wxITEM_CHECK);
	viewMenu->Append(MNU_TOOLBAR, _("&Tool bar\tCtrl-Alt-T"),     _("Show or hide the tool bar."), wxITEM_CHECK);
	viewMenu->AppendSeparator();
	viewMenu->Append(MNU_DEFAULTVIEW, _("&Default view\tCtrl-Alt-V"),     _("Restore the default view."));
	viewMenu->AppendSeparator();
	actionFactory *refFact = new refreshFactory(menuFactories, viewMenu, toolBar);
	new countRowsFactory(menuFactories, viewMenu, 0);
	new refreshMatViewFactory(menuFactories, viewMenu, 0);
	new refreshConcurrentlyMatViewFactory(menuFactories, viewMenu, 0);
	new executePgstattupleFactory(menuFactories, viewMenu, 0);
	new executePgstatindexFactory(menuFactories, viewMenu, 0);
	new resetIndexStatsFactory(menuFactories, viewMenu, 0);
	new executePgcheckindexFactory(menuFactories, viewMenu, 0);
	new enabledisableRuleFactory(menuFactories, toolsMenu, 0);
	new enabledisableTriggerFactory(menuFactories, toolsMenu, 0);
	new enabledisableEventTriggerFactory(menuFactories, toolsMenu, 0);
	new enabledisableJobFactory(menuFactories, toolsMenu, 0);
	new disableAllTriggersFactory(menuFactories, toolsMenu, 0);
	new enableAllTriggersFactory(menuFactories, toolsMenu, 0);
	new validateForeignKeyFactory(menuFactories, toolsMenu, 0);
	new validateCheckFactory(menuFactories, toolsMenu, 0);
	new validateDomainCheckFactory(menuFactories, toolsMenu, 0);
	toolsMenu->AppendSeparator();

	//--------------------------
	new separatorFactory(menuFactories);

	toolBar->AddSeparator();

	new passwordFactory(menuFactories, fileMenu, 0);
	fileMenu->AppendSeparator();
	optionsFactory *optFact = new optionsFactory(menuFactories, fileMenu, 0);
	fileMenu->AppendSeparator();
	new mainConfigFileFactory(menuFactories, fileMenu, 0);
	new hbaConfigFileFactory(menuFactories, fileMenu, 0);
	new pgpassConfigFileFactory(menuFactories, fileMenu, 0);

	fileMenu->AppendSeparator();
	fileMenu->Append(MNU_EXIT, _("E&xit\tCtrl-Q"),		_("Quit this program."));

	new slonyRestartFactory(menuFactories, slonyMenu, 0);
	new slonyUpgradeFactory(menuFactories, slonyMenu, 0);
	new slonyFailoverFactory(menuFactories, slonyMenu, 0);
	new slonyLockSetFactory(menuFactories, slonyMenu, 0);
	new slonyUnlockSetFactory(menuFactories, slonyMenu, 0);
	new slonyMergeSetFactory(menuFactories, slonyMenu, 0);
	new slonyMoveSetFactory(menuFactories, slonyMenu, 0);
	toolsMenu->Append(MNU_SLONY_SUBMENU, _("Slony Replication"), slonyMenu);

	propFactory = new propertyFactory(menuFactories, 0, toolBar);
	new separatorFactory(menuFactories);


	// -------------------------

	editMenu->Append(MNU_COPY, _("&Copy\tCtrl-C"),		_("Copy selected text to clipboard"));
	editMenu->AppendSeparator();

	// -------------------------

	//--------------------------

	newMenuFactory = new submenuFactory(menuFactories);     // placeholder where "New objects" submenu will be inserted
	editMenu->Append(newMenuFactory->GetId(), _("New &Object"), newMenu,    _("Create a new object."));
	editMenu->AppendSeparator();


	//--------------------------
	serverMenuFactory = new submenuFactory(menuFactories);
	toolsMenu->Append(serverMenuFactory->GetId(), _("&Server"), serverMenu,    _("Server menu commands."));

	new connectServerFactory(menuFactories, serverMenu, 0);
	new disconnectServerFactory(menuFactories, serverMenu, 0);
	new disconnectServerFactoryAll(menuFactories, serverMenu, 0);
	new disconnectDatabaseFactory(menuFactories, serverMenu, 0);

	new startServiceFactory(menuFactories, serverMenu, 0);
	new stopServiceFactory(menuFactories, serverMenu, 0);
	new reloadconfServiceFactory(menuFactories, serverMenu, 0);
	new pausereplayServiceFactory(menuFactories, serverMenu, 0);
	new resumereplayServiceFactory(menuFactories, serverMenu, 0);
	new addnamedrestorepointServiceFactory(menuFactories, serverMenu, 0);

	new keywordsServerFactory(menuFactories, serverMenu, 0);

	new createFactory(menuFactories, editMenu, toolBar);
	new dropFactory(menuFactories, editMenu, toolBar);
	new dropCascadedFactory(menuFactories, editMenu, 0);
	new truncateFactory(menuFactories, editMenu, 0);
	new truncateCascadedFactory(menuFactories, editMenu, 0);
	new resetTableStatsFactory(menuFactories, editMenu, 0);
	new resetFunctionStatsFactory(menuFactories, editMenu, 0);
	new reassignDropOwnedFactory(menuFactories, editMenu, 0);
	new moveTablespaceFactory(menuFactories, editMenu, 0);
	new searchObjectFactory(menuFactories, editMenu, 0);
	editMenu->AppendSeparator();

	new separatorFactory(menuFactories);

	toolBar->AddSeparator();
	toolsMenu->AppendSeparator();

	debuggingMenuFactory = new submenuFactory(menuFactories);     // placeholder where "Debugging" submenu will be inserted
	toolsMenu->Append(debuggingMenuFactory->GetId(), _("&Debugging"), debuggingMenu,    _("Debugging options for the selected item."));
	new debuggerFactory(menuFactories, debuggingMenu, 0);
	new breakpointFactory(menuFactories, debuggingMenu, 0);

	new queryToolFactory(menuFactories, toolsMenu, toolBar);
	scriptingMenuFactory = new submenuFactory(menuFactories);    // placeholder where "Query Template" submenu will be inserted
	toolsMenu->Append(scriptingMenuFactory->GetId(), _("Scripts"), scriptingMenu, _("Start Query Tool with scripted query."));
	new queryToolSqlFactory(menuFactories, scriptingMenu, 0);
	new queryToolCreateCascadeFactory(menuFactories, scriptingMenu, 0);
	new queryToolSelectFactory(menuFactories, scriptingMenu, 0);
	new queryToolExecFactory(menuFactories, scriptingMenu, 0);
	new queryToolInsertFactory(menuFactories, scriptingMenu, 0);
	new queryToolUpdateFactory(menuFactories, scriptingMenu, 0);
	new queryToolDeleteFactory(menuFactories, scriptingMenu, 0);

#ifdef DATABASEDESIGNER
	new databaseDesignerFactory(menuFactories, toolsMenu, toolBar);
#endif

	viewdataMenuFactory = new submenuFactory(menuFactories);     // placeholder where "View data" submenu will be inserted
	toolsMenu->Append(viewdataMenuFactory->GetId(), _("View &Data"), viewDataMenu, _("View data."));

	reportMenuFactory = new submenuFactory(menuFactories);     // placeholder where "Reports" submenu will be inserted
	toolsMenu->Append(reportMenuFactory->GetId(), _("&Reports"), reportMenu,    _("Create reports about the selected item."));
	new reportObjectPropertiesFactory(menuFactories, reportMenu, 0);
	new reportObjectDdlFactory(menuFactories, reportMenu, 0);
	new reportObjectDataDictionaryFactory(menuFactories, reportMenu, 0);
	new reportObjectStatisticsFactory(menuFactories, reportMenu, 0);
	new reportObjectDependenciesFactory(menuFactories, reportMenu, 0);
	new reportObjectDependentsFactory(menuFactories, reportMenu, 0);
	new reportObjectListFactory(menuFactories, reportMenu, 0);
	new reportCompareFactory(menuFactories, reportMenu, 0);
	new reportAwrFactory(menuFactories, reportMenu, 0);
	choiceSelectOpts.Add(0);
	choiceSelectOpts.Add(1);

	toolsMenu->AppendSeparator();

	new editGridLimitedFactory(menuFactories, viewDataMenu, toolBar, 100, true);
	new editGridLimitedFactory(menuFactories, viewDataMenu, toolBar, 100, false);
	new editGridFactory(menuFactories, viewDataMenu, toolBar);
	new editGridFilteredFactory(menuFactories, viewDataMenu, toolBar);

	new maintenanceFactory(menuFactories, toolsMenu, toolBar);

	new backupFactory(menuFactories, toolsMenu, 0);
	new backupGlobalsFactory(menuFactories, toolsMenu, 0);
	new LogFactory(menuFactories, cfgMenu, 0);
	new backupServerFactory(menuFactories, toolsMenu, 0);
	new restoreFactory(menuFactories, toolsMenu, 0);
	new importFactory(menuFactories, toolsMenu, 0);

	new grantWizardFactory(menuFactories, toolsMenu, 0);
	new mainConfigFactory(menuFactories, cfgMenu, 0);
	new hbaConfigFactory(menuFactories, cfgMenu, 0);
	toolsMenu->Append(MNU_CONFIGSUBMENU, _("Server Configuration"), cfgMenu);
	toolsMenu->AppendSeparator();

	new runNowFactory(menuFactories, toolsMenu, 0);
	toolsMenu->AppendSeparator();

	new separatorFactory(menuFactories);

	new propertyFactory(menuFactories, editMenu, 0);
	new serverStatusFactory(menuFactories, toolsMenu, 0);

	// Add the plugin toolbar button/menu
	new pluginButtonMenuFactory(menuFactories, pluginsMenu, toolBar, pluginUtilityCount);
	
	//--------------------------
	toolBar->AddSeparator();
	actionFactory *helpFact = new contentsFactory(menuFactories, helpMenu, 0);
	new hintFactory(menuFactories, helpMenu, toolBar, true);
	new faqFactory(menuFactories, helpMenu, 0);
	new bugReportFactory(menuFactories, helpMenu, 0);

	helpMenu->AppendSeparator();

	new pgsqlHelpFactory(menuFactories, helpMenu, toolBar, true);
	if (!appearanceFactory->GetHideEnterprisedbHelp())
		new edbHelpFactory(menuFactories, helpMenu, toolBar, true);
	if (!appearanceFactory->GetHideGreenplumHelp())
		new greenplumHelpFactory(menuFactories, helpMenu, toolBar, true);
	new slonyHelpFactory(menuFactories, helpMenu, toolBar, true);

	// Don't include this seperator on Mac, because the only option
	// under it will be moved to the application menu.
#ifndef __WXMAC__
	helpMenu->AppendSeparator();
#endif

	actionFactory *abFact = new aboutFactory(menuFactories, helpMenu, 0);

#ifdef __WXMAC__
	wxApp::s_macPreferencesMenuItemId = optFact->GetId();
	wxApp::s_macExitMenuItemId = MNU_EXIT;
	wxApp::s_macAboutMenuItemId = abFact->GetId();
#else
	(void)optFact;
	(void)abFact;
#endif


	menuBar = new wxMenuBar();
	menuBar->Append(fileMenu, _("&File"));
	menuBar->Append(editMenu, _("&Edit"));
	// Changing the caption of the plugins menu also needs a similar change below
	menuBar->Append(pluginsMenu, _("&Plugins"));
	menuBar->Append(viewMenu, _("&View"));
	menuBar->Append(toolsMenu, _("&Tools"));
	menuBar->Append(helpMenu, _("&Help"));
	SetMenuBar(menuBar);

	// Disable the plugins menu if there aren't any.
	if (!pluginUtilityCount)
	{
		pluginsMenu->Append(MNU_DUMMY, _("No plugins installed"));
		pluginsMenu->Enable(MNU_DUMMY, false);
	}

	treeContextMenu = 0;
	


	// Status bar
	statusBar = CreateStatusBar(4);
	int iWidths[4] = {0, -1, 400, 100};
	SetStatusWidths(4, iWidths);
	SetStatusBarPane(-1);
	statusBar->SetStatusText(wxT(""), 0);
	statusBar->SetStatusText(_("Ready."), 1);
	statusBar->SetStatusText(_("0 Secs"), 3);

	wxAcceleratorEntry entries[5];
	entries[0].Set(wxACCEL_NORMAL, WXK_F5, refFact->GetId());
	entries[1].Set(wxACCEL_NORMAL, WXK_DELETE, MNU_DELETE);
	entries[2].Set(wxACCEL_NORMAL, WXK_F1, helpFact->GetId());
	entries[3].Set(wxACCEL_SHIFT, WXK_F10, MNU_CONTEXTMENU);
	entries[4].Set(wxACCEL_NORMAL, WXK_F4, MNU_SHORTCUT);
	wxAcceleratorTable accel(5, entries);

	SetAcceleratorTable(accel);
	
	// Display the bar and configure buttons.
	toolBar->Realize();
}


void frmMain::Refresh(pgObject *data)
{
	bool done = false;
	pgObject *obj = NULL;

	StartMsg(data->GetTranslatedMessage(REFRESHINGDETAILS));
	browser->Freeze();

	wxTreeItemId currentItem = data->GetId();
	if (currentItem)
		obj = browser->GetObject(currentItem);

	if (obj && obj->CheckOpenDialogs(browser, currentItem))
	{
		wxString msg = _("There are properties dialogues open for one or more objects that would be refreshed. Please close the properties dialogues and try again.");
		wxMessageBox(msg, _("Cannot refresh browser"), wxICON_WARNING | wxOK);
	}
	else
	{
		if (data->GetMetaType() == PGM_SCHEMA && !data->IsCollection() && data->GetConnection()->BackendMinimumVersion(9, 3))
		{
			// Event triggers backend functions are at schema level.
			// Hence, we can consider that Event Triggers at schema level and partly at database.
			// So, if any schema is refreshed, we need to the event trigger collection as well.
			// It's a special case, which effects the schema operations on the event triggers as well.
			// To solve this, we are navigating to the parent node (database node), and then locating event trigger collections.
			// Once we've found the event triggers collection, we refresh it.
			//
			wxTreeItemId dbItem = browser->GetItemParent(browser->GetItemParent(browser->GetSelection()));
			pgCollection *eventTrgCol = browser->FindCollection(eventTriggerFactory, dbItem);

			if(eventTrgCol)
				Refresh(eventTrgCol);
		}

		// Scan the child nodes and make a list of those that are expanded
		// This is not an exact science as node names may change etc.
		wxArrayString expandedNodes;
		GetExpandedChildNodes(currentItem, expandedNodes);

		browser->DeleteChildren(currentItem);

		// refresh information about the object
		data->SetDirty();

		pgObject *newData = data->Refresh(browser, currentItem);
		done = !data->GetConnection() || data->GetConnection()->GetStatus() == PGCONN_OK;

		if (newData != data)
		{
			wxLogInfo(wxT("Deleting %s %s for refresh"), data->GetTypeName().c_str(), data->GetQuotedFullIdentifier().c_str());

			if (data == currentObject)
				currentObject = newData;

			if (newData)
			{
				wxLogInfo(wxT("Replacing with new node %s %s for refresh"), newData->GetTypeName().c_str(), newData->GetQuotedFullIdentifier().c_str());

				newData->SetId(currentItem);    // not done automatically
				browser->SetItemData(currentItem, newData);

				// Update the node text if this is an object, as it may have been renamed
				if (!newData->IsCollection())
					browser->SetItemText(currentItem, newData->GetDisplayName());

				delete data;
				data = NULL;
			}
			else
			{
				wxLogInfo(wxT("No object to replace: vanished after refresh."));

				// If the connection is dead, just return here
				if (data->GetConnection()->GetStatus() != PGCONN_OK)
				{
					CheckAlive();
					browser->Thaw();
					return;
				}

				wxTreeItemId delItem = currentItem;
				currentItem = browser->GetItemParent(currentItem);
				browser->SelectItem(currentItem);
				browser->Delete(delItem);
			}
		}

		if (currentItem)
		{
			// Select the current node
			execSelChange(currentItem, currentItem == browser->GetSelection());

			// Attempt to expand any child nodes that were previously expanded
			ExpandChildNodes(currentItem, expandedNodes);
		}
	}

	browser->Thaw();
	EndMsg(done);
}

void frmMain::OnCopy(wxCommandEvent &ev)
{
	wxString text;

	// Attempt to copy from the current object

	// Listview
	ctlListView *lv = dynamic_cast<ctlListView *>(currentControl);
	if (lv)
	{
		int row = lv->GetFirstSelected();

		while (row >= 0)
		{
			for (int col = 0; col < lv->GetColumnCount(); col++)
			{
				text.Append(lv->GetText(row, col) + wxT("\t"));
			}
			text.Append(wxT("\n"));
			row = lv->GetNextSelected(row);
		}
		text = text.Trim();
	}

	// ctlSQLBox
	ctlSQLBox *sb = dynamic_cast<ctlSQLBox *>(currentControl);
	if (sb)
	{
		text = sb->GetSelectedText();
	}

	// Set the clipboard text
	if (text.Length() > 0 && wxTheClipboard->Open())
	{
		wxTheClipboard->SetData(new wxTextDataObject(wxTextBuffer::Translate(text)));
		wxTheClipboard->Close();
	}
}

void frmMain::GetExpandedChildNodes(wxTreeItemId node, wxArrayString &expandedNodes)
{
	wxTreeItemIdValue cookie;
	wxTreeItemId child = browser->GetFirstChild(node, cookie);

	while (child.IsOk())
	{
		if (browser->IsExpanded(child))
		{
			GetExpandedChildNodes(child, expandedNodes);
			expandedNodes.Add(GetNodePath(child));
		}

		child = browser->GetNextChild(node, cookie);
	}
}

void frmMain::ExpandChildNodes(wxTreeItemId node, wxArrayString &expandedNodes)
{
	wxTreeItemIdValue cookie;
	wxTreeItemId child = browser->GetFirstChild(node, cookie);

	while (child.IsOk())
	{
		if (expandedNodes.Index(GetNodePath(child)) != wxNOT_FOUND)
		{
			browser->Expand(child);
			ExpandChildNodes(child, expandedNodes);
		}

		child = browser->GetNextChild(node, cookie);
	}
}

wxString frmMain::GetNodePath(wxTreeItemId node)
{
	wxString path;
	path = browser->GetItemText(node).BeforeFirst('(').Trim();

	wxTreeItemId parent = browser->GetItemParent(node);
	while (parent.IsOk())
	{
		path = browser->GetItemText(parent).BeforeFirst('(').Trim() + wxT("/") + path;
		parent = browser->GetItemParent(parent);
	}

	return path;
}

// Return the path for the current node
wxString frmMain::GetCurrentNodePath()
{
	return GetNodePath(currentObject->GetId());
}

// Attempt to reselect the node with the given path
bool frmMain::SetCurrentNode(wxTreeItemId node, const wxString &origPath)
{
	wxString path = origPath.Lower();
	wxTreeItemIdValue cookie;
	wxTreeItemId child = browser->GetFirstChild(node, cookie);


	while (child.IsOk())
	{
		wxString actNodePath = GetNodePath(child).Lower();

		if(path.StartsWith(actNodePath))
		{
			if(!browser->IsExpanded(child))
			{
				browser->SelectItem(child, true);
				browser->Expand(child);
			}

			if (actNodePath == path)
			{
				browser->SelectItem(child, true);
				return true;
			}
			else if (SetCurrentNode(child, path))
				return true;
		}
		child = browser->GetNextChild(node, cookie);

	}

	return false;
}

void frmMain::ShowObjStatistics(pgObject *data, wxWindow *ctrl)
{

	// Refresh panes if they're currently shown on screen, unless
	// they've been specifically requested (eg. a notebook
	// event is telling us they're about to become visible).

	if ((!ctrl && statistics->IsShownOnScreen()) || ctrl == statistics)
	{
		statistics->Freeze();
		data->ShowStatistics(this, statistics);
		statistics->Thaw();
	}

	if ((!ctrl && dependencies->IsShownOnScreen()) || ctrl == dependencies)
	{
		dependencies->Freeze();
		data->ShowDependencies(this, dependencies);
		dependencies->Thaw();
	}

	if ((!ctrl && dependents->IsShownOnScreen()) || ctrl == dependents)
	{
		dependents->Freeze();
		data->ShowDependents(this, dependents);
		dependents->Thaw();
	}
#if !defined(NO_WXJSON_GIT)
#if wxUSE_WEBREQUEST
	if ((!ctrl && git &&  git->IsShownOnScreen()) || ctrl == git)
	{

		//data->ShowDependents(this, dependents);
		if (git && data) git->ShowPage(data);
	}
#endif
#endif
}


// Ensure we show the data in any tabs that become visible
void frmMain::OnPageChange(wxAuiNotebookEvent &event)
{
	pgObject *data = browser->GetObject(browser->GetSelection());

	if (!data)
		return;

	ShowObjStatistics(data, ((wxAuiNotebook *)event.GetEventObject())->GetPage(event.GetSelection()));
}


ctlListView *frmMain::GetStatistics()
{
	return statistics;
}

ctlListView *frmMain::GetDependencies()
{
	return dependencies;
}

ctlListView *frmMain::GetReferencedBy()
{
	return dependents;
}

bool frmMain::CheckAlive()
{
	bool userInformed = false;
	bool closeIt = false;

	wxTreeItemIdValue foldercookie;
	wxTreeItemId folderitem = browser->GetFirstChild(browser->GetRootItem(), foldercookie);
	while (folderitem)
	{
		if (browser->ItemHasChildren(folderitem))
		{
			wxCookieType cookie;
			wxTreeItemId serverItem = browser->GetFirstChild(folderitem, cookie);
			while (serverItem)
			{
				pgServer *server = (pgServer *)browser->GetObject(serverItem);

				if (server && server->IsCreatedBy(serverFactory) && server->connection())
				{
					if (server->connection()->IsAlive())
					{
						wxCookieType cookie2;
						wxTreeItemId item = browser->GetFirstChild(serverItem, cookie2);
						while (item)
						{
							pgObject *obj = browser->GetObject(item);
							if (obj && obj->IsCreatedBy(databaseFactory.GetCollectionFactory()))
							{
								wxCookieType cookie3;
								item = browser->GetFirstChild(obj->GetId(), cookie3);
								while (item)
								{
									pgDatabase *db = (pgDatabase *)browser->GetObject(item);
									if (db && db->IsCreatedBy(databaseFactory))
									{
										pgConn *conn = db->GetConnection();
										if (conn)
										{
											if (!conn->IsAlive() && (conn->GetStatus() == PGCONN_BROKEN || conn->GetStatus() == PGCONN_BAD))
											{
												conn->Close();
												if (!userInformed)
												{
													wxMessageDialog dlg(this, _("Do you want to attempt to reconnect to the database?"),
													                    wxString::Format(_("Connection to database %s lost."), db->GetName().c_str()),
													                    wxICON_EXCLAMATION | wxYES_NO | wxYES_DEFAULT);

													closeIt = (dlg.ShowModal() != wxID_YES);
													userInformed = true;
												}
												if (closeIt)
												{
													db->Disconnect();

													browser->DeleteChildren(db->GetId());
													db->UpdateIcon(browser);
												}
												else
												{
													// Create a server object and connect it.
													wxBusyInfo waiting(wxString::Format(_("Reconnecting to database %s"),
													                                    db->GetName().c_str()), this);

													// Give the UI a chance to redraw
													wxSafeYield();
													wxMilliSleep(100);
													wxSafeYield();

													if (!conn->Reconnect())
													{
														db->Disconnect();

														browser->DeleteChildren(db->GetId());
														db->UpdateIcon(browser);
													}
													else
														// Indicate things are back to normal
														userInformed = false;
												}

											}
										}
									}
									item = browser->GetNextChild(obj->GetId(), cookie3);
								}
							}
							item = browser->GetNextChild(serverItem, cookie2);
						}
					}
					else
					{
						if (server->connection()->GetStatus() == PGCONN_BROKEN || server->connection()->GetStatus() == PGCONN_BAD)
						{
							server->connection()->Close();
							if (!userInformed)
							{
								wxMessageDialog dlg(this, _("Do you want to attempt to reconnect to the server?"),
								                    wxString::Format(_("Connection to server %s lost."), server->GetName().c_str()),
								                    wxICON_EXCLAMATION | wxYES_NO | wxYES_DEFAULT);

								closeIt = (dlg.ShowModal() != wxID_YES);
								userInformed = true;
							}
							if (closeIt)
							{
								server->Disconnect(this);
								browser->SelectItem(serverItem);
								execSelChange(serverItem, true);
								browser->DeleteChildren(serverItem);
							}
							else
							{
								// Create a server object and connect it.
								wxBusyInfo waiting(wxString::Format(_("Reconnecting to server %s (%s:%d)"),
								                                    server->GetDescription().c_str(), server->GetName().c_str(), server->GetPort()), this);

								// Give the UI a chance to redraw
								wxSafeYield();
								wxMilliSleep(100);
								wxSafeYield();

								if (!server->connection()->Reconnect())
								{
									server->Disconnect(this);
									browser->SelectItem(serverItem);
									execSelChange(serverItem, true);
									browser->DeleteChildren(serverItem);
								}
								else
									// Indicate things are back to normal
									userInformed = false;
							}
						}
					}
				}

				serverItem = browser->GetNextChild(folderitem, cookie);
			}
		}
		folderitem = browser->GetNextChild(browser->GetRootItem(), foldercookie);
	}
	return userInformed;
}


wxTreeItemId frmMain::RestoreEnvironment(pgServer *server)
{
	wxTreeItemId item, lastItem;
	wxString lastDatabase = server->GetLastDatabase();
	if (lastDatabase.IsNull())
		return item;

	wxCookieType cookie;
	pgObject *data = 0;
	item = browser->GetFirstChild(server->GetId(), cookie);
	while (item)
	{
		data = browser->GetObject(item);
		if (data && data->IsCreatedBy(databaseFactory.GetCollectionFactory()))
			break;
		// Get the next item
		item = browser->GetNextChild(server->GetId(), cookie);
	}
	if (!item)
		return item;

	// found DATABASES item
	listViews->SetSelection(NBP_PROPERTIES);
	data->ShowTree(this, browser, 0, 0);
	lastItem = item;

	item = browser->GetFirstChild(lastItem, cookie);
	while (item)
	{
		data = browser->GetObject(item);
		if (data && data->IsCreatedBy(databaseFactory) && data->GetName() == lastDatabase)
			break;
		// Get the next item
		item = browser->GetNextChild(lastItem, cookie);
	}
	if (!item)
		return lastItem;

	// found last DATABASE
	data->ShowTree(this, browser, 0, 0);
	lastItem = item;

	wxString lastSchema = server->GetLastSchema();
	if (lastSchema.IsNull())
		return lastItem;

	item = browser->GetFirstChild(lastItem, cookie);
	while (item)
	{
		data = browser->GetObject(item);
		if (data && data->GetMetaType() == PGM_SCHEMA)
			break;
		// Get the next item
		item = browser->GetNextChild(lastItem, cookie);
	}
	if (!item)
		return lastItem;

	// found SCHEMAS item
	data->ShowTree(this, browser, 0, 0);
	lastItem = item;

	item = browser->GetFirstChild(lastItem, cookie);
	while (item)
	{
		data = browser->GetObject(item);
		if (data && data->GetMetaType() == PGM_SCHEMA && data->GetName() == lastSchema)
			break;
		// Get the next item
		item = browser->GetNextChild(lastItem, cookie);
	}

	return (item ? item : lastItem);
}


int frmMain::ReconnectServer(pgServer *server, bool restore)
{
	if (server->GetPuttyTunnel()) {
		// check open port
		int port=server->GetPort();
		wxString localhost=server->GetName(); // localhost
		if (!isPortOpen(localhost,port,500)) {
			server->GetPuttyTunnel()->StartDialog(winMain, server); // execute plugin
			int count=0;
			do {
	        	wxMilliSleep(200);
				count++;
			} while (!isPortOpen(localhost,port,500) && count<20); // wait create putty tunnel
			if (count>=20) {
				StartMsg(_("Create putty tunnel error."));
				EndMsg(true);
				return -1;
			}
		}
	}

	// Create a server object and connect it.
	wxBusyInfo waiting(wxString::Format(_("Connecting to server %s (%s:%d)"),
	                                    server->GetDescription().c_str(), server->GetName().c_str(), server->GetPort()), this);

	// Give the UI a chance to redraw
	wxSafeYield();
	wxMilliSleep(100);
	wxSafeYield();

	int res = server->Connect(this, true, wxEmptyString, false, true);

	// Check the result, and handle it as appropriate
	wxTreeItemId item;
	switch (res)
	{
		case PGCONN_OK:
		{
			if (restore && server->GetRestore())
				StartMsg(_("Restoring previous environment"));
			else
				StartMsg(_("Establishing connection"));

			wxLogInfo(wxT("pgServer object initialised as required."));

			server->ShowTreeDetail(browser);

			browser->Freeze();
			if (restore && server->GetRestore())
				item = RestoreEnvironment(server);
			else
				item = server->GetId();
			browser->Thaw();

			if (item)
			{
				browser->SelectItem(item);

				wxSafeYield();
				browser->Expand(item);
				browser->EnsureVisible(item);
			}

			if (item)
				EndMsg(true);
			else
			{
				if (restore && server->GetRestore())
					EndMsg(false);
				else
					EndMsg(true);
			}
			if (item)
				GetMenuFactories()->CheckMenu((pgObject *)browser->GetItemData(item), GetMenuBar(), (ctlMenuToolbar *)GetToolBar());
			else
				GetMenuFactories()->CheckMenu(server, GetMenuBar(), (ctlMenuToolbar *)GetToolBar());
			browser->SetFocus();
			return res;
		}
		case PGCONN_DNSERR:
		/*
		// looks strange to me. Shouldn_t server be removed from the tree as well?
		delete server;
		OnAddServer(wxCommandEvent());
		break;
		*/
		case PGCONN_BAD:
			ReportConnError(server);
			break;

		default:
			wxLogInfo(wxT("pgServer object didn't initialise because the user aborted."));
			break;
	}

	server->Disconnect(this);
	return res;
}


bool frmMain::reportError(const wxString &error, const wxString &msgToIdentify, const wxString &hint)
{
	bool identified = false;

	wxString translated = wxGetTranslation(msgToIdentify);
	if (translated != msgToIdentify)
	{
		identified = (error.Find(translated) >= 0);
	}

	if (!identified)
	{
		if (msgToIdentify.Left(20) == wxT("Translator attention"))
			identified = (error.Find(msgToIdentify.Mid(msgToIdentify.Find('!') + 1)) >= 0);
		else
			identified = (error.Find(msgToIdentify) >= 0);
	}

	if (identified)
	{
		if (frmHint::WantHint(hint))
			frmHint::ShowHint(this, hint, error);
	}
	return identified;
}


void frmMain::ReportConnError(pgServer *server)
{
	wxString error = server->GetLastError();
	bool wantHint = false;

	wantHint = reportError(error, __("Translator attention: must match libpq translation!Is the server running on host"), HINT_CONNECTSERVER);
	if (!wantHint)
	{
		wantHint = reportError(error, __("Translator attention: must match backend translation!no pg_hba.conf entry for"), HINT_MISSINGHBA);
	}
	if (!wantHint)
	{
		wantHint = reportError(error, __("Translator attention: must match backend translation!Ident authentication failed"), HINT_MISSINGIDENT);
	}
	if (!wantHint)
	{
		wxLogError(__("Error connecting to the server: %s"), error.c_str());
	}
}


void frmMain::StoreServers()
{
	wxLogInfo(wxT("Storing listed servers for later..."));

	// Store the currently listed servers for later retrieval.
	pgServer *server;
	int numServers = 0;

	// Get the hostname for later...
	char buf[255];
	gethostname(buf, 255);
	wxString hostname = wxString(buf, wxConvUTF8);

	wxTreeItemIdValue foldercookie;
	wxTreeItemId folderitem = browser->GetFirstChild(browser->GetRootItem(), foldercookie);
	wxTreeItemId cursoritem = browser->GetSelection();
	while (cursoritem.IsOk()) {
				server = (pgServer *)browser->GetItemData(cursoritem);
				if (server != NULL && server->IsCreatedBy(serverFactory)) {
					break;
				}
				cursoritem=browser->GetItemParent(cursoritem);
	}
	wxString selServerName;
	while (folderitem)
	{
		if (browser->ItemHasChildren(folderitem))
		{
			wxTreeItemIdValue servercookie;
			wxTreeItemId serveritem = browser->GetFirstChild(folderitem, servercookie);
			while (serveritem)
			{
				server = (pgServer *)browser->GetItemData(serveritem);
				if (server != NULL && server->IsCreatedBy(serverFactory))
				{
					wxString key;
					++numServers;
					if (cursoritem==serveritem) selServerName=browser->GetItemText((serveritem));
					key.Printf(wxT("Servers/%d/"), numServers);
					settings->Write(key + wxT("Server"), server->GetName());
					settings->Write(key + wxT("HostAddr"), server->GetHostAddr());
					settings->Write(key + wxT("Description"), server->GetDescription());
					settings->Write(key + wxT("Keywords"), server->GetKeywords());
					settings->Write(key + wxT("Service"), server->GetService());
					settings->Write(key + wxT("ServiceID"), server->GetServiceID());
					settings->Write(key + wxT("DiscoveryID"), server->GetDiscoveryID());
					settings->WriteInt(key + wxT("Port"), server->GetPort());
					settings->WriteBool(key + wxT("StorePwd"), server->GetStorePwd());
					settings->Write(key + wxT("Rolename"), server->GetRolename());
					settings->Write(key + wxT("ConnStr"), server->GetConnStr());
					settings->WriteBool(key + wxT("Restore"), server->GetRestore());
					settings->Write(key + wxT("Database"), server->GetDatabaseName());
					settings->Write(key + wxT("Username"), server->GetUsername());
					settings->Write(key + wxT("LastDatabase"), server->GetLastDatabase());
					settings->Write(key + wxT("LastSchema"), server->GetLastSchema());
					settings->Write(key + wxT("DbRestriction"), server->GetDbRestriction());
					settings->Write(key + wxT("Colour"), server->GetColour());
					settings->WriteInt(key + wxT("SSL"), server->GetSSL());
					settings->Write(key + wxT("Group"), server->GetGroup());
					settings->Write(key + wxT("SSLCert"), server->GetSSLCert());
					settings->Write(key + wxT("SSLKey"), server->GetSSLKey());
					settings->Write(key + wxT("SSLRootCert"), server->GetSSLRootCert());
					settings->Write(key + wxT("SSLCrl"), server->GetSSLCrl());
					settings->WriteBool(key + wxT("SSLCompression"), server->GetSSLCompression());
#if defined(HAVE_OPENSSL_CRYPTO) || defined(HAVE_GCRYPT)
					settings->WriteBool(key + wxT("SSHTunnel"), server->GetSSHTunnel());
					settings->Write(key + wxT("TunnelHost"), server->GetTunnelHost());
					settings->Write(key + wxT("TunnelUserName"), server->GetTunnelUserName());
					settings->WriteBool(key + wxT("TunnelModePwd"), server->GetAuthModePwd());
					settings->Write(key + wxT("PublicKeyFile"), server->GetPublicKeyFile());
					settings->Write(key + wxT("IdentityFile"), server->GetIdentityFile());
					settings->WriteInt(key + wxT("TunnelPort"), server->GetTunnelPort());
#endif
					pgCollection *coll = browser->FindCollection(databaseFactory, server->GetId());
					if (coll)
					{
						treeObjectIterator dbs(browser, coll);
						pgDatabase *db;

						while ((db = (pgDatabase *)dbs.GetNextObject()) != 0)
							settings->Write(key + wxT("Databases/") + db->GetName() + wxT("/SchemaRestriction"), db->GetSchemaRestriction());
					}
				}
				serveritem = browser->GetNextChild(folderitem, servercookie);
			}
		}
		folderitem = browser->GetNextChild(browser->GetRootItem(), foldercookie);
	}

	// Write the server count
	settings->WriteInt(wxT("Servers/Count"), numServers);
	settings->Write(wxT("Servers/SelectItem"), selServerName);
	settings->FlushChanges();
	wxLogInfo(wxT("Stored %d servers."), numServers);
}


void frmMain::RetrieveServers()
{
	wxString label;
	int total = 0;
	wxTreeItemIdValue groupcookie;
	wxTreeItemId groupitem;

	// Retrieve previously stored servers
	wxLogInfo(wxT("Reloading servers..."));

	// Create all servers' nodes
	serverFactory.CreateObjects(serversObj, browser);

	// Count number of servers and groups
	groupitem = browser->GetFirstChild(browser->GetRootItem(), groupcookie);
	while (groupitem)
	{
		total = browser->GetChildrenCount(groupitem, false);
		label = browser->GetItemText(groupitem) + wxT(" (") + NumToStr((long)total) + wxT(")");
		browser->SetItemText(groupitem, label);
		browser->SortChildren(groupitem);
		browser->Expand(groupitem);
		groupitem = browser->GetNextChild(browser->GetRootItem(), groupcookie);
	}
}

pgServer *frmMain::ConnectToServer(const wxString &servername, bool restore)
{
	wxTreeItemIdValue foldercookie, servercookie;
	wxTreeItemId folderitem, serveritem;
	pgObject *object;
	pgServer *server;

	folderitem = browser->GetFirstChild(browser->GetRootItem(), foldercookie);
	while (folderitem)
	{
		if (browser->ItemHasChildren(folderitem))
		{
			serveritem = browser->GetFirstChild(folderitem, servercookie);
			while (serveritem)
			{
				object = browser->GetObject(serveritem);
				if (object && object->IsCreatedBy(serverFactory))
				{
					server = (pgServer *)object;
					if (server->GetDescription() == servername)
					{
						ReconnectServer(server, restore);
						return server;
					}
				}
				serveritem = browser->GetNextChild(folderitem, servercookie);
			}
		}
		folderitem = browser->GetNextChild(browser->GetRootItem(), foldercookie);
	}

	return 0;
}

void frmMain::StartMsg(const wxString &msg)
{
	if (msgLevel++)
		return;

	timermsg.Printf(wxT("%s..."), msg.c_str());
	wxBeginBusyCursor();
	stopwatch.Start(0);

	// Create a copy of the message, with %'s escaped for wxLogStatus
	wxString logmsg = timermsg;
	logmsg.Replace(wxT("%"), wxT("%%"));
	wxLogStatus(logmsg);

	statusBar->SetStatusText(timermsg, 1);
	statusBar->SetStatusText(wxT(""), 2);
}


void frmMain::EndMsg(bool done)
{
	msgLevel--;

	if (!msgLevel)
	{
		// Get the execution time & display it
		float timeval = stopwatch.Time();
		statusBar->SetStatusText(ElapsedTimeToStr(timeval), 3);

		// Display the name of the connection for currently selected object
		wxString connection;
		if (currentObject)
		{
			pgConn *conn = currentObject->GetConnection();

			if (conn)
				connection = conn->GetName();
		}
		statusBar->SetStatusText(connection, 2);

		// Display the 'Done' message
		if (done)
			statusBar->SetStatusText(timermsg + _(" Done."), 1);
		else
			statusBar->SetStatusText(timermsg + _(" Failed."), 1);

		wxLogStatus(
		    wxT("%s (%s)"), timermsg.c_str(),
		    ElapsedTimeToStr(timeval).c_str()
		);
		wxEndBusyCursor();
	}
}


void frmMain::SetStatusText(const wxString &msg)
{
	statusBar->SetStatusText(msg, 1);
	statusBar->SetStatusText(wxEmptyString, 2);
}

void frmMain::SetItemBackgroundColour(wxTreeItemId item, wxColour colour)
{
	wxTreeItemIdValue cookie;

	browser->SetItemBackgroundColour(item, wxColour(colour));
	if (browser->ItemHasChildren(item))
	{
		wxTreeItemId childitem = browser->GetFirstChild(item, cookie);
		while (childitem)
		{
			SetItemBackgroundColour(childitem, colour);
			childitem = browser->GetNextChild(item, cookie);
		}
	}
}

#if defined(HAVE_OPENSSL_CRYPTO) || defined(HAVE_GCRYPT)
void frmMain::OnSSHTunnelEvent(wxCommandEvent &event)
{
	wxLogError(event.GetString());
}
#endif

/////////////////////////////////////////


contentsFactory::contentsFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : actionFactory(list)
{
	mnu->Append(id, _("&Help contents"), _("Open the helpfile."));
}


wxWindow *contentsFactory::StartDialog(frmMain *form, pgObject *obj)
{
	DisplayHelp(wxT("index"), HELP_PGADMIN);
	return 0;
}


faqFactory::faqFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : actionFactory(list)
{
	mnu->Append(id, _("&FAQ"), _("Frequently asked questions."));
}


wxWindow *faqFactory::StartDialog(frmMain *form, pgObject *obj)
{
	wxLaunchDefaultBrowser(wxT("https://github.com/levinsv/pgadmin3/issues"));

	return 0;
}

#include "images/help.pngc"
#include "images/help2.pngc"
pgsqlHelpFactory::pgsqlHelpFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar, bool bigIcon) : actionFactory(list)
{
	mnu->Append(id, _("&PostgreSQL Help"), _("Display help on the PostgreSQL database system."));
	if (toolbar)
	{
		if (bigIcon)
			toolbar->AddTool(id, wxEmptyString, GetBundleSVG(help2_png_bmp, "help2.svg", wxSize(32, 32)), _("Display help on SQL commands."));
		else
			toolbar->AddTool(id, wxEmptyString, *help_png_bmp, _("Display help on SQL commands."));
	}
}


wxWindow *pgsqlHelpFactory::StartDialog(frmMain *form, pgObject *obj)
{
	DisplayHelp(wxT("index"), HELP_POSTGRESQL);
	return 0;
}

edbHelpFactory::edbHelpFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar, bool bigIcon) : actionFactory(list)
{
	mnu->Append(id, _("&EnterpriseDB Help"), _("Display help on the EnterpriseDB database system."));
}


wxWindow *edbHelpFactory::StartDialog(frmMain *form, pgObject *obj)
{
	DisplayHelp(wxT("index"), HELP_ENTERPRISEDB);
	return 0;
}

greenplumHelpFactory::greenplumHelpFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar, bool bigIcon) : actionFactory(list)
{
	mnu->Append(id, _("&Greenplum Database Help"), _("Display help on the Greenplum Database system."));
}


wxWindow *greenplumHelpFactory::StartDialog(frmMain *form, pgObject *obj)
{
	DisplayHelp(wxT("index"), HELP_GREENPLUM);
	return 0;
}


slonyHelpFactory::slonyHelpFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar, bool bigIcon) : actionFactory(list)
{
	mnu->Append(id, _("&Slony Help"), _("Display help on the Slony replication system."));
}


wxWindow *slonyHelpFactory::StartDialog(frmMain *form, pgObject *obj)
{
	DisplayHelp(wxT("index"), HELP_SLONY);
	return 0;
}

bugReportFactory::bugReportFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : actionFactory(list)
{
	mnu->Append(id, _("&Bug Report"), _("How to send a bugreport to the pgAdmin Development Team."));
}


wxWindow *bugReportFactory::StartDialog(frmMain *form, pgObject *obj)
{
	DisplayHelp(wxT("bugreport"), HELP_PGADMIN);
	return 0;
}
