//////////////////////////////////////////////////////////////////////////
//
// pgAdmin III - PostgreSQL Tools
//
// Copyright (C) 2002 - 2016, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// dlgQuery.cpp - Property Dialog
//
//////////////////////////////////////////////////////////////////////////

// wxWindows headers
#include <wx/wx.h>
#include <wx/button.h>

// App headers
#include "pgAdmin3.h"
#include "ctl/ctlMenuToolbar.h"
#include "ctl/ctlSQLBox.h"
#include "schema/pgCollection.h"
#include "schema/pgDatatype.h"
#include "utils/misc.h"
#include "utils/pgDefs.h"
#include "ctl/ctlSecurityPanel.h"
#include "ctl/ctlDefaultSecurityPanel.h"

// Images
#include "images/properties.pngc"

#include "frm/frmMain.h"
#include "frm/frmHint.h"

// Property dialogs
#include "dlg/dlgProperty.h"
#include "dlg/dlgServer.h"
#include "dlg/dlgAggregate.h"
#include "dlg/dlgColumn.h"
#include "dlg/dlgIndex.h"
#include "dlg/dlgIndexConstraint.h"
#include "dlg/dlgForeignKey.h"
#include "dlg/dlgCheck.h"
#include "dlg/dlgRule.h"
#include "dlg/dlgTrigger.h"
#include "dlg/dlgEventTrigger.h"
#include "agent/dlgJob.h"
#include "agent/dlgStep.h"
#include "agent/dlgSchedule.h"

#include "slony/dlgRepCluster.h"
#include "slony/dlgRepNode.h"
#include "slony/dlgRepPath.h"
#include "slony/dlgRepListen.h"
#include "slony/dlgRepSet.h"
#include "slony/dlgRepSequence.h"
#include "slony/dlgRepTable.h"
#include "slony/dlgRepSubscription.h"
#include "schema/pgTable.h"
#include "schema/pgColumn.h"
#include "schema/pgTrigger.h"
#include "schema/pgGroup.h"
#include "schema/pgUser.h"
#include "schema/pgEventTrigger.h"

void dataType::SetOid(OID id)
{
	oid = id;
}

void dataType::SetTypename(wxString name)
{
	typeName = name;
}

OID dataType::GetOid()
{
	return oid;
}

wxString dataType::GetTypename()
{
	return typeName;
}

#define CTRLID_CHKSQLTEXTFIELD 1000


BEGIN_EVENT_TABLE(dlgProperty, DialogWithHelp)
	EVT_NOTEBOOK_PAGE_CHANGED(XRCID("nbNotebook"),  dlgProperty::OnPageSelect)

	EVT_TEXT(XRCID("txtName"),                      dlgProperty::OnChange)
	EVT_TEXT(XRCID("cbOwner"),                      dlgProperty::OnChangeOwner)
	EVT_COMBOBOX(XRCID("cbOwner"),                  dlgProperty::OnChange)
	EVT_TEXT(XRCID("cbSchema"),                     dlgProperty::OnChange)
	EVT_COMBOBOX(XRCID("cbSchema"),                 dlgProperty::OnChange)
	EVT_TEXT(XRCID("txtComment"),                   dlgProperty::OnChange)

	EVT_CHECKBOX(CTRLID_CHKSQLTEXTFIELD,            dlgProperty::OnChangeReadOnly)

	EVT_BUTTON(wxID_HELP,                           dlgProperty::OnHelp)
	EVT_BUTTON(wxID_OK,                             dlgProperty::OnOK)
END_EVENT_TABLE();


dlgProperty::dlgProperty(pgaFactory *f, frmMain *frame, const wxString &resName) : DialogWithHelp(frame)
{
	readOnly = false;
	sqlPane = 0;
	sqlTextField1 = 0;
	sqlTextField2 = 0;
	processing = false;
	mainForm = frame;
	database = 0;
	connection = 0;
	factory = f;
	item = (void *)NULL;
	owneritem = (void *)NULL;
	chkReadOnly = (wxCheckBox *)NULL;
	SetFont(settings->GetSystemFont());
	LoadResource(frame, resName);

#ifdef __WXMSW__
	SetWindowStyleFlag(GetWindowStyleFlag() & ~wxMAXIMIZE_BOX);
#endif

	nbNotebook = CTRL_NOTEBOOK("nbNotebook");
	if (!nbNotebook)
	{
		wxMessageBox(wxString::Format(_("Problem with resource %s: Notebook not found.\nPrepare to crash!"), resName.c_str()));
		return;
	}

	// Set the icon
	wxBitmap bm(factory->GetImage());
	wxIcon ico;
	ico.CopyFromBitmap(bm);
	SetIcon(ico);

	txtName = CTRL_TEXT("txtName");
	txtOid = CTRL_TEXT("txtOID");
	txtComment = CTRL_TEXT("txtComment");
	cbOwner = CTRL_COMBOBOX2("cbOwner");
	cbSchema = CTRL_COMBOBOX2("cbSchema");
	cbClusterSet = CTRL_COMBOBOX1("cbClusterSet");

	wxString db = wxT("Database");
	wxString ts = wxT("Tablespace");
	wxString rg = wxT("Resource Group");
	enableSQL2 = db.Cmp(factory->GetTypeName()) == 0
	             || ts.Cmp(factory->GetTypeName()) == 0
	             || rg.Cmp(factory->GetTypeName()) == 0;

	wxNotebookPage *page = nbNotebook->GetPage(0);
	wxASSERT(page != NULL);
	page->GetClientSize(&width, &height);

	numericValidator.SetStyle(wxFILTER_NUMERIC);
	btnOK->Disable();

	statusBar = XRCCTRL(*this, "unkStatusBar", wxStatusBar);
}


dlgProperty::~dlgProperty()
{
	wxString prop = wxT("Properties/") + wxString(factory->GetTypeName());
	settings->WritePoint(prop, GetPosition());

	if (GetWindowStyle() & wxRESIZE_BORDER)
		settings->WriteSize(prop, GetSize());

	if (obj)
		obj->SetWindowPtr(NULL);
}


wxString dlgProperty::GetHelpPage() const
{
	wxString page;

	pgObject *obj = ((dlgProperty *)this)->GetObject();
	if (obj)
		page = obj->GetHelpPage(false);
	else
	{
		// Attempt to get he page from the dialogue, otherwise, take a shot at it!
		page = this->GetHelpPage(true);
		if (page.Length() == 0)
		{
			page = wxT("pg/sql-create");
			page += wxString(factory->GetTypeName()).Lower();
		}
	}

	return page;
}


void dlgProperty::CheckValid(bool &enable, const bool condition, const wxString &msg)
{
	if (enable)
	{
		if (!condition)
		{
			if (statusBar)
				statusBar->SetStatusText(msg);
			enable = false;
		}
	}
}


void dlgProperty::SetDatabase(pgDatabase *db)
{
	database = db;
	if (db)
		connection = db->GetConnection();
}

void dlgProperty::SetDatatypeCache(dataTypeCache cache)
{
	dtCache = cache;
}

void dlgProperty::EnableOK(bool enable)
{
	btnOK->Enable(enable);
	if (enable)
	{
		if (statusBar)
			statusBar->SetStatusText(wxEmptyString);
	}
}


void dlgProperty::SetSqlReadOnly(bool readonly)
{
	if (chkReadOnly)
		chkReadOnly->Enable(!readonly);
}


