////////////////////////////////////////////////////////////////////////// // // pgAdmin III - PostgreSQL Tools // // Copyright (C) 2002 - 2016, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // // frmEditGrid.cpp - Edit Grid Box // ////////////////////////////////////////////////////////////////////////// // wxWindows headers #include #include // App headers #include "pgAdmin3.h" #include "utils/pgDefs.h" #include "frm/frmMain.h" #include "frm/menu.h" #include "db/pgQueryThread.h" #include #include #include "frm/frmAbout.h" #include "frm/frmEditGrid.h" #include "ctl/ctlMenuToolbar.h" #include "dlg/dlgEditGridOptions.h" #include "frm/frmHint.h" #include "schema/pgCatalogObject.h" #include "schema/pgTable.h" #include "schema/pgForeignTable.h" #include "schema/pgView.h" #include "schema/gpExtTable.h" #include "schema/pgPartition.h" // wxAUI #include // Icons #include "images/viewdata.pngc" #include "images/storedata.pngc" #include "images/readdata.pngc" #include "images/delete.pngc" #include "images/edit_undo.pngc" #include "images/sortfilter.pngc" #include "images/help.pngc" #include "images/clip_copy.pngc" #include "images/clip_paste.pngc" #define CTRLID_LIMITCOMBO 4226 BEGIN_EVENT_TABLE(frmEditGrid, pgFrame) EVT_ERASE_BACKGROUND( frmEditGrid::OnEraseBackground) EVT_SIZE( frmEditGrid::OnSize) EVT_MENU(MNU_REFRESH, frmEditGrid::OnRefresh) EVT_MENU(MNU_DELETE, frmEditGrid::OnDelete) EVT_MENU(MNU_SAVE, frmEditGrid::OnSave) EVT_MENU(MNU_INCLUDEFILTER, frmEditGrid::OnIncludeFilter) EVT_MENU(MNU_EXCLUDEFILTER, frmEditGrid::OnExcludeFilter) EVT_MENU(MNU_REMOVEFILTERS, frmEditGrid::OnRemoveFilters) EVT_MENU(MNU_ASCSORT, frmEditGrid::OnAscSort) EVT_MENU(MNU_DESCSORT, frmEditGrid::OnDescSort) EVT_MENU(MNU_REMOVESORT, frmEditGrid::OnRemoveSort) EVT_MENU(MNU_UNDO, frmEditGrid::OnUndo) EVT_MENU(MNU_OPTIONS, frmEditGrid::OnOptions) EVT_MENU(MNU_HELP, frmEditGrid::OnHelp) EVT_MENU(MNU_CONTENTS, frmEditGrid::OnContents) EVT_MENU(MNU_COPY, frmEditGrid::OnCopy) EVT_MENU(MNU_PASTE, frmEditGrid::OnPaste) EVT_MENU(MNU_LIMITBAR, frmEditGrid::OnToggleLimitBar) EVT_MENU(MNU_TOOLBAR, frmEditGrid::OnToggleToolBar) EVT_MENU(MNU_SCRATCHPAD, frmEditGrid::OnToggleScratchPad) EVT_MENU(MNU_DEFAULTVIEW, frmEditGrid::OnDefaultView) EVT_MENU(MNU_CLOSE, frmEditGrid::OnClose) EVT_CLOSE( frmEditGrid::OnCloseWindow) EVT_KEY_DOWN( frmEditGrid::OnKey) EVT_GRID_RANGE_SELECT( frmEditGrid::OnGridSelectCells) EVT_GRID_SELECT_CELL( frmEditGrid::OnCellChange) EVT_GRID_EDITOR_SHOWN( frmEditGrid::OnEditorShown) EVT_GRID_EDITOR_HIDDEN( frmEditGrid::OnEditorHidden) EVT_GRID_CELL_RIGHT_CLICK( frmEditGrid::OnCellRightClick) EVT_GRID_LABEL_RIGHT_CLICK( frmEditGrid::OnLabelRightClick) EVT_AUI_PANE_BUTTON( frmEditGrid::OnAuiUpdate) END_EVENT_TABLE() frmEditGrid::frmEditGrid(frmMain *form, const wxString &_title, pgConn *_conn, pgSchemaObject *obj, bool pkAscending) : pgFrame(NULL, _title) { closing = false; SetIcon(*viewdata_png_ico); SetFont(settings->GetSystemFont()); dlgName = wxT("frmEditGrid"); RestorePosition(-1, -1, 600, 500, 300, 350); connection = _conn; mainForm = form; thread = 0; relkind = 0; limit = 0; relid = (Oid)obj->GetOid(); editorCell = new sqlCell(); // notify wxAUI which frame to use manager.SetManagedWindow(this); manager.SetFlags(wxAUI_MGR_DEFAULT | wxAUI_MGR_TRANSPARENT_DRAG); SetMinSize(wxSize(300, 350)); CreateStatusBar(); SetStatusBarPane(-1); sqlGrid = new ctlSQLEditGrid(this, CTL_EDITGRID, wxDefaultPosition, wxDefaultSize); sqlGrid->SetTable(0); #ifdef __WXMSW__ sqlGrid->SetDefaultRowSize(sqlGrid->GetDefaultRowSize() + 2, true); #endif // Set up toolbar toolBar = new ctlMenuToolbar(this, -1, wxDefaultPosition, wxDefaultSize, wxTB_FLAT | wxTB_NODIVIDER); toolBar->SetToolBitmapSize(wxSize(16, 16)); toolBar->AddTool(MNU_SAVE, wxEmptyString, *storedata_png_bmp, _("Save the changed row."), wxITEM_NORMAL); toolBar->AddSeparator(); toolBar->AddTool(MNU_REFRESH, wxEmptyString, *readdata_png_bmp, _("Refresh."), wxITEM_NORMAL); toolBar->AddTool(MNU_UNDO, wxEmptyString, *edit_undo_png_bmp, _("Undo change of data."), wxITEM_NORMAL); toolBar->AddSeparator(); toolBar->AddTool(MNU_COPY, wxEmptyString, *clip_copy_png_bmp, _("Copy selected lines to clipboard."), wxITEM_NORMAL); toolBar->AddSeparator(); toolBar->AddTool(MNU_PASTE, wxEmptyString, *clip_paste_png_bmp, _("Paste data from the clipboard."), wxITEM_NORMAL); toolBar->AddSeparator(); toolBar->AddTool(MNU_DELETE, wxEmptyString, *delete_png_bmp, _("Delete selected rows."), wxITEM_NORMAL); toolBar->AddSeparator(); toolBar->AddTool(MNU_OPTIONS, wxEmptyString, *sortfilter_png_bmp, _("Sort/filter options."), wxITEM_NORMAL); toolBar->AddSeparator(); toolBar->AddTool(MNU_HELP, wxEmptyString, *help_png_bmp, _("Display help on this window.")); toolBar->Realize(); toolBar->EnableTool(MNU_SAVE, false); toolBar->EnableTool(MNU_UNDO, false); toolBar->EnableTool(MNU_DELETE, false); // Setup the limit bar #ifndef __WXMAC__ cbLimit = new wxComboBox(this, CTRLID_LIMITCOMBO, wxEmptyString, wxPoint(0, 0), wxSize(GetCharWidth() * 12, -1), wxArrayString(), wxCB_DROPDOWN); #else cbLimit = new wxComboBox(this, CTRLID_LIMITCOMBO, wxEmptyString, wxPoint(0, 0), wxSize(GetCharWidth() * 24, -1), wxArrayString(), wxCB_DROPDOWN); #endif cbLimit->Append(_("No limit")); cbLimit->Append(_("1000 rows")); cbLimit->Append(_("500 rows")); cbLimit->Append(_("100 rows")); cbLimit->SetValue(_("No limit")); // Finally, the scratchpad scratchPad = new wxTextCtrl(this, -1, wxT(""), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxHSCROLL); // Menus // File menu fileMenu = new wxMenu(); fileMenu->Append(MNU_SAVE, _("&Save\tCtrl-S"), _("Save the changed row.")); fileMenu->AppendSeparator(); fileMenu->Append(MNU_CLOSE, _("&Close\tCtrl-W"), _("Close this window.")); fileMenu->Enable(MNU_SAVE, false); // Edit menu editMenu = new wxMenu(); editMenu->Append(MNU_UNDO, _("&Undo\tCtrl-Z"), _("Undo change of data.")); editMenu->AppendSeparator(); editMenu->Append(MNU_COPY, _("&Copy\tCtrl-C"), _("Copy selected cells to clipboard.")); editMenu->Append(MNU_PASTE, _("&Paste\tCtrl-V"), _("Paste data from the clipboard.")); editMenu->Append(MNU_DELETE, _("&Delete"), _("Delete selected rows.")); editMenu->Enable(MNU_UNDO, false); editMenu->Enable(MNU_DELETE, false); // View menu viewMenu = new wxMenu(); viewMenu->Append(MNU_REFRESH, _("&Refresh\tF5"), _("Refresh.")); viewMenu->AppendSeparator(); viewMenu->Append(MNU_LIMITBAR, _("&Limit bar\tCtrl-Alt-L"), _("Show or hide the row limit options bar."), wxITEM_CHECK); viewMenu->Append(MNU_SCRATCHPAD, _("S&cratch pad\tCtrl-Alt-S"), _("Show or hide the scratch pad."), 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.")); // Tools menu toolsMenu = new wxMenu(); toolsMenu->Append(MNU_OPTIONS, _("&Sort / Filter ..."), _("Sort / Filter options.")); toolsMenu->AppendSeparator(); toolsMenu->Append(MNU_INCLUDEFILTER, _("Filter By &Selection"), _("Display only those rows that have this value in this column.")); toolsMenu->Append(MNU_EXCLUDEFILTER, _("Filter E&xcluding Selection"), _("Display only those rows that do not have this value in this column.")); toolsMenu->Append(MNU_REMOVEFILTERS, _("&Remove Filter"), _("Remove all filters on this table")); toolsMenu->AppendSeparator(); toolsMenu->Append(MNU_ASCSORT, _("Sort &Ascending"), _("Append an ASCENDING sort condition based on this column")); toolsMenu->Append(MNU_DESCSORT, _("Sort &Descending"), _("Append a DESCENDING sort condition based on this column")); toolsMenu->Append(MNU_REMOVESORT, _("&Remove Sort"), _("Remove all sort conditions")); // Help menu helpMenu = new wxMenu(); helpMenu->Append(MNU_CONTENTS, _("&Help contents"), _("Open the helpfile.")); helpMenu->Append(MNU_HELP, _("&Edit grid help"), _("Display help on this window.")); #ifdef __WXMAC__ menuFactories = new menuFactoryList(); aboutFactory *af = new aboutFactory(menuFactories, helpMenu, 0); wxApp::s_macAboutMenuItemId = af->GetId(); menuFactories->RegisterMenu(this, wxCommandEventHandler(pgFrame::OnAction)); #endif menuBar = new wxMenuBar(); menuBar->Append(fileMenu, _("&File")); menuBar->Append(editMenu, _("&Edit")); menuBar->Append(viewMenu, _("&View")); menuBar->Append(toolsMenu, _("&Tools")); menuBar->Append(helpMenu, _("&Help")); SetMenuBar(menuBar); // Accelerators wxAcceleratorEntry entries[8]; entries[0].Set(wxACCEL_CTRL, (int)'S', MNU_SAVE); entries[1].Set(wxACCEL_NORMAL, WXK_F5, MNU_REFRESH); entries[2].Set(wxACCEL_CTRL, (int)'Z', MNU_UNDO); entries[3].Set(wxACCEL_NORMAL, WXK_F1, MNU_HELP); entries[4].Set(wxACCEL_CTRL, (int)'C', MNU_COPY); entries[5].Set(wxACCEL_CTRL, (int)'V', MNU_PASTE); entries[6].Set(wxACCEL_NORMAL, WXK_DELETE, MNU_DELETE); entries[7].Set(wxACCEL_CTRL, (int)'W', MNU_CLOSE); wxAcceleratorTable accel(8, entries); SetAcceleratorTable(accel); sqlGrid->SetAcceleratorTable(accel); // Kickstart wxAUI manager.AddPane(toolBar, wxAuiPaneInfo().Name(wxT("toolBar")).Caption(_("Tool bar")).ToolbarPane().Top().LeftDockable(false).RightDockable(false)); manager.AddPane(cbLimit, wxAuiPaneInfo().Name(wxT("limitBar")).Caption(_("Limit bar")).ToolbarPane().Top().LeftDockable(false).RightDockable(false)); manager.AddPane(sqlGrid, wxAuiPaneInfo().Name(wxT("sqlGrid")).Caption(_("Data grid")).Center().CaptionVisible(false).CloseButton(false).MinSize(wxSize(200, 100)).BestSize(wxSize(300, 200))); manager.AddPane(scratchPad, wxAuiPaneInfo().Name(wxT("scratchPad")).Caption(_("Scratch pad")).Bottom().MinSize(wxSize(200, 100)).BestSize(wxSize(300, 150))); // Now load the layout wxString perspective; settings->Read(wxT("frmEditGrid/Perspective-") + wxString(FRMEDITGRID_PERSPECTIVE_VER), &perspective, FRMEDITGRID_DEFAULT_PERSPECTIVE); manager.LoadPerspective(perspective, true); // and reset the captions for the current language manager.GetPane(wxT("toolBar")).Caption(_("Tool bar")); manager.GetPane(wxT("limitBar")).Caption(_("Limit bar")); manager.GetPane(wxT("sqlGrid")).Caption(_("Data grid")); manager.GetPane(wxT("scratchPad")).Caption(_("Scratch pad")); // Sync the View menu options viewMenu->Check(MNU_LIMITBAR, manager.GetPane(wxT("limitBar")).IsShown()); viewMenu->Check(MNU_TOOLBAR, manager.GetPane(wxT("toolBar")).IsShown()); viewMenu->Check(MNU_SCRATCHPAD, manager.GetPane(wxT("scratchPad")).IsShown()); // tell the manager to "commit" all the changes just made manager.Update(); autoOrderBy = false; if (obj->GetMetaType() == PGM_TABLE || obj->GetMetaType() == GP_PARTITION) { pgTable *table = (pgTable *)obj; relkind = 'r'; hasOids = table->GetHasOids(); tableName = table->GetSchema()->GetQuotedFullIdentifier() + wxT(".") + table->GetQuotedIdentifier(); primaryKeyColNumbers = table->GetPrimaryKeyColNumbers(); autoOrderBy = true; // default order by PK/OID will be discarded when a user defines his order orderBy = table->GetQuotedPrimaryKey(); if (orderBy.IsEmpty() && hasOids) orderBy = wxT("oid"); if (!orderBy.IsEmpty()) { if (pkAscending) { orderBy.Replace(wxT(","), wxT(" ASC,")); orderBy += wxT(" ASC"); } else { orderBy.Replace(wxT(","), wxT(" DESC,")); orderBy += wxT(" DESC"); } } } else if (obj->GetMetaType() == PGM_VIEW) { pgView *view = (pgView *)obj; relkind = 'v'; hasOids = false; tableName = view->GetSchema()->GetQuotedFullIdentifier() + wxT(".") + view->GetQuotedIdentifier(); } else if (obj->GetMetaType() == PGM_FOREIGNTABLE) { pgForeignTable *foreigntable = (pgForeignTable *)obj; relkind = 'f'; hasOids = false; tableName = foreigntable->GetSchema()->GetQuotedFullIdentifier() + wxT(".") + foreigntable->GetQuotedIdentifier(); } else if (obj->GetMetaType() == GP_EXTTABLE) { gpExtTable *exttable = (gpExtTable *)obj; relkind = 'x'; hasOids = false; tableName = exttable->GetSchema()->GetQuotedFullIdentifier() + wxT(".") + exttable->GetQuotedIdentifier(); } else if (obj->GetMetaType() == PGM_CATALOGOBJECT) { pgCatalogObject *catobj = (pgCatalogObject *)obj; relkind = 'v'; hasOids = false; tableName = catobj->GetSchema()->GetQuotedFullIdentifier() + wxT(".") + catobj->GetQuotedIdentifier(); } } void frmEditGrid::OnEraseBackground(wxEraseEvent &event) { event.Skip(); } void frmEditGrid::OnSize(wxSizeEvent &event) { event.Skip(); } void frmEditGrid::OnToggleLimitBar(wxCommandEvent &event) { if (viewMenu->IsChecked(MNU_LIMITBAR)) manager.GetPane(wxT("limitBar")).Show(true); else manager.GetPane(wxT("limitBar")).Show(false); manager.Update(); } void frmEditGrid::OnToggleToolBar(wxCommandEvent &event) { if (viewMenu->IsChecked(MNU_TOOLBAR)) manager.GetPane(wxT("toolBar")).Show(true); else manager.GetPane(wxT("toolBar")).Show(false); manager.Update(); } void frmEditGrid::OnToggleScratchPad(wxCommandEvent &event) { if (viewMenu->IsChecked(MNU_SCRATCHPAD)) manager.GetPane(wxT("scratchPad")).Show(true); else manager.GetPane(wxT("scratchPad")).Show(false); manager.Update(); } void frmEditGrid::OnAuiUpdate(wxAuiManagerEvent &event) { if(event.pane->name == wxT("limitBar")) { viewMenu->Check(MNU_LIMITBAR, false); } else if(event.pane->name == wxT("toolBar")) { viewMenu->Check(MNU_TOOLBAR, false); } else if(event.pane->name == wxT("scratchPad")) { viewMenu->Check(MNU_SCRATCHPAD, false); } event.Skip(); } void frmEditGrid::OnDefaultView(wxCommandEvent &event) { manager.LoadPerspective(FRMEDITGRID_DEFAULT_PERSPECTIVE, true); // Reset the captions for the current language manager.GetPane(wxT("toolBar")).Caption(_("Tool bar")); manager.GetPane(wxT("limitBar")).Caption(_("Limit bar")); manager.GetPane(wxT("sqlGrid")).Caption(_("Data grid")); manager.GetPane(wxT("scratchPad")).Caption(_("Scratch pad")); // tell the manager to "commit" all the changes just made manager.Update(); // Sync the View menu options viewMenu->Check(MNU_LIMITBAR, manager.GetPane(wxT("limitBar")).IsShown()); viewMenu->Check(MNU_TOOLBAR, manager.GetPane(wxT("toolBar")).IsShown()); viewMenu->Check(MNU_SCRATCHPAD, manager.GetPane(wxT("scratchPad")).IsShown()); } void frmEditGrid::SetSortCols(const wxString &cols) { if (orderBy != cols) { orderBy = cols; } } void frmEditGrid::SetFilter(const wxString &filter) { if (rowFilter != filter) { rowFilter = filter; } } void frmEditGrid::SetLimit(const int rowlimit) { if (rowlimit != limit) { limit = rowlimit; if (limit <= 0) cbLimit->SetValue(_("No limit")); else cbLimit->SetValue(wxString::Format(wxPLURAL("%i row", "%i rows", limit), limit)); } } void frmEditGrid::OnLabelRightClick(wxGridEvent &event) { wxMenu *xmenu = new wxMenu(); wxArrayInt rows = sqlGrid->GetSelectedRows(); xmenu->Append(MNU_COPY, _("&Copy"), _("Copy selected cells to clipboard.")); xmenu->Append(MNU_PASTE, _("&Paste"), _("Paste data from the clipboard.")); xmenu->Append(MNU_DELETE, _("&Delete"), _("Delete selected rows.")); if ((rows.GetCount()) && (!sqlGrid->IsCurrentCellReadOnly())) { xmenu->Enable(MNU_COPY, true); xmenu->Enable(MNU_DELETE, true); xmenu->Enable(MNU_PASTE, true); } else { xmenu->Enable(MNU_COPY, false); xmenu->Enable(MNU_DELETE, false); xmenu->Enable(MNU_PASTE, false); } sqlGrid->PopupMenu(xmenu); } void frmEditGrid::OnCellRightClick(wxGridEvent &event) { wxMenu *xmenu = new wxMenu(); // If we cannot refresh, assume there is a data thread running. We cannot // check thread->IsRunning() as it can crash if the thread is in some // states :-( if (!toolBar->GetToolEnabled(MNU_REFRESH)) return; sqlGrid->SetGridCursor(event.GetRow(), event.GetCol()); xmenu->Append(MNU_INCLUDEFILTER, _("Filter By &Selection"), _("Display only those rows that have this value in this column.")); xmenu->Append(MNU_EXCLUDEFILTER, _("Filter E&xcluding Selection"), _("Display only those rows that do not have this value in this column.")); xmenu->Append(MNU_REMOVEFILTERS, _("&Remove Filter"), _("Remove all filters on this table")); xmenu->InsertSeparator(3); xmenu->Append(MNU_ASCSORT, _("Sort &Ascending"), _("Append an ASCENDING sort condition based on this column")); xmenu->Append(MNU_DESCSORT, _("Sort &Descending"), _("Append a DESCENDING sort condition based on this column")); xmenu->Append(MNU_REMOVESORT, _("&Remove Sort"), _("Remove all sort conditions")); xmenu->Enable(MNU_INCLUDEFILTER, true); xmenu->Enable(MNU_EXCLUDEFILTER, true); xmenu->Enable(MNU_REMOVEFILTERS, true); xmenu->Enable(MNU_ASCSORT, true); xmenu->Enable(MNU_DESCSORT, true); xmenu->Enable(MNU_REMOVESORT, true); sqlGrid->PopupMenu(xmenu); } void frmEditGrid::OnCellChange(wxGridEvent &event) { sqlTable *table = sqlGrid->GetTable(); bool doSkip = true; if (table) { if (table->LastRow() >= 0) { if (table->LastRow() != event.GetRow()) { doSkip = DoSave(); } } else if (sqlGrid->GetGridCursorRow() != event.GetRow()) { toolBar->EnableTool(MNU_SAVE, false); toolBar->EnableTool(MNU_UNDO, false); fileMenu->Enable(MNU_SAVE, false); editMenu->Enable(MNU_UNDO, false); } } if (doSkip) event.Skip(); } void frmEditGrid::OnIncludeFilter(wxCommandEvent &event) { int curcol = sqlGrid->GetGridCursorCol(); int currow = sqlGrid->GetGridCursorRow(); if (curcol == -1 || currow == -1) return; sqlTable *table = sqlGrid->GetTable(); wxString column_label = qtIdent(table->GetColLabelValueUnformatted(curcol)); wxString new_filter_string; size_t old_filter_string_length = GetFilter().Trim().Len(); if (old_filter_string_length > 0) { new_filter_string = GetFilter().Trim() + wxT(" \n AND "); } if (table->IsColText(curcol)) { if (sqlGrid->GetCellValue(currow, curcol).IsNull()) { new_filter_string += column_label + wxT(" IS NULL "); } else { if (sqlGrid->GetCellValue(currow, curcol) == wxT("\'\'")) { new_filter_string += column_label + wxT(" = ''"); } else { new_filter_string += column_label + wxT(" = ") + connection->qtDbString(sqlGrid->GetCellValue(currow, curcol)) + wxT(" "); } } } else { if (sqlGrid->GetCellValue(currow, curcol).IsNull()) { new_filter_string += column_label + wxT(" IS NULL "); } else { new_filter_string += column_label + wxT(" = ") + sqlGrid->GetCellValue(currow, curcol); } } SetFilter(new_filter_string); Go(); } void frmEditGrid::OnExcludeFilter(wxCommandEvent &event) { int curcol = sqlGrid->GetGridCursorCol(); int currow = sqlGrid->GetGridCursorRow(); if (curcol == -1 || currow == -1) return; sqlTable *table = sqlGrid->GetTable(); wxString column_label = qtIdent(table->GetColLabelValueUnformatted(curcol)); wxString new_filter_string; size_t old_filter_string_length = GetFilter().Trim().Len(); if (old_filter_string_length > 0) { new_filter_string = GetFilter().Trim() + wxT(" \n AND "); } if (table->IsColText(curcol)) { if (sqlGrid->GetCellValue(currow, curcol).IsNull()) { new_filter_string += column_label + wxT(" IS NOT NULL "); } else { if (sqlGrid->GetCellValue(currow, curcol) == wxT("\'\'")) { new_filter_string += column_label + wxString::Format(wxT(" IS DISTINCT FROM '' ")) ; } else { new_filter_string += column_label + wxT(" IS DISTINCT FROM ") + connection->qtDbString(sqlGrid->GetCellValue(currow, curcol)) + wxT(" "); } } } else { if (sqlGrid->GetCellValue(currow, curcol).IsNull()) { new_filter_string += column_label + wxT(" IS NOT NULL ") ; } else { new_filter_string += column_label + wxT(" IS DISTINCT FROM ") + sqlGrid->GetCellValue(currow, curcol); } } SetFilter(new_filter_string); Go(); } void frmEditGrid::OnRemoveFilters(wxCommandEvent &event) { SetFilter(wxT("")); Go(); } void frmEditGrid::OnAscSort(wxCommandEvent &ev) { int curcol = sqlGrid->GetGridCursorCol(); if (curcol == -1) return; sqlTable *table = sqlGrid->GetTable(); wxString column_label = qtIdent(table->GetColLabelValueUnformatted(curcol)); wxString old_sort_string, new_sort_string; if (autoOrderBy) { autoOrderBy = false; old_sort_string = wxT(""); } else old_sort_string = GetSortCols().Trim(); if (old_sort_string.Find(column_label) == wxNOT_FOUND) { if (old_sort_string.Len() > 0) new_sort_string = old_sort_string + wxT(" , "); new_sort_string += column_label + wxT(" ASC "); } else { if (old_sort_string.Find(column_label + wxT(" ASC")) == wxNOT_FOUND) { // Previous occurrence was for DESCENDING sort new_sort_string = old_sort_string; new_sort_string.Replace(column_label + wxT(" DESC"), column_label + wxT(" ASC")); } else { // Previous occurrence was for ASCENDING sort. Nothing to do new_sort_string = old_sort_string; } } SetSortCols(new_sort_string); Go(); } void frmEditGrid::OnDescSort(wxCommandEvent &ev) { int curcol = sqlGrid->GetGridCursorCol(); if (curcol == -1) return; sqlTable *table = sqlGrid->GetTable(); wxString column_label = qtIdent(table->GetColLabelValueUnformatted(curcol)); wxString old_sort_string, new_sort_string; if (autoOrderBy) { autoOrderBy = false; old_sort_string = wxT(""); } else old_sort_string = GetSortCols().Trim(); if (old_sort_string.Find(column_label) == wxNOT_FOUND) { if (old_sort_string.Len() > 0) new_sort_string = old_sort_string + wxT(" , "); new_sort_string += column_label + wxT(" DESC "); } else { if (old_sort_string.Find(column_label + wxT(" DESC")) == wxNOT_FOUND) { // Previous occurrence was for ASCENDING sort new_sort_string = old_sort_string; new_sort_string.Replace(column_label + wxT(" ASC"), column_label + wxT(" DESC")); } else { // Previous occurrence was for DESCENDING sort. Nothing to do new_sort_string = old_sort_string; } } SetSortCols(new_sort_string); Go(); } void frmEditGrid::OnRemoveSort(wxCommandEvent &ev) { SetSortCols(wxT("")); Go(); } void frmEditGrid::OnCopy(wxCommandEvent &ev) { wxWindow *wnd = FindFocus(); if (wnd == scratchPad) { scratchPad->Copy(); } else { if (editorCell->IsSet()) { if (wxTheClipboard->Open()) { if (sqlGrid->GetTable()->IsColText(sqlGrid->GetGridCursorCol())) { wxStyledTextCtrl *text = (wxStyledTextCtrl *)sqlGrid->GetCellEditor(sqlGrid->GetGridCursorRow(), sqlGrid->GetGridCursorCol())->GetControl(); if (text && !text->GetSelectedText().IsEmpty()) { wxTheClipboard->SetData(new wxTextDataObject(text->GetSelectedText())); SetStatusText(_("Data from one cell copied to clipboard.")); } } else { wxTextCtrl *text = (wxTextCtrl *)sqlGrid->GetCellEditor(sqlGrid->GetGridCursorRow(), sqlGrid->GetGridCursorCol())->GetControl(); if (text && !text->GetStringSelection().IsEmpty()) { wxTheClipboard->SetData(new wxTextDataObject(text->GetStringSelection())); SetStatusText(_("Data from one cell copied to clipboard.")); } } wxTheClipboard->Close(); } } else if(sqlGrid->GetNumberRows() > 0) { int copied; copied = sqlGrid->Copy(false); SetStatusText(wxString::Format( wxPLURAL("Data from %d row copied to clipboard.", "Data from %d rows copied to clipboard.", copied), copied)); } } } void frmEditGrid::OnPaste(wxCommandEvent &ev) { wxWindow *wnd = FindFocus(); if (wnd == scratchPad) { scratchPad->Paste(); } else if (editorCell->IsSet()) { if (wxTheClipboard->Open()) { if (wxTheClipboard->IsSupported(wxDF_TEXT)) { wxTextDataObject data; wxTheClipboard->GetData(data); wxControl *ed = sqlGrid->GetCellEditor(editorCell->GetRow(), editorCell->GetCol())->GetControl(); if (ed->IsKindOf(CLASSINFO(wxStyledTextCtrl))) { wxStyledTextCtrl *txtEd = (wxStyledTextCtrl *)ed; txtEd->ReplaceSelection(data.GetText()); } else if (ed->IsKindOf(CLASSINFO(wxCheckBox))) { wxCheckBox *boolEd = (wxCheckBox *)ed; if (data.GetText().Lower() == wxT("true")) boolEd->Set3StateValue(wxCHK_CHECKED); else if (data.GetText().Lower() == wxT("false")) boolEd->Set3StateValue(wxCHK_UNCHECKED); else boolEd->Set3StateValue(wxCHK_UNDETERMINED); } else if (ed->IsKindOf(CLASSINFO(wxTextCtrl))) { wxTextCtrl *txtEd = (wxTextCtrl *)ed; long x, y; txtEd->GetSelection(&x, &y); txtEd->Replace(x, y, data.GetText()); //txtEd->SetValue(data.GetText()); } } wxTheClipboard->Close(); } } else if(sqlGrid->GetNumberRows() > 0) { if (toolBar->GetToolEnabled(MNU_SAVE)) { wxMessageDialog msg(this, _("There is unsaved data in a row.\nDo you want to store to the database?"), _("Unsaved data"), wxYES_NO | wxICON_QUESTION | wxCANCEL); switch (msg.ShowModal()) { case wxID_YES: if (!DoSave()) return; break; case wxID_CANCEL: return; break; case wxID_NO: CancelChange(); break; } } if (sqlGrid->GetTable()->Paste()) { toolBar->EnableTool(MNU_SAVE, true); toolBar->EnableTool(MNU_UNDO, true); fileMenu->Enable(MNU_SAVE, true); editMenu->Enable(MNU_UNDO, true); } } } void frmEditGrid::OnHelp(wxCommandEvent &ev) { DisplayHelp(wxT("editgrid"), HELP_PGADMIN); } void frmEditGrid::OnContents(wxCommandEvent &ev) { DisplayHelp(wxT("index"), HELP_PGADMIN); } void frmEditGrid::OnKey(wxKeyEvent &event) { int curcol = sqlGrid->GetGridCursorCol(); int currow = sqlGrid->GetGridCursorRow(); if (curcol == -1 || currow == -1) return; int keycode = event.GetKeyCode(); wxCommandEvent ev; switch (keycode) { case WXK_DELETE: { if (editorCell->IsSet() || !toolBar->GetToolEnabled(MNU_DELETE)) { if (!sqlGrid->IsCurrentCellReadOnly()) { sqlGrid->EnableCellEditControl(); sqlGrid->ShowCellEditControl(); wxGridCellEditor *edit = sqlGrid->GetCellEditor(currow, curcol); if (edit) { wxControl *ctl = edit->GetControl(); if (ctl) { wxStyledTextCtrl *txt = wxDynamicCast(ctl, wxStyledTextCtrl); if (txt) txt->SetText(wxEmptyString); } edit->DecRef(); } } } else { OnDelete(ev); } return; } case WXK_RETURN: case WXK_NUMPAD_ENTER: // check for shift etc. if (event.ControlDown() || event.ShiftDown()) { // Inject a RETURN into the control wxGridCellEditor *edit = sqlGrid->GetCellEditor(currow, curcol); if (edit) { wxControl *ctl = edit->GetControl(); if (ctl) { wxStyledTextCtrl *txt = wxDynamicCast(ctl, wxStyledTextCtrl); if (txt) txt->ReplaceSelection(END_OF_LINE); } edit->DecRef(); } return; } else { if( keycode != WXK_NUMPAD_ENTER ) { // if we are at the end of the row if (curcol == sqlGrid->GetNumberCols() - 1) { // we first get to the first column of the next row curcol = 0; currow++; // * if the displayed object is a table, // we first need to make sure that the new selected // cell is read/write, otherwise we need to select // the next one // * if the displayed object is not a table (for // example, a view), all cells are readonly, so // we skip that part if (relkind == 'r') { // locate first editable column while (curcol < sqlGrid->GetNumberCols() && sqlGrid->IsReadOnly(currow, curcol)) curcol++; // next line is completely read-only if (curcol == sqlGrid->GetNumberCols()) return; } } else curcol++; } else // ( keycode==WXK_NUMPAD_ENTER ) { currow++; } OnSave(ev); sqlGrid->SetGridCursor(currow, curcol); return; } case WXK_TAB: if (event.ControlDown()) { wxStyledTextCtrl *text = (wxStyledTextCtrl *)sqlGrid->GetCellEditor(sqlGrid->GetGridCursorRow(), sqlGrid->GetGridCursorCol())->GetControl(); if (text) text->SetText(wxT("\t")); return; } break; case WXK_ESCAPE: CancelChange(); break; default: if (sqlGrid->IsEditable() && keycode >= WXK_SPACE && keycode < WXK_START) { if (sqlGrid->IsCurrentCellReadOnly()) return; toolBar->EnableTool(MNU_SAVE, true); toolBar->EnableTool(MNU_UNDO, true); fileMenu->Enable(MNU_SAVE, true); editMenu->Enable(MNU_UNDO, true); } break; } event.Skip(); } void frmEditGrid::OnClose(wxCommandEvent &event) { this->Close(); } void frmEditGrid::OnCloseWindow(wxCloseEvent &event) { // We need to call OnCellChange to check if some cells for the row have been changed wxGridEvent evt; OnCellChange(evt); // If MNU_SAVE item is still enabled, we need to ask about the unsaved data if (toolBar->GetToolEnabled(MNU_SAVE)) { int flag = wxYES_NO | wxICON_QUESTION; if (event.CanVeto()) flag |= wxCANCEL; wxMessageDialog msg(this, _("There is unsaved data in a row.\nDo you want to store to the database?"), _("Unsaved data"), flag); switch (msg.ShowModal()) { case wxID_YES: { if (!DoSave()) { event.Veto(); return; } break; } case wxID_CANCEL: event.Veto(); return; } } Abort(); Destroy(); } void frmEditGrid::OnUndo(wxCommandEvent &event) { sqlGrid->DisableCellEditControl(); sqlGrid->GetTable()->UndoLine(sqlGrid->GetGridCursorRow()); sqlGrid->ForceRefresh(); toolBar->EnableTool(MNU_SAVE, false); toolBar->EnableTool(MNU_UNDO, false); fileMenu->Enable(MNU_SAVE, false); editMenu->Enable(MNU_UNDO, false); } void frmEditGrid::OnRefresh(wxCommandEvent &event) { if (!toolBar->GetToolEnabled(MNU_REFRESH)) return; if (toolBar->GetToolEnabled(MNU_SAVE)) { wxMessageDialog msg(this, _("There is unsaved data in a row.\nDo you want to store to the database?"), _("Unsaved data"), wxYES_NO | wxICON_QUESTION | wxCANCEL); switch (msg.ShowModal()) { case wxID_YES: { if (!DoSave()) return; break; } case wxID_CANCEL: return; } } sqlGrid->DisableCellEditControl(); Go(); } void frmEditGrid::OnSave(wxCommandEvent &event) { if (sqlGrid->GetBatchCount() == 0) DoSave(); } bool frmEditGrid::DoSave() { sqlGrid->HideCellEditControl(); sqlGrid->SaveEditControlValue(); sqlGrid->DisableCellEditControl(); if (!sqlGrid->GetTable()->StoreLine()) return false; toolBar->EnableTool(MNU_SAVE, false); toolBar->EnableTool(MNU_UNDO, false); fileMenu->Enable(MNU_SAVE, false); editMenu->Enable(MNU_UNDO, false); return true; } void frmEditGrid::CancelChange() { sqlGrid->HideCellEditControl(); sqlGrid->SaveEditControlValue(); sqlGrid->DisableCellEditControl(); toolBar->EnableTool(MNU_SAVE, false); toolBar->EnableTool(MNU_UNDO, false); fileMenu->Enable(MNU_SAVE, false); editMenu->Enable(MNU_UNDO, false); sqlGrid->GetTable()->UndoLine(sqlGrid->GetGridCursorRow()); sqlGrid->ForceRefresh(); } void frmEditGrid::OnOptions(wxCommandEvent &event) { if (toolBar->GetToolEnabled(MNU_SAVE)) { wxMessageDialog msg(this, _("There is unsaved data in a row.\nDo you want to store to the database?"), _("Unsaved data"), wxYES_NO | wxICON_QUESTION | wxCANCEL); switch (msg.ShowModal()) { case wxID_YES: { if (!DoSave()) return; break; } case wxID_CANCEL: return; case wxID_NO: CancelChange(); } } dlgEditGridOptions winOptions(this, connection, tableName, sqlGrid); if (winOptions.ShowModal()) Go(); } template < class T > int ArrayCmp(T *a, T *b) { if (*a == *b) return 0; if (*a > *b) return 1; else return -1; } void frmEditGrid::OnDelete(wxCommandEvent &event) { // Don't bugger about with keypresses to the scratch pad. if (FindFocus() == scratchPad) { event.Skip(); return; } if (editorCell->IsSet()) { if (sqlGrid->GetTable()->IsColBoolean(sqlGrid->GetGridCursorCol())) return; if (sqlGrid->GetTable()->IsColText(sqlGrid->GetGridCursorCol())) { wxStyledTextCtrl *text = (wxStyledTextCtrl *)sqlGrid->GetCellEditor(sqlGrid->GetGridCursorRow(), sqlGrid->GetGridCursorCol())->GetControl(); if (text && text->GetCurrentPos() <= text->GetTextLength()) { if (text->GetSelectionStart() == text->GetSelectionEnd()) text->SetSelection(text->GetSelectionStart(), text->GetSelectionStart() + 1); text->Clear(); } } else { wxTextCtrl *text = (wxTextCtrl *)sqlGrid->GetCellEditor(sqlGrid->GetGridCursorRow(), sqlGrid->GetGridCursorCol())->GetControl(); if (text) { long x, y; text->GetSelection(&x, &y); if (x != y) text->Remove(x, x + y + 1); else text->Remove(x, x + 1); } } return; } // If the delete button is disabled, don't try to delete anything if (!toolBar->GetToolEnabled(MNU_DELETE)) return; wxArrayInt delrows = sqlGrid->GetSelectedRows(); int i = delrows.GetCount(); if (i == 0) return; wxString prompt; if (i == 1) prompt = _("Are you sure you wish to delete the selected row?"); else prompt.Printf(_("Are you sure you wish to delete the %d selected rows?"), i); wxMessageDialog msg(this, prompt, _("Delete rows?"), wxYES_NO | wxICON_QUESTION); if (msg.ShowModal() != wxID_YES) return; sqlGrid->BeginBatch(); // Sort the grid so we always delete last->first, otherwise we // could end up deleting anything because the array returned by // GetSelectedRows is in the order that rows were selected by // the user. delrows.Sort(ArrayCmp); // don't care a lot about optimizing here; doing it line by line // just as sqlTable::DeleteRows does bool show_continue_message = true; while (i--) { if (!sqlGrid->DeleteRows(delrows.Item(i), 1) && i > 0 && show_continue_message) { wxMessageDialog msg(this, wxString::Format(wxPLURAL( "There was an error deleting the previous record.\nAre you sure you wish to delete the remaining %d row?", "There was an error deleting the previous record.\nAre you sure you wish to delete the remaining %d rows?", i), i), _("Delete more records ?"), wxYES_NO | wxICON_QUESTION); if (msg.ShowModal() != wxID_YES) break; else show_continue_message = false; } } sqlGrid->EndBatch(); SetStatusText(wxString::Format(wxPLURAL("%d row.", "%d rows.", sqlGrid->GetTable()->GetNumberStoredRows()), sqlGrid->GetTable()->GetNumberStoredRows()), 0); } void frmEditGrid::OnEditorShown(wxGridEvent &event) { toolBar->EnableTool(MNU_SAVE, true); toolBar->EnableTool(MNU_UNDO, true); fileMenu->Enable(MNU_SAVE, true); editMenu->Enable(MNU_UNDO, true); editorCell->SetCell(event.GetRow(), event.GetCol()); event.Skip(); } void frmEditGrid::OnEditorHidden(wxGridEvent &event) { editorCell->ClearCell(); } void frmEditGrid::OnGridSelectCells(wxGridRangeSelectEvent &event) { if (sqlGrid->IsEditable()) { wxArrayInt rows = sqlGrid->GetSelectedRows(); bool enable = rows.GetCount() > 0; if (enable) { wxCommandEvent nullEvent; OnSave(event); // check if a readonly line is selected int row, col; size_t i; for (i = 0 ; i < rows.GetCount() ; i++) { row = rows.Item(i); bool lineEnabled = false; if (row == sqlGrid->GetNumberRows() - 1) { // the (*) line may not be deleted/copied enable = false; break; } for (col = 0 ; col < sqlGrid->GetNumberCols() ; col++) { if (!sqlGrid->IsReadOnly(row, col)) { lineEnabled = true; break; } } if (!lineEnabled) { enable = false; break; } } } toolBar->EnableTool(MNU_DELETE, enable); editMenu->Enable(MNU_DELETE, enable); } event.Skip(); } void frmEditGrid::ShowForm(bool filter) { bool abort = false; if (relkind == 'r' || relkind == 'v' || relkind == 'x' || relkind == 'f') { if (filter) { dlgEditGridOptions *winOptions = new dlgEditGridOptions(this, connection, tableName, sqlGrid); abort = !(winOptions->ShowModal()); } if (abort) { // Hack to ensure there's a table for ~wxGrid() to delete sqlGrid->CreateGrid(0, 0); sqlGrid->SetTable(0); Close(); Destroy(); } else { Show(true); Go(); } } else { wxLogError(__("No Table or view.")); // Hack to ensure there's a table for ~wxGrid() to delete sqlGrid->CreateGrid(0, 0); Close(); Destroy(); } } void frmEditGrid::Go() { long templong; if (cbLimit->GetValue() != wxT("") && cbLimit->GetValue() != _("No limit") && !cbLimit->GetValue().BeforeFirst(' ').ToLong(&templong)) { wxLogError(_("The row limit must be an integer number or 'No limit'")); return; } if (cbLimit->GetValue() == _("No limit")) SetLimit(0); else { cbLimit->GetValue().BeforeFirst(' ').ToLong(&templong); SetLimit(templong); } // Check we have access if (connection->ExecuteScalar(wxT("SELECT count(*) FROM ") + tableName + wxT(" WHERE false")) == wxT("")) return; SetStatusText(_("Refreshing data, please wait."), 0); toolBar->EnableTool(MNU_REFRESH, false); viewMenu->Enable(MNU_REFRESH, false); toolBar->EnableTool(MNU_OPTIONS, false); toolsMenu->Enable(MNU_OPTIONS, false); toolsMenu->Enable(MNU_INCLUDEFILTER, false); toolsMenu->Enable(MNU_EXCLUDEFILTER, false); toolsMenu->Enable(MNU_REMOVEFILTERS, false); toolsMenu->Enable(MNU_ASCSORT, false); toolsMenu->Enable(MNU_DESCSORT, false); toolsMenu->Enable(MNU_REMOVESORT, false); wxString qry = wxT("SELECT "); if (hasOids) qry += wxT("oid, "); qry += wxT("* FROM ") + tableName; if (!rowFilter.IsEmpty()) { qry += wxT(" WHERE ") + rowFilter; } if (!orderBy.IsEmpty()) { qry += wxT("\n ORDER BY ") + orderBy; } if (limit > 0) qry += wxT(" LIMIT ") + wxString::Format(wxT("%i"), limit); thread = new pgQueryThread(connection, qry); if (thread->Create() != wxTHREAD_NO_ERROR) { Abort(); toolBar->EnableTool(MNU_REFRESH, true); viewMenu->Enable(MNU_REFRESH, true); toolBar->EnableTool(MNU_OPTIONS, true); toolsMenu->Enable(MNU_OPTIONS, true); toolsMenu->Enable(MNU_INCLUDEFILTER, true); toolsMenu->Enable(MNU_EXCLUDEFILTER, true); toolsMenu->Enable(MNU_REMOVEFILTERS, true); toolsMenu->Enable(MNU_ASCSORT, true); toolsMenu->Enable(MNU_DESCSORT, true); toolsMenu->Enable(MNU_REMOVESORT, true); return; } thread->Run(); while (thread && thread->IsRunning()) { wxTheApp->Yield(true); wxMilliSleep(10); } // Brute force check to ensure the user didn't get bored and close the window if (closing) return; if (!thread) { toolBar->EnableTool(MNU_REFRESH, true); viewMenu->Enable(MNU_REFRESH, true); toolBar->EnableTool(MNU_OPTIONS, true); toolsMenu->Enable(MNU_OPTIONS, true); toolsMenu->Enable(MNU_INCLUDEFILTER, true); toolsMenu->Enable(MNU_EXCLUDEFILTER, true); toolsMenu->Enable(MNU_REMOVEFILTERS, true); toolsMenu->Enable(MNU_ASCSORT, true); toolsMenu->Enable(MNU_DESCSORT, true); toolsMenu->Enable(MNU_REMOVESORT, true); return; } if (!thread->DataValid()) { Abort(); toolBar->EnableTool(MNU_REFRESH, true); viewMenu->Enable(MNU_REFRESH, true); toolBar->EnableTool(MNU_OPTIONS, true); toolsMenu->Enable(MNU_OPTIONS, true); toolsMenu->Enable(MNU_INCLUDEFILTER, true); toolsMenu->Enable(MNU_EXCLUDEFILTER, true); toolsMenu->Enable(MNU_REMOVEFILTERS, true); toolsMenu->Enable(MNU_ASCSORT, true); toolsMenu->Enable(MNU_DESCSORT, true); toolsMenu->Enable(MNU_REMOVESORT, true); return; } SetStatusText(wxString::Format(wxPLURAL("%d row.", "%d rows.", (int)thread->DataSet()->NumRows()), (int)thread->DataSet()->NumRows()), 0); sqlGrid->BeginBatch(); // to force the grid to create scrollbars, we make sure the size so small that scrollbars are needed // later, we will resize the grid's parent to force the correct size (now including scrollbars, even if // they are suppressed initially. Win32 won't need this. // !!! This hack breaks columns auto-sizing ( GetClientSize().GetWidth() is used in ctlSQLGrid::AutoSizeColumns() ) // !!! Is it still required? //sqlGrid->SetSize(10, 10); sqlGrid->SetTable(new sqlTable(connection, thread, tableName, relid, hasOids, primaryKeyColNumbers, relkind), true); sqlGrid->AutoSizeColumns(false); sqlGrid->EndBatch(); toolBar->EnableTool(MNU_REFRESH, true); viewMenu->Enable(MNU_REFRESH, true); toolBar->EnableTool(MNU_OPTIONS, true); toolsMenu->Enable(MNU_OPTIONS, true); toolsMenu->Enable(MNU_INCLUDEFILTER, true); toolsMenu->Enable(MNU_EXCLUDEFILTER, true); toolsMenu->Enable(MNU_REMOVEFILTERS, true); toolsMenu->Enable(MNU_ASCSORT, true); toolsMenu->Enable(MNU_DESCSORT, true); toolsMenu->Enable(MNU_REMOVESORT, true); manager.Update(); if (!hasOids && primaryKeyColNumbers.IsEmpty() && relkind == 'r') frmHint::ShowHint(this, HINT_READONLY_NOPK, tableName); // Set the thread variable to zero so we don't try to // abort it if the user cancels now. thread = 0; } frmEditGrid::~frmEditGrid() { closing = true; mainForm->RemoveFrame(this); settings->Write(wxT("frmEditGrid/Perspective-") + wxString(FRMEDITGRID_PERSPECTIVE_VER), manager.SavePerspective()); manager.UnInit(); if (connection) delete connection; } void frmEditGrid::Abort() { if (sqlGrid->GetTable()) { sqlGrid->HideCellEditControl(); sqlGrid->SetTable(0); } if (thread) { SetStatusText(_("aborting."), 0); if (thread->IsRunning()) { thread->CancelExecution(); thread->Wait(); } delete thread; thread = 0; } } ctlSQLEditGrid::ctlSQLEditGrid(wxFrame *parent, wxWindowID id, const wxPoint &pos, const wxSize &size) : ctlSQLGrid(parent, id, pos, size) { } bool ctlSQLEditGrid::CheckRowPresent(int row) { return GetTable()->CheckInCache(row); } void ctlSQLEditGrid::ResizeEditor(int row, int col) { if (GetTable()->needsResizing(col)) { wxGridCellAttr *attr = GetCellAttr(row, col); wxGridCellRenderer *renderer = attr->GetRenderer(this, row, col); if ( renderer ) { wxClientDC dc(GetGridWindow()); wxSize size = renderer->GetBestSize(*this, *attr, dc, row, col); renderer->DecRef(); int w = wxMax(size.GetWidth(), 15) + 20; int h = wxMax(size.GetHeight(), 15) + 20; wxGridCellEditor *editor = attr->GetEditor(this, row, col); if (editor) { wxRect cellRect = CellToRect(m_currentCellCoords); wxRect rect = cellRect; rect.SetWidth(w); rect.SetHeight(h); // we might have scrolled CalcUnscrolledPosition(0, 0, &w, &h); rect.SetLeft(rect.GetLeft() - w); rect.SetTop(rect.GetTop() - h); // Clip rect to client size GetClientSize(&w, &h); rect.SetRight(wxMin(rect.GetRight(), w)); rect.SetBottom(wxMin(rect.GetBottom(), h)); // but not smaller than original cell rect.SetWidth(wxMax(cellRect.GetWidth(), rect.GetWidth())); rect.SetHeight(wxMax(cellRect.GetHeight(), rect.GetHeight())); editor->SetSize(rect); editor->DecRef(); } } attr->DecRef(); } } wxArrayInt ctlSQLEditGrid::GetSelectedRows() const { wxArrayInt rows, rows2; wxGridCellCoordsArray tl = GetSelectionBlockTopLeft(), br = GetSelectionBlockBottomRight(); int maxCol = ((ctlSQLEditGrid *)this)->GetNumberCols() - 1; size_t i; for (i = 0 ; i < tl.GetCount() ; i++) { wxGridCellCoords c1 = tl.Item(i), c2 = br.Item(i); if (c1.GetCol() != 0 || c2.GetCol() != maxCol) return rows2; int j; for (j = c1.GetRow() ; j <= c2.GetRow() ; j++) rows.Add(j); } rows2 = wxGrid::GetSelectedRows(); rows.Sort(ArrayCmp); rows2.Sort(ArrayCmp); size_t i2 = 0, cellRowMax = rows.GetCount(); for (i = 0 ; i < rows2.GetCount() ; i++) { int row = rows2.Item(i); while (i2 < cellRowMax && rows.Item(i2) < row) i2++; if (i2 == cellRowMax || row != rows.Item(i2)) rows.Add(row); } return rows; } class sqlGridTextEditor : public wxGridCellTextEditor { public: virtual wxGridCellEditor *Clone() const { return new sqlGridTextEditor(); } void Create(wxWindow *parent, wxWindowID id, wxEvtHandler *evtHandler); void BeginEdit(int row, int col, wxGrid *grid); #if wxCHECK_VERSION(2, 9, 0) void ApplyEdit(int row, int col, wxGrid *grid); bool EndEdit(int row, int col, const wxGrid *grid, const wxString &, wxString *); #else bool EndEdit(int row, int col, wxGrid *grid); #endif wxString GetValue() const; virtual void Reset() { DoReset(m_startValue); } void StartingKey(wxKeyEvent &event); protected: void DoBeginEdit(const wxString &startValue); wxStyledTextCtrl *Text() const { return (wxStyledTextCtrl *)m_control; } void DoReset(const wxString &startValue); wxString m_startValue; }; void sqlGridTextEditor::Create(wxWindow *parent, wxWindowID id, wxEvtHandler *evtHandler) { int flags = wxSTC_WRAP_NONE; m_control = new wxStyledTextCtrl(parent, id, wxDefaultPosition, wxDefaultSize, flags ); wxGridCellEditor::Create(parent, id, evtHandler); } void sqlGridTextEditor::BeginEdit(int row, int col, wxGrid *grid) { m_startValue = grid->GetTable()->GetValue(row, col); DoBeginEdit(m_startValue); ((ctlSQLEditGrid *)grid)->ResizeEditor(row, col); } void sqlGridTextEditor::DoBeginEdit(const wxString &startValue) { Text()->SetMarginWidth(1, 0); Text()->SetText(startValue); Text()->SetCurrentPos(Text()->GetTextLength()); Text()->SetUseHorizontalScrollBar(false); Text()->SetUseVerticalScrollBar(false); Text()->SetSelection(0, -1); Text()->SetFocus(); } #if wxCHECK_VERSION(2, 9, 0) void sqlGridTextEditor::ApplyEdit(int row, int col, wxGrid *grid) { wxString value = Text()->GetText(); grid->GetTable()->SetValue(row, col, value); } #endif #if wxCHECK_VERSION(2, 9, 0) bool sqlGridTextEditor::EndEdit(int row, int col, const wxGrid *grid, const wxString &, wxString *) #else bool sqlGridTextEditor::EndEdit(int row, int col, wxGrid *grid) #endif { bool changed = false; wxString value = Text()->GetText(); if (value != m_startValue) changed = true; #if !wxCHECK_VERSION(2, 9, 0) if (changed) grid->GetTable()->SetValue(row, col, value); #endif return changed; } wxString sqlGridTextEditor::GetValue() const { return Text()->GetText(); } void sqlGridTextEditor::DoReset(const wxString &startValue) { Text()->SetText(startValue); Text()->SetSelection(-1, -1); } void sqlGridTextEditor::StartingKey(wxKeyEvent &event) { wxChar ch; #if wxUSE_UNICODE ch = event.GetUnicodeKey(); if (ch <= 127) ch = (wxChar)event.GetKeyCode(); #else ch = (wxChar)event.GetKeyCode(); #endif if (ch != (wxChar)WXK_BACK) { Text()->SetText(ch); Text()->GotoPos(Text()->GetLength()); } } class sqlGridNumericEditor : public wxGridCellTextEditor { public: sqlGridNumericEditor(int len = -1, int prec = -1) { numlen = len; numprec = prec; } virtual wxGridCellEditor *Clone() const { return new sqlGridNumericEditor(numlen, numprec); } virtual void Create(wxWindow *parent, wxWindowID id, wxEvtHandler *evtHandler); virtual bool IsAcceptedKey(wxKeyEvent &event); virtual void BeginEdit(int row, int col, wxGrid *grid); #if wxCHECK_VERSION(2, 9, 0) void ApplyEdit(int row, int col, wxGrid *grid); bool EndEdit(int row, int col, const wxGrid *grid, const wxString &, wxString *); #else bool EndEdit(int row, int col, wxGrid *grid); #endif virtual void Reset() { DoReset(m_startValue); } virtual void StartingKey(wxKeyEvent &event); virtual void SetParameters(const wxString ¶ms); protected: int numlen, numprec; wxString m_startValue; }; void sqlGridNumericEditor::StartingKey(wxKeyEvent &event) { int keycode = event.GetKeyCode(); bool allowed = false; switch (keycode) { case WXK_DECIMAL: case WXK_NUMPAD_DECIMAL: case '.': if (numprec) allowed = true; break; case '+': case WXK_ADD: case WXK_NUMPAD_ADD: case '-': case WXK_SUBTRACT: case WXK_NUMPAD_SUBTRACT: case WXK_NUMPAD0: case WXK_NUMPAD1: case WXK_NUMPAD2: case WXK_NUMPAD3: case WXK_NUMPAD4: case WXK_NUMPAD5: case WXK_NUMPAD6: case WXK_NUMPAD7: case WXK_NUMPAD8: case WXK_NUMPAD9: allowed = true; break; default: if (wxIsdigit(keycode)) allowed = true; break; } if (allowed) wxGridCellTextEditor::StartingKey(event); else event.Skip(); } bool sqlGridNumericEditor::IsAcceptedKey(wxKeyEvent &event) { if ( wxGridCellEditor::IsAcceptedKey(event) ) { int keycode = event.GetKeyCode(); switch ( keycode ) { case WXK_DECIMAL: case WXK_NUMPAD_DECIMAL: return (numprec != 0); case '+': case WXK_ADD: case WXK_NUMPAD_ADD: case '-': case WXK_SUBTRACT: case WXK_NUMPAD_SUBTRACT: case WXK_NUMPAD0: case WXK_NUMPAD1: case WXK_NUMPAD2: case WXK_NUMPAD3: case WXK_NUMPAD4: case WXK_NUMPAD5: case WXK_NUMPAD6: case WXK_NUMPAD7: case WXK_NUMPAD8: case WXK_NUMPAD9: return true; default: return wxIsdigit(keycode) != 0; } } return false; } void sqlGridNumericEditor::BeginEdit(int row, int col, wxGrid *grid) { m_startValue = grid->GetTable()->GetValue(row, col); wxString value = m_startValue; // localize value here DoBeginEdit(value); } #if wxCHECK_VERSION(2, 9, 0) void sqlGridNumericEditor::ApplyEdit(int row, int col, wxGrid *grid) { wxString value = Text()->GetValue(); grid->GetTable()->SetValue(row, col, value); m_startValue = wxEmptyString; Text()->SetValue(m_startValue); } #endif #if wxCHECK_VERSION(2, 9, 0) bool sqlGridNumericEditor::EndEdit(int row, int col, const wxGrid *grid, const wxString &, wxString *) #else bool sqlGridNumericEditor::EndEdit(int row, int col, wxGrid *grid) #endif { wxASSERT_MSG(m_control, wxT("The sqlGridNumericEditor must be Created first!")); bool changed = false; wxString value = Text()->GetValue(); // de-localize value here if (value != m_startValue) changed = true; #if !wxCHECK_VERSION(2, 9, 0) if (changed) grid->GetTable()->SetValue(row, col, value); m_startValue = wxEmptyString; Text()->SetValue(m_startValue); #endif return changed; } void sqlGridNumericEditor::SetParameters(const wxString ¶ms) { if ( !params ) { // reset to default numlen = -1; numprec = -1; } else { long tmp; if ( params.BeforeFirst(wxT(',')).ToLong(&tmp) ) { numlen = (int)tmp; if ( params.AfterFirst(wxT(',')).ToLong(&tmp) ) { numprec = (int)tmp; // skip the error message below return; } } } } void sqlGridNumericEditor::Create(wxWindow *parent, wxWindowID id, wxEvtHandler *evtHandler) { m_control = new wxTextCtrl(parent, id, wxEmptyString, wxDefaultPosition, wxDefaultSize); wxGridCellEditor::Create(parent, id, evtHandler); Text()->SetValidator(wxTextValidator(wxFILTER_NUMERIC)); } ////////////////////////////////////////////////////////////////////// // Bool editor ////////////////////////////////////////////////////////////////////// class sqlGridBoolEditor : public wxGridCellEditor { public: sqlGridBoolEditor() { } virtual void Create(wxWindow *parent, wxWindowID id, wxEvtHandler *evtHandler); virtual void SetSize(const wxRect &rect); virtual void Show(bool show, wxGridCellAttr *attr = (wxGridCellAttr *)NULL); virtual bool IsAcceptedKey(wxKeyEvent &event); virtual void BeginEdit(int row, int col, wxGrid *grid); #if wxCHECK_VERSION(2, 9, 0) virtual void ApplyEdit(int row, int col, wxGrid *grid); // pure virtual in wx 2.9+, doesn't exist in prior versions virtual bool EndEdit(int row, int col, const wxGrid *grid, const wxString &, wxString *); #else virtual bool EndEdit(int row, int col, wxGrid *grid); #endif virtual void Reset(); virtual void StartingClick(); virtual void StartingKey(wxKeyEvent &event); virtual wxGridCellEditor *Clone() const { return new sqlGridBoolEditor; } virtual wxString GetValue() const; protected: wxCheckBox *CBox() const { return (wxCheckBox *)m_control; } private: wxCheckBoxState m_startValue; DECLARE_NO_COPY_CLASS(sqlGridBoolEditor) }; void sqlGridBoolEditor::Create(wxWindow *parent, wxWindowID id, wxEvtHandler *evtHandler) { m_control = new wxCheckBox(parent, id, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxNO_BORDER | wxCHK_3STATE | wxCHK_ALLOW_3RD_STATE_FOR_USER); wxGridCellEditor::Create(parent, id, evtHandler); } void sqlGridBoolEditor::SetSize(const wxRect &r) { bool resize = false; wxSize size = m_control->GetSize(); wxCoord minSize = wxMin(r.width, r.height); // check if the checkbox is not too big/small for this cell wxSize sizeBest = m_control->GetBestSize(); if ( !(size == sizeBest) ) { // reset to default size if it had been made smaller size = sizeBest; resize = true; } if ( size.x >= minSize || size.y >= minSize ) { // leave 1 pixel margin size.x = size.y = minSize - 2; resize = true; } if ( resize ) { m_control->SetSize(size); } // position it in the centre of the rectangle (TODO: support alignment?) #if defined(__WXGTK__) // the checkbox without label still has some space to the right in wxGTK, // so shift it to the right size.x -= 8; #elif defined(__WXMSW__) // here too, but in other way size.x += 1; size.y -= 2; #endif int hAlign = wxALIGN_CENTRE; int vAlign = wxALIGN_CENTRE; if (GetCellAttr()) GetCellAttr()->GetAlignment(& hAlign, & vAlign); int x = 0, y = 0; if (hAlign == wxALIGN_LEFT) { x = r.x + 2; #ifdef __WXMSW__ x += 2; #endif y = r.y + r.height / 2 - size.y / 2; } else if (hAlign == wxALIGN_RIGHT) { x = r.x + r.width - size.x - 2; y = r.y + r.height / 2 - size.y / 2; } else if (hAlign == wxALIGN_CENTRE) { x = r.x + r.width / 2 - size.x / 2; y = r.y + r.height / 2 - size.y / 2; } m_control->Move(x, y); } void sqlGridBoolEditor::Show(bool show, wxGridCellAttr *attr) { m_control->Show(show); if ( show ) { wxColour colBg = attr ? attr->GetBackgroundColour() : *wxLIGHT_GREY; CBox()->SetBackgroundColour(colBg); } } void sqlGridBoolEditor::BeginEdit(int row, int col, wxGrid *grid) { wxASSERT_MSG(m_control, wxT("The sqlGridBoolEditor must be Created first!")); wxString value = grid->GetTable()->GetValue(row, col); if (value == wxT("TRUE")) m_startValue = wxCHK_CHECKED; else if (value == wxT("FALSE")) m_startValue = wxCHK_UNCHECKED; else m_startValue = wxCHK_UNDETERMINED; CBox()->Set3StateValue(m_startValue); CBox()->SetFocus(); } #define BOOL_EDIT_SWITCH switch (value) \ { \ case wxCHK_UNCHECKED:\ grid->GetTable()->SetValue(row, col, wxT("FALSE"));\ break;\ case wxCHK_CHECKED:\ grid->GetTable()->SetValue(row, col, wxT("TRUE"));\ break;\ case wxCHK_UNDETERMINED:\ grid->GetTable()->SetValue(row, col, wxEmptyString);\ break;\ }\ #if wxCHECK_VERSION(2, 9, 0) // pure virtual in 2.9+, doesn't exist in prior versions void sqlGridBoolEditor::ApplyEdit(int row, int col, wxGrid *grid) { wxCheckBoxState value = CBox()->Get3StateValue(); BOOL_EDIT_SWITCH } #endif #if wxCHECK_VERSION(2, 9, 0) bool sqlGridBoolEditor::EndEdit(int row, int col, const wxGrid *grid, const wxString &, wxString *) #else bool sqlGridBoolEditor::EndEdit(int row, int col, wxGrid *grid) #endif { wxASSERT_MSG(m_control, wxT("The sqlGridBoolEditor must be Created first!")); bool changed = false; wxCheckBoxState value = CBox()->Get3StateValue(); if ( value != m_startValue ) changed = true; #if !wxCHECK_VERSION(2, 9, 0) if ( changed ) { BOOL_EDIT_SWITCH } #endif return changed; } void sqlGridBoolEditor::Reset() { wxASSERT_MSG(m_control, wxT("The wxGridCellEditor must be Created first!")); CBox()->Set3StateValue(m_startValue); } void sqlGridBoolEditor::StartingClick() { // We used to cycle the value on click here but // that can lead to odd behaviour of the cell. // Without cycling here, the checkbox is displayed // but the user must toggle the box itself - she // cannot just keep clicking the cell. } bool sqlGridBoolEditor::IsAcceptedKey(wxKeyEvent &event) { if ( wxGridCellEditor::IsAcceptedKey(event) ) { int keycode = event.GetKeyCode(); switch ( keycode ) { case WXK_SPACE: case '+': case '-': case 'n': case 'N': return true; } } return false; } void sqlGridBoolEditor::StartingKey(wxKeyEvent &event) { int keycode = event.GetKeyCode(); wxCheckBoxState value = CBox()->Get3StateValue(); switch ( keycode ) { case WXK_SPACE: switch (value) { case wxCHK_UNCHECKED: case wxCHK_UNDETERMINED: CBox()->Set3StateValue(wxCHK_CHECKED); break; case wxCHK_CHECKED: CBox()->Set3StateValue(wxCHK_UNCHECKED); break; } break; case '+': CBox()->Set3StateValue(wxCHK_CHECKED); break; case '-': CBox()->Set3StateValue(wxCHK_UNCHECKED); break; case 'n': case 'N': CBox()->Set3StateValue(wxCHK_UNDETERMINED); break; } } // return the value as "1" for true and the empty string for false wxString sqlGridBoolEditor::GetValue() const { wxCheckBoxState value = CBox()->Get3StateValue(); switch (value) { case wxCHK_UNCHECKED: return wxT("FALSE"); break; case wxCHK_CHECKED: return wxT("TRUE"); break; case wxCHK_UNDETERMINED: return wxEmptyString; break; } return wxEmptyString; } ////////////////////////////////////////////////////////////////////// // End Bool editor ////////////////////////////////////////////////////////////////////// sqlTable::sqlTable(pgConn *conn, pgQueryThread *_thread, const wxString &tabName, const OID _relid, bool _hasOid, const wxString &_pkCols, char _relkind) { connection = conn; primaryKeyColNumbers = _pkCols; relid = _relid; relkind = _relkind; tableName = tabName; hasOids = _hasOid; thread = _thread; rowsCached = 0; rowsAdded = 0; rowsStored = 0; rowsDeleted = 0; dataPool = 0; addPool = new cacheLinePool(500); // arbitrary initial size lastRow = -1; int i; lineIndex = 0; nRows = thread->DataSet()->NumRows(); nCols = thread->DataSet()->NumCols(); columns = new sqlCellAttr[nCols]; savedLine.cols = new wxString[nCols]; // Get the "real" column list, including any dropped columns, as // key positions etc do not ignore these. pgSet *allColsSet = connection->ExecuteSet( wxT("SELECT attisdropped FROM pg_attribute") wxT(" WHERE attnum > 0 AND attrelid=") + NumToStr(relid) + wxT("::oid\n") wxT(" ORDER BY attnum")); int x = 1; if (allColsSet) { for (i = 0; i < allColsSet->NumRows(); i++) { if (allColsSet->GetVal(wxT("attisdropped")) == wxT("t")) { colMap.Add(0); } else { colMap.Add(x); x++; } allColsSet->MoveNext(); } delete allColsSet; } pgSet *colSet = connection->ExecuteSet( wxT("SELECT n.nspname AS nspname, relname, format_type(t.oid,NULL) AS typname, format_type(t.oid, att.atttypmod) AS displaytypname, ") wxT("nt.nspname AS typnspname, attname, attnum, COALESCE(b.oid, t.oid) AS basetype, atthasdef, pg_get_expr(def.adbin,0) AS adsrc,\n") wxT(" CASE WHEN t.typbasetype::oid=0 THEN att.atttypmod else t.typtypmod END AS typmod,\n") wxT(" CASE WHEN t.typbasetype::oid=0 THEN att.attlen else t.typlen END AS typlen\n") wxT(" FROM pg_attribute att\n") wxT(" JOIN pg_type t ON t.oid=att.atttypid\n") wxT(" JOIN pg_namespace nt ON nt.oid=t.typnamespace\n") wxT(" JOIN pg_class c ON c.oid=attrelid\n") wxT(" JOIN pg_namespace n ON n.oid=relnamespace\n") wxT(" LEFT OUTER JOIN pg_type b ON b.oid=t.typbasetype\n") wxT(" LEFT OUTER JOIN pg_attrdef def ON adrelid=attrelid AND adnum=attnum\n") wxT(" WHERE attnum > 0 AND NOT attisdropped AND attrelid=") + NumToStr(relid) + wxT("::oid\n") wxT(" ORDER BY attnum")); bool canInsert = false; if (colSet) { if (hasOids) { columns[0].name = wxT("oid"); columns[0].numeric = true; columns[0].attr->SetReadOnly(true); columns[0].type = PGTYPCLASS_NUMERIC; } for (i = (hasOids ? 1 : 0) ; i < nCols ; i++) { wxGridCellEditor *editor = 0; columns[i].name = colSet->GetVal(wxT("attname")); columns[i].typeName = colSet->GetVal(wxT("typname")); columns[i].displayTypeName = colSet->GetVal(wxT("displaytypname")); // Special case for character datatypes. We always cast them to text to avoid // truncation issues with casts like ::character(3) if (columns[i].typeName == wxT("character") || columns[i].typeName == wxT("character varying") || columns[i].typeName == wxT("\"char\"")) columns[i].typeName = wxT("text"); columns[i].type = (Oid)colSet->GetOid(wxT("basetype")); if ((columns[i].type == PGOID_TYPE_INT4 || columns[i].type == PGOID_TYPE_INT8 || columns[i].type == PGOID_TYPE_INT2) && colSet->GetBool(wxT("atthasdef"))) { wxString adsrc = colSet->GetVal(wxT("adsrc")); if (adsrc == wxT("nextval('") + colSet->GetVal(wxT("relname")) + wxT("_") + columns[i].name + wxT("_seq'::text)") || adsrc == wxT("nextval('") + colSet->GetVal(wxT("nspname")) + wxT(".") + colSet->GetVal(wxT("relname")) + wxT("_") + columns[i].name + wxT("_seq'::text)") || adsrc == wxT("nextval('") + colSet->GetVal(wxT("relname")) + wxT("_") + columns[i].name + wxT("_seq'::regclass)") || adsrc == wxT("nextval('") + colSet->GetVal(wxT("nspname")) + wxT(".") + colSet->GetVal(wxT("relname")) + wxT("_") + columns[i].name + wxT("_seq'::regclass)")) { if (columns[i].type == PGOID_TYPE_INT4) columns[i].type = (Oid)PGOID_TYPE_SERIAL; else if (columns[i].type == PGOID_TYPE_INT8) columns[i].type = (Oid)PGOID_TYPE_SERIAL8; else columns[i].type = (Oid)PGOID_TYPE_SERIAL2; } } columns[i].typlen = colSet->GetLong(wxT("typlen")); columns[i].typmod = colSet->GetLong(wxT("typmod")); switch (columns[i].type) { case PGOID_TYPE_BOOL: columns[i].numeric = false; columns[i].attr->SetReadOnly(false); editor = new sqlGridBoolEditor(); break; case PGOID_TYPE_INT8: case PGOID_TYPE_SERIAL8: SetNumberEditor(i, 20); break; case PGOID_TYPE_INT2: case PGOID_TYPE_SERIAL2: SetNumberEditor(i, 5); break; case PGOID_TYPE_INT4: case PGOID_TYPE_SERIAL: SetNumberEditor(i, 10); break; case PGOID_TYPE_OID: case PGOID_TYPE_TID: case PGOID_TYPE_XID: case PGOID_TYPE_CID: SetNumberEditor(i, 10); break; case PGOID_TYPE_FLOAT4: case PGOID_TYPE_FLOAT8: columns[i].numeric = true; columns[i].attr->SetReadOnly(false); editor = new sqlGridNumericEditor(); break; case PGOID_TYPE_MONEY: columns[i].numeric = false; columns[i].attr->SetReadOnly(false); editor = new wxGridCellTextEditor(); break; case PGOID_TYPE_NUMERIC: { columns[i].numeric = true; columns[i].attr->SetReadOnly(false); int len = columns[i].size(); int prec = columns[i].precision(); if (prec > 0) len -= (prec); editor = new sqlGridNumericEditor(len, prec); break; } case PGOID_TYPE_BYTEA: columns[i].numeric = false; columns[i].attr->SetReadOnly(true); break; case PGOID_TYPE_DATE: case PGOID_TYPE_TIME: case PGOID_TYPE_TIMETZ: case PGOID_TYPE_TIMESTAMP: case PGOID_TYPE_TIMESTAMPTZ: case PGOID_TYPE_INTERVAL: columns[i].numeric = false; columns[i].attr->SetReadOnly(false); editor = new sqlGridTextEditor(); break; case PGOID_TYPE_CHAR: case PGOID_TYPE_NAME: case PGOID_TYPE_TEXT: default: columns[i].numeric = false; columns[i].attr->SetReadOnly(false); columns[i].needResize = true; editor = new sqlGridTextEditor(); break; } if (editor) columns[i].attr->SetEditor(editor); if (relkind != 'r' || (!hasOids && primaryKeyColNumbers.IsNull())) { // for security reasons, we need oid or pk to enable updates. If none is available, // the table definition can be considered faulty. columns[i].attr->SetReadOnly(true); } if (!columns[i].attr->IsReadOnly()) canInsert = true; wxStringTokenizer collist(primaryKeyColNumbers, wxT(",")); long cn = 0; long attnum = colSet->GetLong(wxT("attnum")); while (cn < attnum && collist.HasMoreTokens()) { cn = StrToLong(collist.GetNextToken()); if (cn == attnum) columns[i].isPrimaryKey = true; } colSet->MoveNext(); } delete colSet; } else { // um, we never should reach here because this would mean // that datatypes are unreadable. // *if* we reach here, namespace info is missing. for (i = 0 ; i < nCols ; i++) { columns[i].typeName = thread->DataSet()->ColType(i); columns[i].name = thread->DataSet()->ColName(i); } } if (nRows) { dataPool = new cacheLinePool(nRows); lineIndex = new int[nRows]; for (i = 0 ; i < nRows ; i++) lineIndex[i] = i; } if (canInsert) { // an empty line waiting for inserts rowsAdded = 1; } } sqlTable::~sqlTable() { if (thread) delete thread; if (dataPool) delete dataPool; delete addPool; delete[] columns; if (lineIndex) delete[] lineIndex; } int sqlTable::GetNumberCols() { return nCols; } int sqlTable::GetNumberRows() { return nRows + rowsAdded - rowsDeleted; } int sqlTable::GetNumberStoredRows() { return nRows + rowsStored - rowsDeleted; } bool ctlSQLEditGrid::IsColText(int col) { return GetTable()->IsColText(col); } bool sqlTable::IsColText(int col) { return !columns[col].numeric && !(columns[col].type == PGOID_TYPE_BOOL); } bool sqlTable::IsColBoolean(int col) { return (columns[col].type == PGOID_TYPE_BOOL); } wxString sqlTable::GetColLabelValue(int col) { wxString label = columns[col].name + wxT("\n"); if (columns[col].isPrimaryKey) label += wxT("[PK] "); switch (columns[col].type) { case (Oid)PGOID_TYPE_SERIAL: label += wxT("serial"); break; case (Oid)PGOID_TYPE_SERIAL8: label += wxT("bigserial"); break; case (Oid)PGOID_TYPE_SERIAL2: label += wxT("smallserial"); break; default: label += columns[col].displayTypeName; break; } return label; } wxString sqlTable::GetColLabelValueUnformatted(int col) { return columns[col].name; } wxString sqlTable::GetRowLabelValue(int row) { wxString label; if (row < nRows - rowsDeleted || GetLine(row)->stored) label.Printf(wxT("%d"), row + 1); else label = wxT("*"); return label; } void sqlTable::SetNumberEditor(int col, int len) { columns[col].numeric = true; columns[col].attr->SetReadOnly(false); columns[col].attr->SetEditor(new sqlGridNumericEditor(len, 0)); } bool sqlTable::CheckInCache(int row) { if (row > nRows - rowsDeleted + rowsAdded) return false; if (row >= nRows - rowsDeleted) return true; return dataPool->IsFilled(row); } cacheLine *sqlTable::GetLine(int row) { cacheLine *line; if (row < nRows - rowsDeleted) line = dataPool->Get(lineIndex[row]); else line = addPool->Get(row - (nRows - rowsDeleted)); return line; } wxString sqlTable::MakeKey(cacheLine *line) { wxString whereClause; if (!primaryKeyColNumbers.IsEmpty()) { wxStringTokenizer collist(primaryKeyColNumbers, wxT(",")); long cn; int offset; if (hasOids) offset = 0; else offset = 1; while (collist.HasMoreTokens()) { cn = StrToLong(collist.GetNextToken()); // Translate the column location to the real location in the actual columns still present cn = colMap[cn - 1]; wxString colval = line->cols[cn - offset]; if (colval.IsEmpty()) return wxEmptyString; if (colval == wxT("''") && columns[cn - offset].typeName == wxT("text")) colval = wxEmptyString; if (!whereClause.IsEmpty()) whereClause += wxT(" AND "); whereClause += qtIdent(columns[cn - offset].name) + wxT(" = ") + connection->qtDbString(colval); if (columns[cn - offset].typeName != wxT("")) { whereClause += wxT("::"); whereClause += columns[cn - offset].displayTypeName; } } } else if (hasOids) whereClause = wxT("oid = ") + line->cols[0]; return whereClause; } void sqlTable::UndoLine(int row) { if (lastRow >= 0 && row >= 0) { cacheLine *line = GetLine(row); if (line) { int i; for (i = 0 ; i < nCols ; i++) line->cols[i] = savedLine.cols[i]; ctlMenuToolbar *tb = (ctlMenuToolbar *)((wxFrame *)GetView()->GetParent())->GetToolBar(); if (tb) { tb->EnableTool(MNU_SAVE, false); tb->EnableTool(MNU_UNDO, false); } wxMenu *fm = ((frmEditGrid *)GetView()->GetParent())->GetFileMenu(); if (fm) fm->Enable(MNU_SAVE, false); wxMenu *em = ((frmEditGrid *)GetView()->GetParent())->GetEditMenu(); if (em) em->Enable(MNU_UNDO, false); } } lastRow = -1; } bool sqlTable::StoreLine() { bool done = false; GetView()->BeginBatch(); if (lastRow >= 0) { cacheLine *line = GetLine(lastRow); int i; wxString colList, valList; if (line->stored) { // UPDATE for (i = (hasOids ? 1 : 0) ; i < nCols ; i++) { if (savedLine.cols[i] != line->cols[i]) { if (!valList.IsNull()) valList += wxT(", "); valList += qtIdent(columns[i].name) + wxT("=") + columns[i].Quote(connection, line->cols[i]); } } if (valList.IsEmpty()) done = true; else { wxString key = MakeKey(&savedLine); wxASSERT(!key.IsEmpty()); done = connection->ExecuteVoid(wxT( "UPDATE ") + tableName + wxT( " SET ") + valList + wxT( " WHERE ") + key); } } else { // INSERT for (i = 0 ; i < nCols ; i++) { if (!columns[i].attr->IsReadOnly() && !line->cols[i].IsEmpty()) { if (!colList.IsNull()) { valList += wxT(", "); colList += wxT(", "); } colList += qtIdent(columns[i].name); valList += columns[i].Quote(connection, line->cols[i]); } } if (!valList.IsEmpty()) { pgSet *set = connection->ExecuteSet( wxT("INSERT INTO ") + tableName + wxT("(") + colList + wxT(") VALUES (") + valList + wxT(")")); if (set) { if (set->GetInsertedCount() > 0) { if (hasOids) line->cols[0] = NumToStr((long)set->GetInsertedOid()); delete set; done = true; rowsStored++; ((wxFrame *)GetView()->GetParent())->SetStatusText(wxString::Format(wxT("%d rows."), GetNumberStoredRows())); if (rowsAdded == rowsStored) GetView()->AppendRows(); // Read back what we inserted to get default vals wxString key = MakeKey(line); if (key.IsEmpty()) { // That's a problem: obviously, the key generated isn't present // because it's serial or default or otherwise generated in the backend // we don't get. // That's why the whole line is declared readonly. line->readOnly = true; } else { set = connection->ExecuteSet( wxT("SELECT * FROM ") + tableName + wxT(" WHERE ") + key); if (set) { for (i = (hasOids ? 1 : 0) ; i < nCols ; i++) { line->cols[i] = set->GetVal(columns[i].name); } delete set; } } } } } } if (done) { line->stored = true; lastRow = -1; } else GetView()->SelectRow(lastRow); } GetView()->EndBatch(); return done; } void sqlTable::SetValue(int row, int col, const wxString &value) { cacheLine *line = GetLine(row); if (!line) { // Bad problem, no line! return; } if (row != lastRow) { if (lastRow >= 0) StoreLine(); if (!line->cols) line->cols = new wxString[nCols]; // remember line contents for later reference in update ... where int i; for (i = 0 ; i < nCols ; i++) savedLine.cols[i] = line->cols[i]; lastRow = row; } ctlMenuToolbar *tb = (ctlMenuToolbar *)((wxFrame *)GetView()->GetParent())->GetToolBar(); if (tb) { tb->EnableTool(MNU_SAVE, true); tb->EnableTool(MNU_UNDO, true); } wxMenu *fm = ((frmEditGrid *)GetView()->GetParent())->GetFileMenu(); if (fm) fm->Enable(MNU_SAVE, true); wxMenu *em = ((frmEditGrid *)GetView()->GetParent())->GetEditMenu(); if (em) em->Enable(MNU_UNDO, true); line->cols[col] = value; } wxString sqlTable::GetValue(int row, int col) { wxString val; cacheLine *line; if (row < nRows - rowsDeleted) line = dataPool->Get(lineIndex[row]); else line = addPool->Get(row - (nRows - rowsDeleted)); if (!line) { // Bad problem, no line! return val; } if (!line->cols) { line->cols = new wxString[nCols]; if (row < nRows - rowsDeleted) { if (!thread) { wxLogError(__("Unexpected empty cache line: dataSet already closed.")); return val; } line->stored = true; if (lineIndex[row] != thread->DataSet()->CurrentPos() - 1) thread->DataSet()->Locate(lineIndex[row] + 1); int i; for (i = 0 ; i < nCols ; i++) { wxString val; if (thread->DataSet()->ColType(i) == wxT("bytea")) val = _(""); else { val = thread->DataSet()->GetVal(i); if (val.IsEmpty()) { if (!thread->DataSet()->IsNull(i)) val = wxT("''"); } else if (val == wxT("''")) val = wxT("\\'\\'"); } line->cols[i] = val; } rowsCached++; if (rowsCached == nRows) { delete thread; thread = 0; } } } if (columns[col].type == PGOID_TYPE_BOOL) { if (line->cols[col] != wxEmptyString) line->cols[col] = (StrToBool(line->cols[col]) ? wxT("TRUE") : wxT("FALSE")); } val = line->cols[col]; return val; } bool sqlTable::AppendRows(size_t rows) { rowsAdded += rows; GetLine(nRows + rowsAdded - rowsDeleted - 1); wxGridTableMessage msg(this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, rows); GetView()->ProcessTableMessage(msg); return true; } bool sqlTable::DeleteRows(size_t pos, size_t rows) { size_t i = pos; size_t rowsDone = 0; while (i < pos + rows) { cacheLine *line = GetLine(pos); if (!line) break; // If line->cols is null, it probably means we need to force the cacheline to be populated. if (!line->cols) { GetValue(pos, 0); line = GetLine(pos); } if (line->stored) { wxString key = MakeKey(line); wxASSERT(!key.IsEmpty()); bool done = connection->ExecuteVoid(wxT( "DELETE FROM ") + tableName + wxT(" WHERE ") + key); if (!done) break; if ((int)pos < nRows - rowsDeleted) { rowsDeleted++; if ((int)pos < nRows - rowsDeleted) memmove(lineIndex + pos, lineIndex + pos + 1, sizeof(*lineIndex) * (nRows - rowsDeleted - pos)); } else { rowsAdded--; if (GetLine(pos)->stored) rowsStored--; addPool->Delete(pos - (nRows - rowsDeleted)); } rowsDone++; } else { // last empty line won't be deleted, just cleared int j; for (j = 0 ; j < nCols ; j++) line->cols[j] = wxT(""); } i++; } if (rowsDone > 0 && GetView()) { wxGridTableMessage msg(this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, pos, rowsDone); GetView()->ProcessTableMessage(msg); } return (rowsDone != 0); } bool sqlTable::Paste() { int row, col; int start, pos, len; wxArrayString data; wxString text, quoteChar, colSep; bool inQuotes, inData, skipSerial; if (!this) return false; if (wxTheClipboard->Open()) { if (wxTheClipboard->IsSupported(wxDF_TEXT)) { wxTextDataObject textData; wxTheClipboard->GetData(textData); text = textData.GetText(); } else { wxTheClipboard->Close(); return false; } wxTheClipboard->Close(); } else { return false; } start = pos = 0; len = text.Len(); quoteChar = settings->GetCopyQuoteChar(); colSep = settings->GetCopyColSeparator(); inQuotes = inData = false; while (pos < len && !(text[pos] == '\n' && !inQuotes)) { if (!inData) { if (text[pos] == quoteChar) { inQuotes = inData = true; pos++; start++; continue; } else { inQuotes = false; } inData = true; } if (inQuotes && text[pos] == quoteChar && (text[pos + 1] == colSep || text[pos + 1] == '\r' || text[pos + 1] == '\n')) { data.Add(text.Mid(start, pos - start)); start = (pos += 2); inData = false; } else if (!inQuotes && text[pos] == colSep) { data.Add(text.Mid(start, pos - start)); start = ++pos; inData = false; } else { pos++; } } if (start < pos) { if (inQuotes && text[pos - 1] == quoteChar) data.Add(text.Mid(start, pos - start - 1)); else data.Add(text.Mid(start, pos - start)); } row = GetNumberRows() - 1; skipSerial = false; for (col = 0; col < nCols; col++) { if (columns[col].type == (unsigned int)PGOID_TYPE_SERIAL || columns[col].type == (unsigned int)PGOID_TYPE_SERIAL8 || columns[col].type == (unsigned int)PGOID_TYPE_SERIAL2) { wxMessageDialog msg(GetView()->GetParent(), _("This table contains serial columns. Do you want to use the values in the clipboard for these columns?"), _("Paste Data"), wxYES_NO | wxICON_QUESTION); if (msg.ShowModal() != wxID_YES) { skipSerial = true; } break; } } bool pasted = false; for (col = (hasOids ? 1 : 0); col < nCols && col < (int)data.GetCount(); col++) { if (!(skipSerial && (columns[col].type == (unsigned int)PGOID_TYPE_SERIAL || columns[col].type == (unsigned int)PGOID_TYPE_SERIAL8 || columns[col].type == (unsigned int)PGOID_TYPE_SERIAL2))) { SetValue(row, col, data.Item(col)); GetView()->SetGridCursor(row, col); GetView()->MakeCellVisible(row, col); pasted = true; } } GetView()->ForceRefresh(); return pasted; } wxGridCellAttr *sqlTable::GetAttr(int row, int col, wxGridCellAttr::wxAttrKind kind) { cacheLine *line = GetLine(row); if (line && line->readOnly) { wxGridCellAttr *attr = new wxGridCellAttr(columns[col].attr); attr->SetReadOnly(); return attr; } else { columns[col].attr->IncRef(); return columns[col].attr; } } wxString sqlCellAttr::Quote(pgConn *conn, const wxString &value) { wxString str; if (value.IsEmpty()) str = wxT("NULL"); else if (numeric) str = conn->qtDbString(value); else if (value == wxT("\\'\\'")) str = conn->qtDbString(wxT("''")); else if (value == wxT("''")) str = wxT("''"); else if (type == PGOID_TYPE_BOOL) str = value; else if (type == PGOID_TYPE_BIT) // Don't cast this one return wxT("B'") + value + wxT("'"); else str = conn->qtDbString(value); return str + wxT("::") + displayTypeName; } int sqlCellAttr::size() { if (typlen == -1 && typmod > 0) { return (typmod - 4) >> 16; } else return typlen; } int sqlCellAttr::precision() { if (typlen == -1 && typmod > 0) { return (typmod - 4) & 0x7fff; } else return -1; } cacheLinePool::cacheLinePool(int initialLines) { ptr = new cacheLine*[initialLines]; if (ptr) { anzLines = initialLines; memset(ptr, 0, sizeof(cacheLine *)*anzLines); } else { anzLines = 0; wxLogError(__("Out of Memory for cacheLinePool")); } } cacheLinePool::~cacheLinePool() { if (ptr) { while (anzLines--) { if (ptr[anzLines]) delete ptr[anzLines]; } delete[] ptr; } } void cacheLinePool::Delete(int lineNo) { if (ptr && lineNo >= 0 && lineNo < anzLines) { #if 1 if (ptr[lineNo]) delete ptr[lineNo]; if (lineNo < anzLines - 1) { // beware: overlapping copy memmove(ptr + lineNo, ptr + lineNo + 1, sizeof(cacheLine *) * (anzLines - lineNo - 1)); } #else cacheLine *c; c = ptr[0]; ptr[0] = ptr[1]; ptr[1] = c; #endif ptr[anzLines - 1] = 0; } } cacheLine *cacheLinePool::Get(int lineNo) { if (lineNo < 0) return 0; if (lineNo >= anzLines) { cacheLine **old = ptr; int oldAnz = anzLines; anzLines = lineNo + 100; ptr = new cacheLine*[anzLines]; if (!ptr) { anzLines = 0; wxLogError(__("Out of Memory for cacheLinePool")); } else { if (oldAnz) { memcpy(ptr, old, sizeof(cacheLine *)*oldAnz); delete[] old; } memset(ptr + oldAnz, 0, anzLines - oldAnz); } } if (lineNo < anzLines) { if (!ptr[lineNo]) ptr[lineNo] = new cacheLine(); return ptr[lineNo]; } return 0; } bool cacheLinePool::IsFilled(int lineNo) { return (lineNo < anzLines && ptr[lineNo]); } bool editGridFactoryBase::CheckEnable(pgObject *obj) { if (obj) { pgaFactory *factory = obj->GetFactory(); return factory == &pg_partitionFactory || factory == &tableFactory || factory == &foreignTableFactory || factory == &viewFactory || factory == &extTableFactory || factory == &catalogObjectFactory; } return false; } wxWindow *editGridFactoryBase::ViewData(frmMain *form, pgObject *obj, bool filter) { pgDatabase *db = ((pgSchemaObject *)obj)->GetDatabase(); wxString applicationname = appearanceFactory->GetLongAppName() + _(" - Edit Grid"); pgServer *server = db->GetServer(); pgConn *conn = db->CreateConn(applicationname); if (conn) { wxString txt = _("Edit Data - ") + server->GetDescription() + wxT(" (") + server->GetName() + wxT(":") + NumToStr((long)server->GetPort()) + wxT(") - ") + db->GetName() + wxT(" - ") + obj->GetFullIdentifier(); frmEditGrid *eg = new frmEditGrid(form, txt, conn, (pgSchemaObject *)obj, pkAscending); eg->SetLimit(rowlimit); eg->ShowForm(filter); return eg; } return 0; } editGridFactory::editGridFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : editGridFactoryBase(list) { mnu->Append(id, _("View &All Rows\tCtrl-D"), _("View the data in the selected object.")); toolbar->AddTool(id, _("View All Rows\tCtrl-D"), *GetBundleSVG(viewdata_png_bmp, "viewdata.svg", wxSize(32, 32)), _("View the data in the selected object."), wxITEM_NORMAL); context = false; } wxWindow *editGridFactory::StartDialog(frmMain *form, pgObject *obj) { return ViewData(form, obj, false); } #include "images/viewfiltereddata.pngc" editGridFilteredFactory::editGridFilteredFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : editGridFactoryBase(list) { mnu->Append(id, _("View F&iltered Rows...\tCtrl-G"), _("Apply a filter and view the data in the selected object.")); toolbar->AddTool(id, _("View Filtered Rows\tCtrl-G"), *GetBundleSVG(viewfiltereddata_png_bmp, "viewfiltereddata.svg", wxSize(32, 32)) , _("Apply a filter and view the data in the selected object."), wxITEM_NORMAL); context = false; } wxWindow *editGridFilteredFactory::StartDialog(frmMain *form, pgObject *obj) { return ViewData(form, obj, true); } editGridLimitedFactory::editGridLimitedFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar, int limit, bool ascending) : editGridFactoryBase(list) { if (ascending) mnu->Append(id, wxString::Format(wxPLURAL("View Top %i Row", "View Top %i Rows", limit), limit), _("View a limited number of rows in the selected object.")); else mnu->Append(id, wxString::Format(wxPLURAL("View Last %i Row", "View Last %i Rows", limit), limit), _("View a limited number of rows in the selected object.")); pkAscending = ascending; rowlimit = limit; context = false; } wxWindow *editGridLimitedFactory::StartDialog(frmMain *form, pgObject *obj) { return ViewData(form, obj, false); }