int dlgProperty::Go(bool modal)
{
	wxASSERT(factory != 0);

	if(GetObject())
		obj = GetObject();
	else
		obj = mainForm->GetBrowser()->GetObject(mainForm->GetBrowser()->GetSelection());

	// restore previous position and size, if applicable
	wxString prop = wxT("Properties/") + wxString(factory->GetTypeName());

	wxSize origSize = GetSize();

	if (GetWindowStyle() & wxRESIZE_BORDER)
		SetSize(settings->Read(prop, GetSize()));

	wxPoint pos = settings->Read(prop, GetPosition());
	if (pos.x < 0)
		pos.x = 0;
	if (pos.y < 0)
		pos.y = 0;
	Move(pos);

	wxSize size = GetSize();
	CheckOnScreen(this, pos, size, origSize.GetWidth(), origSize.GetHeight());
	Move(pos);

	ctlComboBoxFix *cbowner = (ctlComboBoxFix *)cbOwner;
	ctlComboBoxFix *cbschema = (ctlComboBoxFix *)cbSchema;

	if (cbClusterSet)
	{
		cbClusterSet->Append(wxEmptyString);
		cbClusterSet->SetSelection(0);

		if (mainForm && database)
		{
			wxArrayString clusters = database->GetSlonyClusters(mainForm->GetBrowser());

			size_t i;
			for (i = 0 ; i < clusters.GetCount() ; i++)
			{
				wxString cluster = wxT("_") + clusters.Item(i);
				pgSetIterator sets(connection,
				                   wxT("SELECT set_id, ") + qtIdent(cluster) + wxT(".slonyversionmajor(), ") + qtIdent(cluster) + wxT(".slonyversionminor()\n")
				                   wxT("  FROM ") + qtIdent(cluster) + wxT(".sl_set\n")
				                   wxT(" WHERE set_origin = ") + qtIdent(cluster) +
				                   wxT(".getlocalnodeid(") + qtDbString(cluster) + wxT(");"));

				while (sets.RowsLeft())
				{
					wxString str;
					long setId = sets.GetLong(wxT("set_id"));
					long majorVer = sets.GetLong(wxT("slonyversionmajor"));
					long minorVer = sets.GetLong(wxT("slonyversionminor"));
					str.Printf(_("Cluster \"%s\", set %ld"), clusters.Item(i).c_str(), setId);
					cbClusterSet->Append(str, static_cast<void *>(new replClientData(cluster, setId, majorVer, minorVer)));
				}
			}
		}
		if (cbClusterSet->GetCount() < 2)
			cbClusterSet->Disable();
	}

	if (cbowner && !cbowner->GetCount())
	{
		if (!GetObject())
			cbOwner->Append(wxEmptyString);
		AddGroups(cbowner);
		AddUsers(cbowner);
	}
	if (txtOid)
		txtOid->Disable();

	if (cbschema && !cbschema->GetCount())
		AddSchemas(cbschema);

	if (GetObject())
	{
		if (txtName)
			txtName->SetValue(GetObject()->GetName());
		if (txtOid)
			txtOid->SetValue(NumToStr((unsigned long)GetObject()->GetOid()));
		if (cbOwner)
			cbOwner->SetValue(GetObject()->GetOwner());
		if (cbSchema)
			cbSchema->SetValue(GetObject()->GetSchema()->GetName());
		if (txtComment)
			txtComment->SetValue(GetObject()->GetComment());


		if (!readOnly && !GetObject()->CanCreate())
		{
			// users who can't create will usually not be allowed to change either.
			readOnly = false;
		}

		wxString typeName = factory->GetTypeName();
		SetTitle(wxString(wxGetTranslation(typeName)) + wxT(" ") + GetObject()->GetFullIdentifier());
		if (typeName==wxT("Function")||typeName==wxT("Procedure")) {
			if (nbNotebook)
			   nbNotebook->SetSelection(4);
		}

	}
	else
	{
		if (factory)
			SetTitle(wxGetTranslation(factory->GetNewString()));
		if (cbSchema)
		{
			if (obj->GetMetaType() == PGM_SCHEMA)
				cbSchema->SetValue(obj->GetName());
			else
				cbSchema->SetValue(obj->GetSchema()->GetName());
		}
	}
	if (statusBar)
		statusBar->SetStatusText(wxEmptyString);

	if (nbNotebook)
	{
		wxNotebookPage *pg = nbNotebook->GetPage(0);
		if (pg)
			pg->SetFocus();
	}

	// This fixes a UI glitch on MacOS X and Windows
	// Because of the new layout code, the Privileges pane don't size itself properly
	SetSize(GetSize().GetWidth() + 1, GetSize().GetHeight());
	SetSize(GetSize().GetWidth() - 1, GetSize().GetHeight());

	if (modal)
		return ShowModal();
	else
		Show(true);

	return 0;
}


void dlgProperty::CreateAdditionalPages()
{
	if (wxString(factory->GetTypeName()).Cmp(wxT("Server")))
	{
		// create a panel
		sqlPane = new wxPanel(nbNotebook);

		// add panel to the notebook
		nbNotebook->AddPage(sqlPane, wxT("SQL"));

		// create a flex grid sizer
		wxFlexGridSizer *fgsizer = new wxFlexGridSizer(1, 5, 5);

		// add checkbox to the panel
		chkReadOnly = new wxCheckBox(sqlPane, CTRLID_CHKSQLTEXTFIELD, _("Read only"));
		chkReadOnly->SetValue(true);
		fgsizer->Add(chkReadOnly, 1, wxALL | wxALIGN_LEFT, 5);

		// text entry box
		sqlTextField1 = new ctlSQLBox(sqlPane, CTL_PROPSQL,
		                              wxDefaultPosition, wxDefaultSize,
		                              wxTE_MULTILINE | wxSUNKEN_BORDER | wxTE_RICH2);
		fgsizer->Add(sqlTextField1, 1, wxALL | wxEXPAND, 5);

		// text entry box
		if (enableSQL2)
		{
			sqlTextField2 = new ctlSQLBox(sqlPane, CTL_PROPSQL,
			                              wxDefaultPosition, wxDefaultSize,
			                              wxTE_MULTILINE | wxSUNKEN_BORDER | wxTE_RICH2);
			fgsizer->Add(sqlTextField2, 1, wxALL | wxEXPAND, 5);
		}

		fgsizer->AddGrowableCol(0);
		fgsizer->AddGrowableRow(1);
		if (fgsizer->GetRows() > 1)
		{
			fgsizer->AddGrowableRow(2);
		}

		sqlPane->SetAutoLayout(true);
		sqlPane->SetSizer(fgsizer);
	}
}


wxString dlgProperty::GetName()
{
	if (txtName)
	{
		if (GetObject())
		{
			// If there is an existing object name with a leading or trailing
			// space, don't try to remove it.
			if (GetObject()->GetName() == txtName->GetValue())
				return txtName->GetValue();
			else
				return txtName->GetValue().Strip(wxString::both);
		}
		else
			return txtName->GetValue().Strip(wxString::both);
	}
	return wxEmptyString;
}


void dlgProperty::AppendNameChange(wxString &sql, const wxString &objName)
{
	if (GetObject()->GetName() != GetName())
	{
		if (objName.Length() > 0)
		{
			sql += wxT("ALTER ") + objName
			       +  wxT("\n  RENAME TO ") + qtIdent(GetName())
			       +  wxT(";\n");
		}
		else
		{
			sql += wxT("ALTER ") + GetObject()->GetTypeName().MakeUpper()
			       +  wxT(" ") + GetObject()->GetQuotedFullIdentifier()
			       +  wxT("\n  RENAME TO ") + qtIdent(GetName())
			       +  wxT(";\n");
		}
	}
}


void dlgProperty::AppendOwnerChange(wxString &sql, const wxString &objName)
{
	if (!GetObject() || GetObject()->GetOwner() != cbOwner->GetValue())
	{
		sql += wxT("ALTER ") + objName
		       +  wxT("\n  OWNER TO ") + qtIdent(cbOwner->GetValue())
		       +  wxT(";\n");
	}
}


void dlgProperty::AppendOwnerNew(wxString &sql, const wxString &objName)
{
	if (cbOwner->GetGuessedSelection() > 0)
		sql += wxT("ALTER ") + objName
		       +  wxT("\n  OWNER TO ") + qtIdent(cbOwner->GetValue())
		       +  wxT(";\n");
}


void dlgProperty::AppendSchemaChange(wxString &sql, const wxString &objName)
{
	wxString currentschema;

	if (GetObject()->GetMetaType() == PGM_SCHEMA)
	{
		currentschema = GetObject()->GetName();
	}
	else
	{
		currentschema = GetObject()->GetSchema()->GetName();
	}

	if (currentschema != cbSchema->GetValue())
	{
		sql += wxT("ALTER ") + objName
		       +  wxT("\n  SET SCHEMA ") + qtIdent(cbSchema->GetValue())
		       +  wxT(";\n");
	}
}


void dlgProperty::AppendComment(wxString &sql, const wxString &objName, pgObject *obj)
{
	wxString comment = txtComment->GetValue();
	if ((!obj && !comment.IsEmpty()) || (obj && obj->GetComment() != comment))
	{
		sql += wxT("COMMENT ON ") + objName
		       + wxT("\n  IS ") + qtDbString(comment) + wxT(";\n");
	}
}


void dlgProperty::AppendComment(wxString &sql, const wxString &objType, pgSchema *schema, pgObject *obj)
{
	wxString comment = txtComment->GetValue();
	if ((!obj && !comment.IsEmpty()) || (obj && obj->GetComment() != comment))
	{
		sql += wxT("COMMENT ON ") + objType + wxT(" ");
		if (schema)
			sql += schema->GetQuotedPrefix();
		sql += qtIdent(GetName()) + wxT("\n  IS ") + qtDbString(comment) + wxT(";\n");
	}
}


void dlgProperty::AppendQuoted(wxString &sql, const wxString &name)
{
	// quick and quite dirty:
	// !!! this is unsafe if the name itself contains a dot which isn't meant as separator between schema and object
	if (name.First('.') >= 0)
	{
		sql += qtIdent(name.BeforeFirst('.')) + wxT(".") + qtIdent(name.AfterFirst('.'));
	}
	else
		sql += qtIdent(name);
}

void dlgProperty::AppendQuotedType(wxString &sql, const wxString &name)
{
	// see AppendQuoted()
	if (name.First('.') >= 0)
	{
		sql += qtIdent(name.BeforeFirst('.')) + wxT(".") + qtTypeIdent(name.AfterFirst('.'));
	}
	else
		sql += qtTypeIdent(name);
}


void dlgProperty::FillCombobox(const wxString &query, ctlComboBoxFix *cb1, ctlComboBoxFix *cb2)
{
	if (!cb1 && !cb2)
		return;

	pgSet *set = connection->ExecuteSet(query);
	if (set)
	{
		while (!set->Eof())
		{
			if (cb1)
				cb1->Append(set->GetVal(0));
			if (cb2)
				cb2->Append(set->GetVal(0));
			set->MoveNext();
		}
		delete set;
	}

}


void dlgProperty::AddDatabases(ctlComboBoxFix *cb)
{
	FillCombobox(wxT("SELECT datname FROM pg_database ORDER BY 1"), cb);
}


void dlgProperty::AddUsers(ctlComboBoxFix *cb1, ctlComboBoxFix *cb2)
{
	if (connection->BackendMinimumVersion(8, 1))
	{
		FillCombobox(wxT("SELECT rolname FROM pg_roles WHERE rolcanlogin ORDER BY 1"), cb1, cb2);
	}
	else
	{
		FillCombobox(wxT("SELECT usename FROM pg_user ORDER BY 1"), cb1, cb2);
	}
}


void dlgProperty::AddGroups(ctlComboBoxFix *combo)
{
	if (connection->BackendMinimumVersion(8, 1))
	{
		FillCombobox(wxT("SELECT rolname FROM pg_roles WHERE NOT rolcanlogin ORDER BY 1"), combo);
	}
	else
	{
		FillCombobox(wxT("SELECT groname FROM pg_group ORDER BY 1"), combo);
	}
}


void dlgProperty::AddSchemas(ctlComboBoxFix *combo)
{
	if (connection->BackendMinimumVersion(8, 1))
	{
		FillCombobox(wxT("SELECT nspname FROM pg_namespace WHERE nspname NOT LIKE E'pg\\\\_%' AND nspname != 'information_schema' ORDER BY nspname"),
		             combo);
	}
}


void dlgProperty::PrepareTablespace(ctlComboBoxFix *cb, const OID current)
{
	wxASSERT(cb != 0);

	if (connection->BackendMinimumVersion(8, 0))
	{
		// Populate the combo
		cb->FillOidKey(connection, wxT("SELECT oid, spcname FROM pg_tablespace WHERE spcname <> 'pg_global' ORDER BY spcname"));

		if (current)
			cb->SetKey(current);
		else
		{
			if (database)
				cb->SetValue(database->GetDefaultTablespace());
			else
			{
				wxString def = connection->ExecuteScalar(wxT("SELECT current_setting('default_tablespace');"));
				if (def == wxEmptyString || def == wxT("unset"))
					def = wxT("pg_default");
				cb->SetValue(def);
			}
		}
	}
	else
		cb->Disable();
}


void dlgProperty::OnChangeStc(wxStyledTextEvent &ev)
{
	CheckChange();
}


void dlgProperty::OnChange(wxCommandEvent &ev)
{
	CheckChange();
}


void dlgProperty::OnChangeOwner(wxCommandEvent &ev)
{
	ctlComboBox *cb = cbOwner;
	if (cb)
		cb->GuessSelection(ev);
	CheckChange();
}


void dlgProperty::OnChangeReadOnly(wxCommandEvent &ev)
{
	size_t pos;
	bool showmessage;

	showmessage = chkReadOnly->GetValue()
	              && ! (!enableSQL2 && GetSql().Length() == 0 && sqlTextField1->GetText().Cmp(_("-- nothing to change")) == 0)
	              && ! (!enableSQL2 && GetSql().Length() == 0 && sqlTextField1->GetText().Cmp(_("-- definition incomplete")) == 0)
	              && ! (enableSQL2 && GetSql().Length() == 0 && GetSql2().Length() == 0 && sqlTextField1->GetText().Cmp(_("-- nothing to change")) == 0 && sqlTextField2->GetText().Length() == 0)
	              && ! (enableSQL2 && GetSql().Length() == 0 && GetSql2().Length() == 0 && sqlTextField1->GetText().Cmp(_("-- definition incomplete")) == 0 && sqlTextField2->GetText().Length() == 0)
	              && (sqlTextField1->GetText().Cmp(GetSql()) != 0 || (enableSQL2 && sqlTextField2->GetText().Cmp(GetSql2()) != 0));

	if (showmessage)
	{
		if (wxMessageBox(_("Are you sure you wish to cancel your edit?"), _("SQL editor"), wxYES_NO | wxNO_DEFAULT) != wxYES)
		{
			chkReadOnly->SetValue(false);
			return;
		}
	}

	sqlTextField1->SetReadOnly(chkReadOnly->GetValue());
	if (enableSQL2)
	{
		sqlTextField2->SetReadOnly(chkReadOnly->GetValue());
	}
	for (pos = 0; pos < nbNotebook->GetPageCount() - 1; pos++)
	{
		nbNotebook->GetPage(pos)->Enable(chkReadOnly->GetValue());
	}

	if (chkReadOnly->GetValue())
	{
		FillSQLTextfield();
	}
}


void dlgProperty::FillSQLTextfield()
{
	// create a function because this is a duplicated code
	sqlTextField1->SetReadOnly(false);
	if (enableSQL2)
	{
		sqlTextField2->SetReadOnly(false);
	}
	if (btnOK->IsEnabled())
	{
		wxString tmp;
		if (cbClusterSet && cbClusterSet->GetSelection() > 0)
		{
			replClientData *data = (replClientData *)cbClusterSet->wxItemContainer::GetClientData(cbClusterSet->GetSelection());
			if(data)
				tmp.Printf(_("-- Execute replicated using cluster \"%s\", set %ld\n"), data->cluster.c_str(), data->setId);
		}
		sqlTextField1->SetText(tmp + GetSql());
		if (enableSQL2)
		{
			sqlTextField2->SetText(GetSql2());
		}
	}
	else
	{
		if (GetObject())
			sqlTextField1->SetText(_("-- nothing to change"));
		else
			sqlTextField1->SetText(_("-- definition incomplete"));
		if (enableSQL2)
		{
			sqlTextField2->SetText(wxT(""));
		}
	}
	sqlTextField1->SetReadOnly(true);
	if (enableSQL2)
	{
		sqlTextField2->SetReadOnly(true);
	}
}


bool dlgProperty::tryUpdate(wxTreeItemId collectionItem)
{
	ctlTree *browser = mainForm->GetBrowser();
	pgCollection *collection = (pgCollection *)browser->GetObject(collectionItem);
	if (collection && collection->IsCollection() && factory->GetCollectionFactory() == collection->GetFactory())
	{
		pgObject *data = CreateObject(collection);
		if (data)
		{

			wxString nodeName = this->GetDisplayName();
			if (nodeName.IsEmpty())
				nodeName = data->GetDisplayName();

			size_t pos = 0;
			wxTreeItemId newItem;

			if (!data->IsCreatedBy(columnFactory))
			{
				// columns should be appended, not inserted alphabetically

				wxCookieType cookie;
				newItem = browser->GetFirstChild(collectionItem, cookie);
				while (newItem)
				{
					if (browser->GetItemText(newItem) > nodeName)
						break;
					pos++;
					newItem = browser->GetNextChild(collectionItem, cookie);
				}
			}

			if (newItem)
				browser->InsertItem(collectionItem, pos, nodeName, data->GetIconId(), -1, data);
			else
				browser->AppendItem(collectionItem, nodeName, data->GetIconId(), -1, data);

			if (data->WantDummyChild())
				browser->AppendItem(data->GetId(), wxT("Dummy"));

			if (browser->GetSelection() == item)
				collection->ShowTreeDetail(browser, 0, mainForm->GetProperties());
			else
				collection->UpdateChildCount(browser);
		}
		else
		{
			// CreateObject didn't return a new pgObject; refresh the complete collection
			mainForm->Refresh(collection);
		}
		return true;
	}
	return false;
}



void dlgProperty::ShowObject()
{
	mainForm->ObjectBrowserRefreshing(true);
	pgObject *data = GetObject();

	// We might have a parent to refresh. If so, the children will
	// inherently get refreshed as well. Yay :-)
	if (owneritem)
	{
		// Get the object node in case we need it later
		wxTreeItemId objectnode = mainForm->GetBrowser()->GetItemParent(owneritem);

		// Stash the selected items path
		wxString currentPath = mainForm->GetCurrentNodePath();

		pgObject *tblobj = mainForm->GetBrowser()->GetObject(owneritem);

		if (tblobj)
		{
			dlgProperty *ownDialog = NULL;
			if (data)
			{
				ownDialog = data->GetWindowPtr();
				data->SetWindowPtr(NULL);
			}
			mainForm->Refresh(tblobj);
			if (data)
			{
				data->SetWindowPtr(ownDialog);
			}
		}

		// Restore the previous selection...
		mainForm->SetCurrentNode(mainForm->GetBrowser()->GetRootItem(), currentPath);
	}
	else if (data)
	{
		pgObject *newData = data->Refresh(mainForm->GetBrowser(), item);
		if (newData && newData != data)
		{
			mainForm->SetCurrentObject(newData);
			mainForm->GetBrowser()->SetItemData(item, newData);

			newData->SetId(item);
			delete data;
			SetObject(newData);

			newData->UpdateIcon(mainForm->GetBrowser());
		}
		if (newData)
		{
			mainForm->GetBrowser()->DeleteChildren(newData->GetId());

			if (item == mainForm->GetBrowser()->GetSelection())
				newData->ShowTree(mainForm, mainForm->GetBrowser(), mainForm->GetProperties(), 0);
			mainForm->GetBrowser()->SetItemText(item, newData->GetFullName());
			mainForm->GetSqlPane()->SetReadOnly(false);
			mainForm->GetSqlPane()->SetText(newData->GetSql(mainForm->GetBrowser()));
			mainForm->GetSqlPane()->SetReadOnly(true);
		}
	}
	else if (item && chkReadOnly->GetValue())
	{
		wxTreeItemId collectionItem = item;

		while (collectionItem)
		{
			// search up the tree for our collection
			if (tryUpdate(collectionItem))
				break;
			collectionItem = mainForm->GetBrowser()->GetItemParent(collectionItem);
		}
	}
	else // Brute force update the current item
	{
		pgObject *currobj = mainForm->GetBrowser()->GetObject(mainForm->GetBrowser()->GetSelection());

		if (currobj)
			mainForm->Refresh(currobj);
	}
	mainForm->ObjectBrowserRefreshing(false);
}


bool dlgProperty::apply(const wxString &sql, const wxString &sql2)
{
	wxString tmp;
	pgConn *myConn = connection;

	if (GetDisconnectFirst())
	{
		myConn = database->GetServer()->GetConnection();
		database->Disconnect();
	}

	if (!sql.IsEmpty())
	{
		wxArrayString queries;

		if (WannaSplitQueries())
			queries = SplitQueries(BuildSql(sql));
		else
			queries.Add(BuildSql(sql));

		for (size_t index = 0; index < queries.GetCount(); index++)
		{
			tmp = queries.Item(index);
			if (!myConn->ExecuteVoid(tmp))
			{
				// error message is displayed inside ExecuteVoid
				return false;
			}

			if (database)
				database->AppendSchemaChange(tmp);
		}
	}

	// Process the second SQL statement. This is primarily only used by
	// CREATE DATABASE which cannot be run in a multi-statement query in
	// PostgreSQL 8.3+
	if (!sql2.IsEmpty())
	{
		tmp = BuildSql(sql2);

		if (!myConn->ExecuteVoid(tmp))
		{
			// error message is displayed inside ExecuteVoid
			// Warn the user about partially applied changes, but don't bail out.
			// Carry on as if everything was successful (because the most important
			// change was!!
			wxMessageBox(_("An error occurred executing the second stage SQL statement.\n\nChanges may have been partially applied."), _("Warning"), wxICON_EXCLAMATION | wxOK, this);
		}
		else // Only apend schema changes if there was no error.
		{
			if (database)
				database->AppendSchemaChange(tmp);
		}
	}

	ShowObject();

	return true;
}


wxString dlgProperty::BuildSql(const wxString &sql)
{
	wxString tmp;

	if (cbClusterSet && cbClusterSet->GetSelection() > 0)
	{
		replClientData *data = (replClientData *)cbClusterSet->wxItemContainer::GetClientData(cbClusterSet->GetSelection());
		if (data)
		{
			if (data->majorVer > 1 || (data->majorVer == 1 && data->minorVer >= 2))
			{
				// From slony version 2.2.0 onwards ddlscript_prepare() method is removed and
				// ddlscript_complete() method arguments got changed so we have to use ddlcapture() method
				// instead of ddlscript_prepare() and changed the argument of ddlscript_complete() method
				if ((data->majorVer == 2 && data->minorVer >= 2) || (data->majorVer > 2))
				{
					tmp = wxT("SELECT ") + qtIdent(data->cluster)
					      + wxT(".ddlcapture(") + qtDbString(sql) + wxT(", ") + wxT("NULL::text") + wxT(" );\n")
					      + wxT("SELECT ") + qtIdent(data->cluster)
					      + wxT(".ddlscript_complete(") + wxT("NULL::text") + wxT(" );\n");
				}
				else
				{
					tmp = wxT("SELECT ") + qtIdent(data->cluster)
					      + wxT(".ddlscript_prepare(") + NumToStr(data->setId) + wxT(", -1);\n")
					      + sql + wxT(";\n")
					      + wxT("SELECT ") + qtIdent(data->cluster)
					      + wxT(".ddlscript_complete(") + NumToStr(data->setId) + wxT(", ")
					      + qtDbString(sql) + wxT(", -1);\n");
				}
			}
			else
			{
				tmp = wxT("SELECT ") + qtIdent(data->cluster)
				      + wxT(".ddlscript(") + NumToStr(data->setId) + wxT(", ")
				      + qtDbString(sql) + wxT(", 0);\n");
			}
		}
	}
	else
		tmp = sql;

	return tmp;
}


wxArrayString dlgProperty::SplitQueries(const wxString &sql)
{
	wxArrayString queries;
	wxString query;
	wxString c;

	bool antislash = false;
	bool quote_string = false;
	bool doublequote_string = false;

	for (size_t item = 0; item < sql.Length(); item++)
	{
		c = sql.GetChar(item);

		if (c == wxT("\\"))
			antislash = true;

		if (c == wxT("'"))
		{
			if (antislash)
				antislash = false;
			else if (quote_string)
				quote_string = false;
			else if (!doublequote_string)
				quote_string = true;
		}

		if (c == wxT("\""))
		{
			if (antislash)
				antislash = false;
			else if (doublequote_string)
				doublequote_string = false;
			else if (!quote_string)
				doublequote_string = true;
		}

		query = query + c;

		if (c == wxT(";") && !antislash && !quote_string && !doublequote_string)
		{
			queries.Add(query);
			query = wxEmptyString;
		}
	}

	return queries;
}


void dlgProperty::OnOK(wxCommandEvent &ev)
{
#ifdef __WXGTK__
	if (!btnOK->IsEnabled())
		return;
#endif
	if (!IsUpToDate())
	{
		if (wxMessageBox(wxT("The object has been changed by another user. Do you wish to continue to try to update it?"), wxT("Overwrite changes?"), wxYES_NO) != wxYES)
			return;
	}

	EnableOK(false);

	if (IsModal())
	{
		EndModal(0);
		return;
	}

	wxString sql;
	wxString sql2;
	if (chkReadOnly->GetValue())
	{
		sql = GetSql();
		sql2 = GetSql2();
	}
	else
	{
		sql = sqlTextField1->GetText();
		if (enableSQL2)
		{
			sql2 = sqlTextField2->GetText();
		}
		else
		{
			sql2 = wxT("");
		}
	}

	if (!apply(sql, sql2))
	{
		EnableOK(true);
		return;
	}

	Destroy();
}


void dlgProperty::OnPageSelect(wxNotebookEvent &event)
{
	if (sqlTextField1 && chkReadOnly->GetValue() &&
	        event.GetSelection() == (int)nbNotebook->GetPageCount() - 1)
	{
		FillSQLTextfield();
	}
}



void dlgProperty::InitDialog(frmMain *frame, pgObject *node)
{
	CenterOnParent();
	if (!connection)
		connection = node->GetConnection();
	database = node->GetDatabase();

	if (factory != node->GetFactory() && !node->IsCollection())
	{
		wxCookieType cookie;
		wxTreeItemId collectionItem = frame->GetBrowser()->GetFirstChild(node->GetId(), cookie);
		while (collectionItem)
		{
			pgCollection *collection = (pgCollection *)frame->GetBrowser()->GetObject(collectionItem);
			if (collection && collection->IsCollection() && collection->IsCollectionFor(node))
				break;

			collectionItem = frame->GetBrowser()->GetNextChild(node->GetId(), cookie);
		}
		item = collectionItem;
	}
	else
		item = node->GetId();

	// Additional hacks to get the table to refresh when modifying sub-objects
	if (!item && (node->GetMetaType() == PGM_TABLE || node->GetMetaType() == PGM_VIEW
	              || node->GetMetaType() == GP_PARTITION || node->GetMetaType() == PGM_DOMAIN))
		owneritem = node->GetId();

	int metatype = node->GetMetaType();

	switch (metatype)
	{
		case PGM_COLUMN:
			owneritem = node->GetTable()->GetId();
			break;

		case PGM_CHECK:
		case PGM_FOREIGNKEY:
		case PGM_CONSTRAINT:
		case PGM_EXCLUDE:
		case PGM_INDEX:
		case PGM_PRIMARYKEY:
		case PGM_UNIQUE:
		case PGM_TRIGGER:
		case PGM_RULE: // Rules are technically table objects! Yeuch
		case EDB_PACKAGEFUNCTION:
		case EDB_PACKAGEVARIABLE:
		case PGM_SCHEDULE:
		case PGM_STEP:
			if (node->IsCollection())
				owneritem = frame->GetBrowser()->GetParentObject(node->GetId())->GetId();
			else
				owneritem = frame->GetBrowser()->GetParentObject(frame->GetBrowser()->GetParentObject(node->GetId())->GetId())->GetId();
			break;

		default:
			// we want to do this as objects can change schema
			owneritem = node->GetId();
			break;
	}
}


dlgProperty *dlgProperty::CreateDlg(frmMain *frame, pgObject *node, bool asNew, pgaFactory *factory)
{
	if (!factory)
	{
		factory = node->GetFactory();
		if (node->IsCollection())
			factory = ((pgaCollectionFactory *)factory)->GetItemFactory();
	}

	pgObject *currentNode, *parentNode;
	if (asNew)
		currentNode = 0;
	else
		currentNode = node;

	if (factory != node->GetFactory())
		parentNode = node;
	else
		parentNode = frame->GetBrowser()->GetObject(
		                 frame->GetBrowser()->GetItemParent(node->GetId()));

	if (parentNode && parentNode->IsCollection() && parentNode->GetMetaType() != PGM_SERVER)
		parentNode = frame->GetBrowser()->GetObject(
		                 frame->GetBrowser()->GetItemParent(parentNode->GetId()));

	dlgProperty *dlg = 0;

	if (factory)
	{
		dlg = factory->CreateDialog(frame, currentNode, parentNode);
		if (dlg)
		{
			if (factory->IsCollection())
				factory = ((pgaCollectionFactory *)factory)->GetItemFactory();
			wxASSERT(factory);

			dlg->InitDialog(frame, node);

			if (currentNode)
				currentNode->SetWindowPtr(dlg);
		}
	}
	return dlg;
}


bool dlgProperty::CreateObjectDialog(frmMain *frame, pgObject *node, pgaFactory *factory)
{
	if (node->GetMetaType() != PGM_SERVER)
	{
		pgConn *conn = node->GetConnection();
		if (!conn || conn->GetStatus() != PGCONN_OK || !conn->IsAlive())
			return false;
	}

	dlgProperty *dlg = NULL;

	if (node)
		dlg = node->GetWindowPtr();

	if (dlg)
		dlg->Raise();
	else
	{
		dlg = CreateDlg(frame, node, true, factory);

		if (dlg)
		{
			dlg->SetTitle(wxGetTranslation(dlg->factory->GetNewString()));

			dlg->CreateAdditionalPages();
			dlg->Go();
			dlg->CheckChange();
		}
		else
			wxMessageBox(_("Not implemented."));
	}

	return true;
}


bool dlgProperty::EditObjectDialog(frmMain *frame, ctlSQLBox *sqlbox, pgObject *node)
{
	if (node->GetMetaType() != PGM_SERVER)
	{
		pgConn *conn = node->GetConnection();
		if (!conn || conn->GetStatus() != PGCONN_OK || !conn->IsAlive())
			return false;
	}

	// If this is a function or view, hint that the user might want to edit the object in
	// the query tool.
	if (node->GetMetaType() == PGM_FUNCTION || node->GetMetaType() == PGM_VIEW)
	{
		if (frmHint::ShowHint(frame, HINT_OBJECT_EDITING) == wxID_CANCEL)
			return false;
	}

	dlgProperty *dlg = NULL;

	if (node)
		dlg = node->GetWindowPtr();

	if (dlg)
		dlg->Raise();
	else
	{
		dlg = CreateDlg(frame, node, false);

		if (dlg)
		{
			wxString typeName = dlg->factory->GetTypeName();
			dlg->SetTitle(wxString(wxGetTranslation(typeName)) + wxT(" ") + node->GetFullIdentifier());

			dlg->CreateAdditionalPages();
			dlg->Go();

			dlg->CheckChange();
		}
		else
			wxMessageBox(_("Not implemented."));
	}

	return true;
}

wxString dlgProperty::qtDbString(const wxString &str)
{
	// Use the server aware version if possible
	if (connection)
		return connection->qtDbString(str);
	else if (database)
		return database->GetConnection()->qtDbString(str);
	else
	{
		wxString ret = str;
		ret.Replace(wxT("\\"), wxT("\\\\"));
		ret.Replace(wxT("'"), wxT("''"));
		ret.Append(wxT("'"));
		ret.Prepend(wxT("'"));
		return ret;
	}
}

void dlgProperty::OnHelp(wxCommandEvent &ev)
{
	wxString page = GetHelpPage();

	if (!page.IsEmpty())
	{
		if (page.StartsWith(wxT("pg/")))
		{
			if (connection)
			{
				if (connection->GetIsEdb())
					DisplayHelp(page.Mid(3), HELP_ENTERPRISEDB);
				else if (connection->GetIsGreenplum())
					DisplayHelp(page.Mid(3), HELP_GREENPLUM);
				else
					DisplayHelp(page.Mid(3), HELP_POSTGRESQL);
			}
			else
				DisplayHelp(page.Mid(3), HELP_POSTGRESQL);
		}
		else if (page.StartsWith(wxT("slony/")))
			DisplayHelp(page.Mid(6), HELP_SLONY);
		else
			DisplayHelp(page, HELP_PGADMIN);
	}
}

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


dlgTypeProperty::dlgTypeProperty(pgaFactory *f, frmMain *frame, const wxString &resName)
	: dlgProperty(f, frame, resName)
{
	isVarLen = false;
	isVarPrec = false;
	if (wxWindow::FindWindow(XRCID("txtLength")))
	{
		txtLength = CTRL_TEXT("txtLength");
		txtLength->SetValidator(numericValidator);
		txtLength->Disable();
	}
	else
		txtLength = 0;
	if (wxWindow::FindWindow(XRCID("txtPrecision")))
	{
		txtPrecision = CTRL_TEXT("txtPrecision");
		txtPrecision->SetValidator(numericValidator);
		txtPrecision->Disable();
	}
	else
		txtPrecision = 0;
}


void dlgTypeProperty::FillDatatype(ctlComboBox *cb, bool withDomains, bool addSerials)
{
	FillDatatype(cb, 0, withDomains, addSerials);
}

void dlgTypeProperty::FillDatatype(ctlComboBox *cb, ctlComboBox *cb2, bool withDomains, bool addSerials)
{

	if (dtCache.IsEmpty())
	{
		// A column dialog is directly called, no datatype caching is done.
		// Fetching datatypes from server.
		DatatypeReader tr(database, withDomains, addSerials);
		while (tr.HasMore())
		{
			pgDatatype dt = tr.GetDatatype();

			AddType(wxT("?"), tr.GetOid(), dt.GetQuotedSchemaPrefix(database) + dt.QuotedFullName());
			cb->Append(dt.GetQuotedSchemaPrefix(database) + dt.QuotedFullName());
			if (cb2)
				cb2->Append(dt.GetQuotedSchemaPrefix(database) + dt.QuotedFullName());
			tr.MoveNext();
		}
	}
	else
	{
		// A column dialog is called from a table dialog where we have already cached the datatypes.
		// Using cached datatypes.
		size_t i;
		for (i = 0; i < dtCache.GetCount(); i++)
		{
			AddType(wxT("?"), dtCache.Item(i)->GetOid(), dtCache.Item(i)->GetTypename());
			cb->Append(dtCache.Item(i)->GetTypename());
			if (cb2)
				cb2->Append(dtCache.Item(i)->GetTypename());
		}
	}

}


int dlgTypeProperty::Go(bool modal)
{
	if (GetObject())
	{
		if (txtLength)
			txtLength->SetValidator(numericValidator);
		if (txtPrecision)
			txtPrecision->SetValidator(numericValidator);
	}
	return dlgProperty::Go(modal);
}



void dlgTypeProperty::AddType(const wxString &typ, const OID oid, const wxString quotedName)
{
	wxString vartyp;
	if (typ == wxT("?"))
	{
		switch ((long)oid)
		{
			case PGOID_TYPE_BIT:
			case PGOID_TYPE_BIT_ARRAY:
			case PGOID_TYPE_VARBIT:
			case PGOID_TYPE_VARBIT_ARRAY:
			case PGOID_TYPE_BPCHAR:
			case PGOID_TYPE_BPCHAR_ARRAY:
			case PGOID_TYPE_VARCHAR:
			case PGOID_TYPE_VARCHAR_ARRAY:
				vartyp = wxT("L");
				break;
			case PGOID_TYPE_TIME:
			case PGOID_TYPE_TIME_ARRAY:
			case PGOID_TYPE_TIMETZ:
			case PGOID_TYPE_TIMETZ_ARRAY:
			case PGOID_TYPE_TIMESTAMP:
			case PGOID_TYPE_TIMESTAMP_ARRAY:
			case PGOID_TYPE_TIMESTAMPTZ:
			case PGOID_TYPE_TIMESTAMPTZ_ARRAY:
			case PGOID_TYPE_INTERVAL:
			case PGOID_TYPE_INTERVAL_ARRAY:
				vartyp = wxT("D");
				break;
			case PGOID_TYPE_NUMERIC:
			case PGOID_TYPE_NUMERIC_ARRAY:
				vartyp = wxT("P");
				break;
			default:
				vartyp = wxT(" ");
				break;
		}
	}
	else
		vartyp = typ;

	types.Add(vartyp + NumToStr(oid) + wxT(":") + quotedName);
}


wxString dlgTypeProperty::GetTypeInfo(int sel)
{
	wxString str;
	if (sel >= 0)
		str = types.Item(sel);

	return str;
}


wxString dlgTypeProperty::GetTypeOid(int sel)
{
	wxString str;
	if (sel >= 0)
		str = types.Item(sel).Mid(1).BeforeFirst(':');

	return str;
}


wxString dlgTypeProperty::GetQuotedTypename(int sel)
{
	wxString sql, suffix;
	bool isArray = false;

	if (sel >= 0)
	{
		sql = types.Item(sel).AfterFirst(':');

		// Deal with time/timestamp first as they're special cases
		if (sql.Left(19) == wxT("time with time zone"))
		{
			if (sql.Right(2) == wxT("[]"))
				isArray = true;
			sql = wxT("time");
			suffix = wxT("with time zone");
		}
		else if (sql.Left(21) == wxT("time without time zone"))
		{
			if (sql.Right(2) == wxT("[]"))
				isArray = true;
			sql = wxT("time");
			suffix = wxT("without time zone");
		}
		else if (sql.Left(24) == wxT("timestamp with time zone"))
		{
			if (sql.Right(2) == wxT("[]"))
				isArray = true;
			sql = wxT("timestamp");
			suffix = wxT("with time zone");
		}
		else if (sql.Left(27) == wxT("timestamp without time zone"))
		{
			if (sql.Right(2) == wxT("[]"))
				isArray = true;
			sql = wxT("timestamp");
			suffix = wxT("without time zone");
		}
		else if (sql.Right(2) == wxT("[]"))
		{
			sql = sql.SubString(0, sql.Len() - 3);
			isArray = true;
		}
		else if (sql.Right(3) == wxT("[]\""))
		{
			sql = sql.SubString(1, sql.Len() - 4);
			isArray = true;
		}

		// Stick the length on
		if (isVarLen && txtLength)
		{
			wxString varlen = txtLength->GetValue();
			if (!varlen.IsEmpty() && NumToStr(StrToLong(varlen)) == varlen && StrToLong(varlen) >= minVarLen)
			{
				sql += wxT("(") + varlen;
				if (isVarPrec && txtPrecision)
				{
					wxString varprec = txtPrecision->GetValue();
					if (!varprec.IsEmpty())
						sql += wxT(",") + varprec;
				}
				sql += wxT(")");
			}
		}
	}

	// Append any post-length suffix
	if (suffix.length())
		sql += wxT(" ") + suffix;

	// Append any array decoration
	if (isArray)
		sql += wxT("[]");

	return sql;
}


void dlgTypeProperty::CheckLenEnable()
{
	int sel = cbDatatype->GetGuessedSelection();
	if (sel >= 0)
	{
		wxString info = types.Item(sel);
		isVarPrec = info.StartsWith(wxT("P"));
		isVarLen =  isVarPrec || info.StartsWith(wxT("L")) || info.StartsWith(wxT("D"));
		minVarLen = (info.StartsWith(wxT("D")) ? 0 : 1);
		maxVarLen = isVarPrec ? 1000 :
		            minVarLen ? 0x7fffffff : 10;
	}
}


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


dlgCollistProperty::dlgCollistProperty(pgaFactory *f, frmMain *frame, const wxString &resName, pgTable *parentNode)
	: dlgProperty(f, frame, resName)
{
	columns = 0;
	table = parentNode;
}


dlgCollistProperty::dlgCollistProperty(pgaFactory *f, frmMain *frame, const wxString &resName, ctlListView *colList)
	: dlgProperty(f, frame, resName)
{
	columns = colList;
	table = 0;
}


int dlgCollistProperty::Go(bool modal)
{
	if (columns)
	{
		int pos;
		// iterate cols
		for (pos = 0 ; pos < columns->GetItemCount() ; pos++)
		{
			wxString col = columns->GetItemText(pos);
			if (cbColumns->FindString(col) < 0)
			{
				cbColumns->Append(col, StrToOid(columns->GetText(pos, 7)));
			}
		}
	}
	if (table)
	{
		wxCookieType cookie;
		pgObject *data;
		wxTreeItemId columnsItem = mainForm->GetBrowser()->GetFirstChild(table->GetId(), cookie);
		while (columnsItem)
		{
			data = mainForm->GetBrowser()->GetObject(columnsItem);
			if (data->GetMetaType() == PGM_COLUMN && data->IsCollection())
				break;
			columnsItem = mainForm->GetBrowser()->GetNextChild(table->GetId(), cookie);
		}

		if (columnsItem)
		{
			wxCookieType cookie;
			pgColumn *column;
			wxTreeItemId item = mainForm->GetBrowser()->GetFirstChild(columnsItem, cookie);

			// check columns
			while (item)
			{
				column = (pgColumn *)mainForm->GetBrowser()->GetObject(item);
				if (column->IsCreatedBy(columnFactory))
				{
					if (column->GetColNumber() > 0)
					{
						cbColumns->Append(column->GetName(), column->GetAttTypId());
					}
				}

				item = mainForm->GetBrowser()->GetNextChild(columnsItem, cookie);
			}
		}
	}

	return dlgProperty::Go(modal);
}



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


BEGIN_EVENT_TABLE(dlgSecurityProperty, dlgProperty)
	EVT_BUTTON(CTL_ADDPRIV,             dlgSecurityProperty::OnAddPriv)
	EVT_BUTTON(CTL_DELPRIV,             dlgSecurityProperty::OnDelPriv)
#ifdef __WXMAC__
	EVT_SIZE(                           dlgSecurityProperty::OnChangeSize)
#endif
END_EVENT_TABLE();

void dlgSecurityProperty::SetPrivilegesLayout()
{
	securityPage->lbPrivileges->GetParent()->Layout();
}

dlgSecurityProperty::dlgSecurityProperty(pgaFactory *f, frmMain *frame, pgObject *obj, const wxString &resName, const wxString &privList, const char *privChar)
	: dlgProperty(f, frame, resName)
{
	securityChanged = false;


	if (!privList.IsEmpty() && (!obj || obj->CanCreate()))
	{
		securityPage = new ctlSecurityPanel(nbNotebook, privList, privChar, frame->GetImageList());

		if (obj)
		{

			wxArrayString groups;
			// Fetch Groups Information
			pgSet *setGrp = obj->GetConnection()->ExecuteSet(wxT("SELECT groname FROM pg_group ORDER BY groname"));

			if (setGrp)
			{
				while (!setGrp->Eof())
				{
					groups.Add(setGrp->GetVal(0));
					setGrp->MoveNext();
				}
				delete setGrp;
			}

			wxString str = obj->GetAcl();
			if (!str.IsEmpty())
			{
				str = str.Mid(1, str.Length() - 2);
				wxStringTokenizer tokens(str, wxT(","));

				while (tokens.HasMoreTokens())
				{
					wxString str = tokens.GetNextToken();
					if (str[0U] == '"')
						str = str.Mid(1, str.Length() - 2);

					wxString name = str.BeforeLast('=');
					wxString value;

					connection = obj->GetConnection();
					if (connection->BackendMinimumVersion(7, 4))
						value = str.Mid(name.Length() + 1).BeforeLast('/');
					else
						value = str.Mid(name.Length() + 1);

					int icon = userFactory.GetIconId();

					if (name.Left(6).IsSameAs(wxT("group "), false))
					{
						icon = groupFactory.GetIconId();
						name = wxT("group ") + qtStrip(name.Mid(6));
					}
					else if (name.IsEmpty())
					{
						icon = PGICON_PUBLIC;
						name = wxT("public");
					}
					else
					{
						name = qtStrip(name);
						for (unsigned int index = 0; index < groups.Count(); index++)
							if (name == groups[index])
							{
								name = wxT("group ") + name;
								icon = groupFactory.GetIconId();
								break;
							}
					}

					securityPage->lbPrivileges->AppendItem(icon, name, value);
					currentAcl.Add(name + wxT("=") + value);
				}
			}
			else
			{
				int icon = PGICON_PUBLIC;
				wxString name = wxT("public");
				wxString value;
				if (obj->GetMetaType() == PGM_DATABASE)
					value = wxT("Tc");
				else if (obj->GetMetaType() == PGM_FUNCTION)
					value = wxT("X");
				else if (obj->GetMetaType() == PGM_LANGUAGE)
					value = wxT("U");

				if (value != wxEmptyString)
				{
					securityPage->lbPrivileges->AppendItem(icon, name, value);
					currentAcl.Add(name + wxT("=") + value);
				}
			}
		}
	}
	else
		securityPage = NULL;
}


dlgSecurityProperty::~dlgSecurityProperty()
{
}



#ifdef __WXMAC__
void dlgSecurityProperty::OnChangeSize(wxSizeEvent &ev)
{
	if (securityPage)
		securityPage->lbPrivileges->SetSize(wxDefaultCoord, wxDefaultCoord,
		                                    ev.GetSize().GetWidth(), ev.GetSize().GetHeight() - 550);
	if (GetAutoLayout())
	{
		Layout();
	}
}
#endif


int dlgSecurityProperty::Go(bool modal)
{
	if (securityPage)
	{
		if (cbOwner && !cbOwner->GetCount())
		{
			if (!GetObject())
				cbOwner->Append(wxEmptyString);
			AddGroups(cbOwner);
			AddUsers(cbOwner);
		}

		securityPage->SetConnection(connection);
		//securityPage->Layout();
	}

	return dlgProperty::Go(modal);
}


void dlgSecurityProperty::AddGroups(ctlComboBox *comboBox)
{
	if (!((securityPage && securityPage->cbGroups) || comboBox))
		return;

	pgSet *set = connection->ExecuteSet(wxT("SELECT groname FROM pg_group ORDER BY groname"));

	if (set)
	{
		while (!set->Eof())
		{
			if (securityPage && securityPage->cbGroups)
				securityPage->cbGroups->Append(wxT("group ") + set->GetVal(0));
			if (comboBox)
				comboBox->Append(set->GetVal(0));
			set->MoveNext();
		}
		delete set;
	}
}


void dlgSecurityProperty::AddUsers(ctlComboBox *combobox)
{
	if (securityPage && securityPage->cbGroups && settings->GetShowUsersForPrivileges())
	{
		securityPage->stGroup->SetLabel(_("Group/User"));
		dlgProperty::AddUsers(securityPage->cbGroups, combobox);
		Layout();
	}
	else
		dlgProperty::AddUsers(combobox);
}


void dlgSecurityProperty::OnAddPriv(wxCommandEvent &ev)
{
	securityChanged = true;
	EnableOK(btnOK->IsEnabled());
}


void dlgSecurityProperty::OnDelPriv(wxCommandEvent &ev)
{
	securityChanged = true;
	EnableOK(btnOK->IsEnabled());
}


wxString dlgSecurityProperty::GetHelpPage() const
{
	if (nbNotebook->GetSelection() == (int)nbNotebook->GetPageCount() - 2)
		return wxT("pg/sql-grant");
	else
		return dlgProperty::GetHelpPage();
}


void dlgSecurityProperty::EnableOK(bool enable, bool ignoreSql)
{
	// Don't enable the OK button if the object isn't yet created,
	// leave that to the object dialog.
	if (securityChanged && GetObject() && !ignoreSql)
	{
		wxString sql = GetSql();
		if (sql.IsEmpty())
		{
			enable = false;
			securityChanged = false;
		}
		else
			enable = true;
	}
	dlgProperty::EnableOK(enable);
}


wxString dlgSecurityProperty::GetGrant(const wxString &allPattern, const wxString &grantObject)
{
	if (securityPage)
		return securityPage->GetGrant(allPattern, grantObject, &currentAcl);
	else
		return wxString();
}

bool dlgSecurityProperty::DisablePrivilege(const wxString &priv)
{
	if (securityPage)
		return securityPage->DisablePrivilege(priv);
	else
		return true;
}

void dlgSecurityProperty::AppendCurrentAcl(const wxString &name, const wxString &value)
{
	if (!(name.IsEmpty() && value.IsEmpty()))
		currentAcl.Add(name + wxT("=") + value);
}


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


BEGIN_EVENT_TABLE(dlgDefaultSecurityProperty, dlgSecurityProperty)
	EVT_BUTTON(CTL_DEFADDPRIV, dlgDefaultSecurityProperty::OnAddPriv)
	EVT_BUTTON(CTL_DEFDELPRIV, dlgDefaultSecurityProperty::OnDelPriv)
#ifdef __WXMAC__
	EVT_SIZE(                  dlgDefaultSecurityProperty::OnChangeSize)
#endif
END_EVENT_TABLE();


dlgDefaultSecurityProperty::dlgDefaultSecurityProperty(pgaFactory *f, frmMain *frame, pgObject *obj, const wxString &resName, const wxString &privList, const char *privChar, bool createDefPrivPanel)
	: dlgSecurityProperty(f, frame, obj, resName, privList, privChar), defaultSecurityChanged(false)
{
	pgConn *l_conn = obj ? obj->GetConnection() : connection;
	if ((!obj || obj->CanCreate()) && createDefPrivPanel)
		defaultSecurityPage = new ctlDefaultSecurityPanel(l_conn, nbNotebook, frame->GetImageList());
	else
		defaultSecurityPage = NULL;
}


void dlgDefaultSecurityProperty::AddGroups(ctlComboBox *comboBox)
{
	if (!((securityPage && securityPage->cbGroups) || comboBox || defaultSecurityPage))
		return;

	pgSet *set = connection->ExecuteSet(wxT("SELECT groname FROM pg_group ORDER BY groname"));

	if (set)
	{
		while (!set->Eof())
		{
			if (securityPage && securityPage->cbGroups)
				securityPage->cbGroups->Append(wxT("group ") + set->GetVal(0));

			if (comboBox)
				comboBox->Append(set->GetVal(0));

			if (defaultSecurityPage)
				defaultSecurityPage->m_groups.Add(wxT("group ") + set->GetVal(0));

			set->MoveNext();
		}
		delete set;
	}
}


void dlgDefaultSecurityProperty::AddUsers(ctlComboBox *combobox)
{
	if ((securityPage && securityPage->cbGroups) || defaultSecurityPage || combobox)
	{
		wxString strFetchUserQuery =
		    connection->BackendMinimumVersion(8, 1) ?
		    wxT("SELECT rolname FROM pg_roles WHERE rolcanlogin ORDER BY 1") :
		    wxT("SELECT usename FROM pg_user ORDER BY 1");

		pgSet *set = connection->ExecuteSet(strFetchUserQuery);
		if (set)
		{
			while (!set->Eof())
			{
				if (settings->GetShowUsersForPrivileges())
				{
					if (securityPage && securityPage->cbGroups)
						securityPage->cbGroups->Append(set->GetVal(0));

					if (defaultSecurityPage)
						defaultSecurityPage->m_groups.Add(set->GetVal(0));
				}

				if (combobox)
					combobox->Append(set->GetVal(0));

				set->MoveNext();
			}
			delete set;
		}
	}
}

#ifdef __WXMAC__
void dlgDefaultSecurityProperty::OnChangeSize(wxSizeEvent &ev)
{
	wxSize l_size = ev.GetSize();
	if (defaultSecurityPage && l_size.GetWidth() > 10 && l_size.GetHeight() > 25)
		defaultSecurityPage->SetSize(l_size.GetWidth() - 10, l_size.GetHeight() - 25);
	dlgSecurityProperty::OnChangeSize(ev);
}
#endif


void dlgDefaultSecurityProperty::EnableOK(bool enable, bool ignoreSql)
{
	// Don't enable the OK button if the object isn't yet created,
	// leave that to the object dialog.
	if (GetObject() && !ignoreSql)
	{
		wxString sql = GetSql();
		if (sql.IsEmpty())
		{
			enable = false;
		}
		else
			enable = true;
	}
	dlgSecurityProperty::EnableOK(enable, ignoreSql);
}


void dlgDefaultSecurityProperty::OnAddPriv(wxCommandEvent &ev)
{
	defaultSecurityChanged = true;
	EnableOK(btnOK->IsEnabled());
}


void dlgDefaultSecurityProperty::OnDelPriv(wxCommandEvent &ev)
{
	defaultSecurityChanged = true;
	EnableOK(btnOK->IsEnabled());
}

wxString dlgDefaultSecurityProperty::GetDefaultPrivileges(const wxString &schemaName)
{
	if (defaultSecurityChanged)
		return defaultSecurityPage->GetDefaultPrivileges(schemaName);
	return wxT("");
}

int dlgDefaultSecurityProperty::Go(bool modal, bool createDefPrivs, const wxString &defPrivsOnTables,
                                   const wxString &defPrivsOnSeqs, const wxString &defPrivsOnFuncs,
                                   const wxString &defPrivsOnTypes)
{
	if (securityPage)
	{
		if (cbOwner && !cbOwner->GetCount())
		{
			if (!GetObject())
				cbOwner->Append(wxEmptyString);
			AddGroups(cbOwner);
			AddUsers(cbOwner);
		}

		securityPage->SetConnection(connection);
		//securityPage->Layout();
	}

	int res = dlgSecurityProperty::Go(modal);

	if (defaultSecurityPage)
	{
		if (createDefPrivs && connection->BackendMinimumVersion(9, 0))
			defaultSecurityPage->UpdatePrivilegePages(createDefPrivs, defPrivsOnTables,
			        defPrivsOnSeqs, defPrivsOnFuncs, defPrivsOnTypes);
		else
			defaultSecurityPage->Enable(false);
	}

	return res;
}

wxString dlgDefaultSecurityProperty::GetHelpPage() const
{
	int nDiff      = nbNotebook->GetPageCount() - nbNotebook->GetSelection();

	switch (nDiff)
	{
		case 3:
			return wxT("pg/sql-grant");
		case 2:
			return wxT("pg/sql-alterdefaultprivileges");
		default:
			return dlgProperty::GetHelpPage();
	}
}

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


BEGIN_EVENT_TABLE(dlgAgentProperty, dlgProperty)
	EVT_BUTTON (wxID_OK,                            dlgAgentProperty::OnOK)
END_EVENT_TABLE();

dlgAgentProperty::dlgAgentProperty(pgaFactory *f, frmMain *frame, const wxString &resName)
	: dlgProperty(f, frame, resName)
{
	recId = 0;
}


wxString dlgAgentProperty::GetSql()
{
	wxString str = GetInsertSql();
	if (!str.IsEmpty())
		str += wxT("\n\n");
	return str + GetUpdateSql();
}



bool dlgAgentProperty::executeSql()
{
	wxString sql;
	bool dataChanged = false;

	sql = GetInsertSql();
	if (!sql.IsEmpty())
	{
		int pos;
		long jobId = 0, schId = 0, stpId = 0;
		if (sql.Contains(wxT("<JobId>")))
		{
			recId = jobId = StrToLong(connection->ExecuteScalar(wxT("SELECT nextval('pgagent.pga_job_jobid_seq');")));
			while ((pos = sql.Find(wxT("<JobId>"))) >= 0)
				sql = sql.Left(pos) + NumToStr(jobId) + sql.Mid(pos + 7);
		}

		if (sql.Contains(wxT("<SchId>")))
		{
			// Each schedule ID should be unique. This'll need work if a schedule hits more than
			// one table or anything.
			recId = schId = StrToLong(connection->ExecuteScalar(wxT("SELECT nextval('pgagent.pga_schedule_jscid_seq');")));
			while ((pos = sql.Find(wxT("<SchId>"))) >= 0)
			{
				sql = sql.Left(pos) + NumToStr(schId) + sql.Mid(pos + 7);
				recId = schId = StrToLong(connection->ExecuteScalar(wxT("SELECT nextval('pgagent.pga_schedule_jscid_seq');")));
			}
		}

		if (sql.Contains(wxT("<StpId>")))
		{
			// Each step ID should be unique. This'll need work if a step hits more than
			// one table or anything.
			recId = stpId = StrToLong(connection->ExecuteScalar(wxT("SELECT nextval('pgagent.pga_jobstep_jstid_seq');")));
			while ((pos = sql.Find(wxT("<StpId>"))) >= 0)
			{
				sql = sql.Left(pos) + NumToStr(stpId) + sql.Mid(pos + 7);
				recId = stpId = StrToLong(connection->ExecuteScalar(wxT("SELECT nextval('pgagent.pga_jobstep_jstid_seq');")));
			}
		}

		pgSet *set = connection->ExecuteSet(sql);
		if (set)
		{
			delete set;
		}
		if (!set)
		{
			return false;
		}
		dataChanged = true;
	}

	sql = GetUpdateSql();
	if (!sql.IsEmpty())
	{
		int pos;
		while ((pos = sql.Find(wxT("<JobId>"))) >= 0)
			sql = sql.Left(pos) + NumToStr(recId) + sql.Mid(pos + 7);

		long newId;
		if (sql.Contains(wxT("<SchId>")))
		{
			// Each schedule ID should be unique. This'll need work if a schedule hits more than
			// one table or anything.
			newId = StrToLong(connection->ExecuteScalar(wxT("SELECT nextval('pgagent.pga_schedule_jscid_seq');")));
			while ((pos = sql.Find(wxT("<SchId>"))) >= 0)
			{
				sql = sql.Left(pos) + NumToStr(newId) + sql.Mid(pos + 7);
				newId = StrToLong(connection->ExecuteScalar(wxT("SELECT nextval('pgagent.pga_schedule_jscid_seq');")));
			}
		}

		if (sql.Contains(wxT("<StpId>")))
		{
			// Each step ID should be unique. This'll need work if a step hits more than
			// one table or anything.
			newId = StrToLong(connection->ExecuteScalar(wxT("SELECT nextval('pgagent.pga_jobstep_jstid_seq');")));
			while ((pos = sql.Find(wxT("<StpId>"))) >= 0)
			{
				sql = sql.Left(pos) + NumToStr(newId) + sql.Mid(pos + 7);
				newId = StrToLong(connection->ExecuteScalar(wxT("SELECT nextval('pgagent.pga_jobstep_jstid_seq');")));
			}
		}

		if (!connection->ExecuteVoid(sql))
		{
			// error message is displayed inside ExecuteVoid
			return false;
		}
		dataChanged = true;
	}

	return dataChanged;
}


void dlgAgentProperty::OnOK(wxCommandEvent &ev)
{
#ifdef __WXGTK__
	if (!btnOK->IsEnabled())
		return;
#endif
	if (!IsUpToDate())
	{
		if (wxMessageBox(wxT("The object has been changed by another user. Do you wish to continue to try to update it?"), wxT("Overwrite changes?"), wxYES_NO) != wxYES)
			return;
	}

	if (IsModal())
	{
		EndModal(0);
		return;
	}

	connection->ExecuteVoid(wxT("BEGIN TRANSACTION"));

	if (executeSql())
	{
		connection->ExecuteVoid(wxT("COMMIT TRANSACTION"));
		ShowObject();
	}
	else
	{
		connection->ExecuteVoid(wxT("ROLLBACK TRANSACTION"));
	}

	Destroy();
}


propertyFactory::propertyFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : contextActionFactory(list)
{
	if (mnu)
		mnu->Append(id, _("&Properties...\tCtrl-Alt-Enter"), _("Display/edit the properties of the selected object."));
	else
		context = false;
	if (toolbar)
		toolbar->AddTool(id, wxEmptyString, GetBundleSVG(properties_png_bmp, "properties.svg", wxSize(32, 32)), _("Display/edit the properties of the selected object."), wxITEM_NORMAL);
}


wxWindow *propertyFactory::StartDialog(frmMain *form, pgObject *obj)
{
	if (!dlgProperty::EditObjectDialog(form, form->GetSqlPane(), obj))
		form->CheckAlive();

	return 0;
}


bool propertyFactory::CheckEnable(pgObject *obj)
{
	return obj && ((obj->GetMetaType() == PGM_DATABASE) ? (obj->GetConnection() != NULL) : true) && obj->CanEdit();
}


#include "images/create.pngc"
createFactory::createFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : actionFactory(list)
{
	mnu->Append(id, _("&Create..."),  _("Create a new object of the same type as the selected object."));
	toolbar->AddTool(id, wxEmptyString, GetBundleSVG(create_png_bmp, "create.svg", wxSize(32, 32)), _("Create a new object of the same type as the selected object."), wxITEM_NORMAL);
}


wxWindow *createFactory::StartDialog(frmMain *form, pgObject *obj)
{
	if (!dlgProperty::CreateObjectDialog(form, obj, 0))
		form->CheckAlive();

	return 0;
}


bool createFactory::CheckEnable(pgObject *obj)
{
	return obj && obj->CanCreate();
}


#include "images/drop.pngc"
dropFactory::dropFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : contextActionFactory(list)
{
	mnu->Append(id, _("&Delete/Drop...\tDel"),  _("Delete/Drop the selected object."));
	toolbar->AddTool(id, wxEmptyString, GetBundleSVG(drop_png_bmp, "drop.svg", wxSize(32, 32)), _("Drop the currently selected object."), wxITEM_NORMAL);
}


wxWindow *dropFactory::StartDialog(frmMain *form, pgObject *obj)
{
	form->ExecDrop(false);
	return 0;
}


bool dropFactory::CheckEnable(pgObject *obj)
{
	return obj && obj->CanDrop();
}


dropCascadedFactory::dropCascadedFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : contextActionFactory(list)
{
	mnu->Append(id, _("Drop cascaded..."), _("Drop the selected object and all objects dependent on it."));
}


wxWindow *dropCascadedFactory::StartDialog(frmMain *form, pgObject *obj)
{
	form->ExecDrop(true);
	return 0;
}


bool dropCascadedFactory::CheckEnable(pgObject *obj)
{
	return obj && obj->CanDrop() && obj->CanDropCascaded();
}


#include "images/refresh.pngc"
refreshFactory::refreshFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : contextActionFactory(list)
{
	if (mnu)
		mnu->Append(id, _("Re&fresh\tF5"), _("Refresh the selected object."));
	else
		context = false;
	if (toolbar)
		toolbar->AddTool(id, wxEmptyString, GetBundleSVG(refresh_png_bmp,"refresh.svg",wxSize(32,32)), _("Refresh the selected object."), wxITEM_NORMAL);
}


wxWindow *refreshFactory::StartDialog(frmMain *form, pgObject *obj)
{
	if (form)
		obj = form->GetBrowser()->GetObject(form->GetBrowser()->GetSelection());

	if (obj)
		if (CheckEnable(obj))
			form->Refresh(obj);
	return 0;
}


bool refreshFactory::CheckEnable(pgObject *obj)
{
	// This isn't really clean... But we don't have a pgObject::CanRefresh() so far,
	// so it's Good Enough (tm) for now.
	return obj != 0 && !obj->IsCreatedBy(serverFactory.GetCollectionFactory());
}

