diff --git a/CHANGELOG b/CHANGELOG index 39ee010..3343fa7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -37,6 +37,8 @@ Changes Date Dev Ver Change details ---------- --- ------ -------------- +2016-02-16 DP 1.24.0 Allow multiple SQL editor tabs to be used in the + query tool [Sergey Busel] 2016-02-16 DP 1.22.2 Prevent a crash in some situtions where the database connection is lost and needs to be reset. 2016-02-11 DP 1.22.2 Fix display of elapsed query time for queries running diff --git a/pgadmin/ctl/ctlSQLBox.cpp b/pgadmin/ctl/ctlSQLBox.cpp index 0c80091..a45bb5e 100644 --- a/pgadmin/ctl/ctlSQLBox.cpp +++ b/pgadmin/ctl/ctlSQLBox.cpp @@ -184,6 +184,87 @@ void ctlSQLBox::SetDatabase(pgConn *db) m_database = db; } +void ctlSQLBox::SetChanged(bool b) +{ + if (m_changed != b) + { + m_changed = b; + UpdateTitle(); + } +} + +bool ctlSQLBox::IsChanged() +{ + return m_changed; +} + +void ctlSQLBox::SetOrigin(int origin) +{ + m_origin = origin; +} + +int ctlSQLBox::GetOrigin() +{ + return m_origin; +} + +void ctlSQLBox::SetFilename(wxString &filename) +{ + m_filename = filename; + UpdateTitle(); +} + +wxString ctlSQLBox::GetFilename() +{ + return m_filename; +} + +void ctlSQLBox::SetTitle(wxString &title) +{ + m_title = title; +} + +wxString ctlSQLBox::GetTitle(bool withChangeInd) +{ + wxString title = m_title; + wxString chStr; + if (!withChangeInd) + { + chStr = GetChangeIndicator(); + if (title.EndsWith(chStr)) + title = title.Mid(0, title.Len() - chStr.Len()); + } + return title; +} + +wxString ctlSQLBox::GetChangeIndicator() +{ + if (m_changestr.IsEmpty()) + m_changestr = _("*"); + return m_changestr; +} + +void ctlSQLBox::UpdateTitle() +{ + bool hasCh = false; + wxString chStr = GetChangeIndicator(); + wxString title = GetFilename(); + + if (!title.IsEmpty()) + title = wxFileName::FileName(title).GetFullName(); + else + title = GetTitle(); + + hasCh = title.EndsWith(chStr); + + if (IsChanged() && !hasCh) + title = title + chStr; + else if (!IsChanged() && hasCh) + title = title.Mid(0, title.Len() - chStr.Len()); + + SetTitle(title); +} + void ctlSQLBox::OnSearchReplace(wxCommandEvent &ev) { if (!m_dlgFindReplace) diff --git a/pgadmin/frm/frmQuery.cpp b/pgadmin/frm/frmQuery.cpp index df9c376..067b2d5 100644 --- a/pgadmin/frm/frmQuery.cpp +++ b/pgadmin/frm/frmQuery.cpp @@ -84,6 +84,7 @@ #define CTRLID_CONNECTION 4200 #define CTRLID_DATABASELABEL 4201 +#define CTL_SQLQUERYBOOK 4202 #define XML_FROM_WXSTRING(s) ((const xmlChar *)(const char *)s.mb_str(wxConvUTF8)) #define WXSTRING_FROM_XML(s) wxString((char *)s, wxConvUTF8) @@ -106,6 +107,7 @@ BEGIN_EVENT_TABLE(frmQuery, pgFrame) EVT_MENU(MNU_OPEN, frmQuery::OnOpen) EVT_MENU(MNU_SAVE, frmQuery::OnSave) EVT_MENU(MNU_SAVEAS, frmQuery::OnSaveAs) + EVT_MENU(MNU_NEWSQLTAB, frmQuery::OnSqlBookAddPage) EVT_MENU(MNU_EXPORT, frmQuery::OnExport) EVT_MENU(MNU_SAVEAS_IMAGE_GQB, frmQuery::SaveExplainAsImage) EVT_MENU(MNU_SAVEAS_IMAGE_EXPLAIN, frmQuery::SaveExplainAsImage) @@ -170,6 +172,9 @@ BEGIN_EVENT_TABLE(frmQuery, pgFrame) EVT_PGQUERYRESULT(QUERY_COMPLETE, frmQuery::OnQueryComplete) EVT_MENU(PGSCRIPT_COMPLETE, frmQuery::OnScriptComplete) EVT_AUINOTEBOOK_PAGE_CHANGED(CTL_NTBKCENTER, frmQuery::OnChangeNotebook) + EVT_AUINOTEBOOK_PAGE_CHANGED(CTL_SQLQUERYBOOK, frmQuery::OnSqlBookPageChanged) + EVT_AUINOTEBOOK_PAGE_CHANGING(CTL_SQLQUERYBOOK, frmQuery::OnSqlBookPageChanging) + EVT_AUINOTEBOOK_PAGE_CLOSE(CTL_SQLQUERYBOOK, frmQuery::OnSqlBookPageClose) EVT_SPLITTER_SASH_POS_CHANGED(GQB_HORZ_SASH, frmQuery::OnResizeHorizontally) EVT_BUTTON(CTL_DELETECURRENTBTN, frmQuery::OnDeleteCurrent) EVT_BUTTON(CTL_DELETEALLBTN, frmQuery::OnDeleteAll) @@ -235,7 +240,6 @@ frmQuery::frmQuery(frmMain *form, const wxString &_title, pgConn *_conn, const w loading = true; closing = false; - origin = ORIGIN_MANUAL; dlgName = wxT("frmQuery"); recentKey = wxT("RecentFiles"); @@ -263,6 +267,11 @@ frmQuery::frmQuery(frmMain *form, const wxString &_title, pgConn *_conn, const w saveasImageMenu->Append(MNU_SAVEAS_IMAGE_GQB, _("Graphical Query (image)"), _("Save Graphical Query as an image")); saveasImageMenu->Append(MNU_SAVEAS_IMAGE_EXPLAIN, _("Explain (image)"), _("Save output of Explain as an image")); fileMenu->Append(wxID_ANY, _("Save as"), saveasImageMenu); + + // SQL tabs related menu items + fileMenu->AppendSeparator(); + fileMenu->Append(MNU_NEWSQLTAB, _("New SQL &tab\tCtrl-T"), _("Open a new query tab")); + fileMenu->AppendSeparator(); fileMenu->Append(MNU_EXPORT, _("&Export..."), _("Export data to file")); fileMenu->Append(MNU_QUICKREPORT, _("&Quick report..."), _("Run a quick report...")); @@ -388,7 +397,7 @@ frmQuery::frmQuery(frmMain *form, const wxString &_title, pgConn *_conn, const w UpdateRecentFiles(); - wxAcceleratorEntry entries[15]; + wxAcceleratorEntry entries[16]; entries[0].Set(wxACCEL_CTRL, (int)'E', MNU_EXECUTE); entries[1].Set(wxACCEL_CTRL, (int)'O', MNU_OPEN); @@ -405,8 +414,9 @@ frmQuery::frmQuery(frmMain *form, const wxString &_title, pgConn *_conn, const w entries[12].Set(wxACCEL_CTRL, (int)'N', MNU_NEW); entries[13].Set(wxACCEL_NORMAL, WXK_F6, MNU_EXECPGS); entries[14].Set(wxACCEL_NORMAL, WXK_F8, MNU_EXECFILE); + entries[15].Set(wxACCEL_CTRL, (int)'T', MNU_NEWSQLTAB); - wxAcceleratorTable accel(15, entries); + wxAcceleratorTable accel(16, entries); SetAcceleratorTable(accel); queryMenu->Enable(MNU_CANCEL, false); @@ -421,7 +431,7 @@ frmQuery::frmQuery(frmMain *form, const wxString &_title, pgConn *_conn, const w toolBar->SetToolBitmapSize(wxSize(16, 16)); - toolBar->AddTool(MNU_NEW, wxEmptyString, *file_new_png_bmp, _("New window"), wxITEM_NORMAL); + toolBar->AddTool(MNU_NEWSQLTAB, wxEmptyString, *file_new_png_bmp, _("New SQL tab"), wxITEM_NORMAL); toolBar->AddTool(MNU_OPEN, wxEmptyString, *file_open_png_bmp, _("Open file"), wxITEM_NORMAL); toolBar->AddTool(MNU_SAVE, wxEmptyString, *file_save_png_bmp, _("Save file"), wxITEM_NORMAL); toolBar->AddSeparator(); @@ -494,13 +504,14 @@ frmQuery::frmQuery(frmMain *form, const wxString &_title, pgConn *_conn, const w // This one will contain the SQL box wxBoxSizer *boxSQL = new wxBoxSizer(wxHORIZONTAL); - // Query box - sqlQuery = new ctlSQLBox(pnlQuery, CTL_SQLQUERY, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxSIMPLE_BORDER | wxTE_RICH2); - sqlQuery->SetDatabase(conn); - sqlQuery->SetMarginWidth(1, 16); - sqlQuery->SetDropTarget(new DnDFile(this)); - SetEOLModeDisplay(sqlQuery->GetEOLMode()); - boxSQL->Add(sqlQuery, 1, wxEXPAND | wxRIGHT | wxLEFT | wxBOTTOM, 1); + // Use sqlQueryBook instead of sqlQuery and put the book inside boxSQL sizer. + // We don't add any SQL tabs until all menu items are initialized from settings. + sqlQueryBook = new ctlAuiNotebook(pnlQuery, CTL_SQLQUERYBOOK, wxDefaultPosition, wxDefaultSize, wxAUI_NB_TOP | wxAUI_NB_TAB_SPLIT | wxAUI_NB_TAB_MOVE | wxAUI_NB_SCROLL_BUTTONS | wxAUI_NB_CLOSE_ON_ACTIVE_TAB | wxAUI_NB_WINDOWLIST_BUTTON); + sqlQueryCounter = 0; + sqlQueryExec = NULL; + sqlQueryExecLast = NULL; + + boxSQL->Add(sqlQueryBook, 1, wxEXPAND | wxRIGHT | wxLEFT | wxBOTTOM, 1); boxQuery->Add(boxSQL, 1, wxEXPAND | wxRIGHT | wxLEFT | wxBOTTOM, 1); @@ -535,8 +546,6 @@ frmQuery::frmQuery(frmMain *form, const wxString &_title, pgConn *_conn, const w outputPane->AddPage(msgResult, _("Messages")); outputPane->AddPage(msgHistory, _("History")); - sqlQuery->Connect(wxID_ANY, wxEVT_SET_FOCUS, wxFocusEventHandler(frmQuery::OnFocus)); - sqlQuery->Connect(wxID_ANY, wxEVT_KILL_FOCUS, wxFocusEventHandler(frmQuery::OnFocus)); sqlResult->Connect(wxID_ANY, wxEVT_SET_FOCUS, wxFocusEventHandler(frmQuery::OnFocus)); msgResult->Connect(wxID_ANY, wxEVT_SET_FOCUS, wxFocusEventHandler(frmQuery::OnFocus)); msgHistory->Connect(wxID_ANY, wxEVT_SET_FOCUS, wxFocusEventHandler(frmQuery::OnFocus)); @@ -586,47 +595,30 @@ frmQuery::frmQuery(frmMain *form, const wxString &_title, pgConn *_conn, const w // Auto indent settings->Read(wxT("frmQuery/AutoIndent"), &bVal, true); editMenu->Check(MNU_AUTOINDENT, bVal); - if (bVal) - sqlQuery->SetAutoIndent(true); - else - sqlQuery->SetAutoIndent(false); // Word wrap settings->Read(wxT("frmQuery/WordWrap"), &bVal, false); viewMenu->Check(MNU_WORDWRAP, bVal); - if (bVal) - sqlQuery->SetWrapMode(wxSTC_WRAP_WORD); - else - sqlQuery->SetWrapMode(wxSTC_WRAP_NONE); // Indent Guides settings->Read(wxT("frmQuery/ShowIndentGuides"), &bVal, false); viewMenu->Check(MNU_SHOWINDENTGUIDES, bVal); - if (bVal) - sqlQuery->SetIndentationGuides(true); - else - sqlQuery->SetIndentationGuides(false); // Whitespace settings->Read(wxT("frmQuery/ShowWhitespace"), &bVal, false); viewMenu->Check(MNU_SHOWWHITESPACE, bVal); - if (bVal) - sqlQuery->SetViewWhiteSpace(wxSTC_WS_VISIBLEALWAYS); - else - sqlQuery->SetViewWhiteSpace(wxSTC_WS_INVISIBLE); // Line ends settings->Read(wxT("frmQuery/ShowLineEnds"), &bVal, false); viewMenu->Check(MNU_SHOWLINEENDS, bVal); - if (bVal) - sqlQuery->SetViewEOL(1); - else - sqlQuery->SetViewEOL(0); // Line number settings->Read(wxT("frmQuery/ShowLineNumber"), &bVal, false); viewMenu->Check(MNU_SHOWLINENUMBER, bVal); + // Create the SQL box. After this, sqlQuery variable can be used. + SqlBookAddPage(); + if (!file.IsEmpty() && wxFileName::FileExists(file)) { wxFileName fn = file; @@ -641,12 +633,13 @@ frmQuery::frmQuery(frmMain *form, const wxString &_title, pgConn *_conn, const w sqlQuery->SetText(query); sqlQuery->Colourise(0, query.Length()); wxSafeYield(); // needed to process sqlQuery modify event - changed = false; - origin = ORIGIN_INITIAL; + sqlQuery->SetChanged(false); + sqlQuery->SetOrigin(ORIGIN_INITIAL); /* _title if not empty should contain displayName of base object for the query. It's pretty good for a proposed filename if the user chooses to Save As. */ lastFilename = _title; setExtendedTitle(); + SqlBookUpdatePageTitle(); } updateMenu(); @@ -818,6 +811,9 @@ void frmQuery::OnAuiUpdate(wxAuiManagerEvent &event) void frmQuery::OnDefaultView(wxCommandEvent &event) { + // Reset captions to whatever AuiManager expects in perspective + BeginPerspectiveChange(); + manager.LoadPerspective(FRMQUERY_DEFAULT_PERSPECTIVE, true); // Reset the captions for the current language @@ -827,6 +823,8 @@ void frmQuery::OnDefaultView(wxCommandEvent &event) manager.GetPane(wxT("outputPane")).Caption(_("Output pane")); manager.GetPane(wxT("scratchPad")).Caption(_("Scratch pad")); + EndPerspectiveChange(false); + manager.Update(); // Sync the View menu options @@ -860,10 +858,7 @@ void frmQuery::OnAutoIndent(wxCommandEvent &event) settings->WriteBool(wxT("frmQuery/AutoIndent"), editMenu->IsChecked(MNU_AUTOINDENT)); - if (editMenu->IsChecked(MNU_AUTOINDENT)) - sqlQuery->SetAutoIndent(true); - else - sqlQuery->SetAutoIndent(false); + SqlBookSetAutoIndent(event.IsChecked()); } @@ -873,10 +868,7 @@ void frmQuery::OnWordWrap(wxCommandEvent &event) settings->WriteBool(wxT("frmQuery/WordWrap"), viewMenu->IsChecked(MNU_WORDWRAP)); - if (viewMenu->IsChecked(MNU_WORDWRAP)) - sqlQuery->SetWrapMode(wxSTC_WRAP_WORD); - else - sqlQuery->SetWrapMode(wxSTC_WRAP_NONE); + SqlBookSetWrapMode(event.IsChecked()); } @@ -886,10 +878,7 @@ void frmQuery::OnShowIndentGuides(wxCommandEvent &event) settings->WriteBool(wxT("frmQuery/ShowIndentGuides"), viewMenu->IsChecked(MNU_SHOWINDENTGUIDES)); - if (viewMenu->IsChecked(MNU_SHOWINDENTGUIDES)) - sqlQuery->SetIndentationGuides(true); - else - sqlQuery->SetIndentationGuides(false); + SqlBookSetIndentGuides(event.IsChecked()); } @@ -899,10 +888,7 @@ void frmQuery::OnShowWhitespace(wxCommandEvent &event) settings->WriteBool(wxT("frmQuery/ShowWhitespace"), viewMenu->IsChecked(MNU_SHOWWHITESPACE)); - if (viewMenu->IsChecked(MNU_SHOWWHITESPACE)) - sqlQuery->SetViewWhiteSpace(wxSTC_WS_VISIBLEALWAYS); - else - sqlQuery->SetViewWhiteSpace(wxSTC_WS_INVISIBLE); + SqlBookSetViewWhiteSpace(event.IsChecked()); } @@ -912,10 +898,7 @@ void frmQuery::OnShowLineEnds(wxCommandEvent &event) settings->WriteBool(wxT("frmQuery/ShowLineEnds"), viewMenu->IsChecked(MNU_SHOWLINEENDS)); - if (viewMenu->IsChecked(MNU_SHOWLINEENDS)) - sqlQuery->SetViewEOL(1); - else - sqlQuery->SetViewEOL(0); + SqlBookSetViewEOL(event.IsChecked()); } @@ -925,7 +908,7 @@ void frmQuery::OnShowLineNumber(wxCommandEvent &event) settings->WriteBool(wxT("frmQuery/ShowLineNumber"), viewMenu->IsChecked(MNU_SHOWLINENUMBER)); - sqlQuery->UpdateLineNumber(); + SqlBookSetViewLineNumbers(event.IsChecked()); } void frmQuery::OnActivate(wxActivateEvent &event) @@ -1080,6 +1063,7 @@ void frmQuery::OnChangeConnection(wxCommandEvent &ev) else { conn = (pgConn *)cbConnection->GetClientData(sel); + SqlBookSetDatabase(conn); sqlResult->SetConnection(conn); pgScript->SetConnection(conn); title = wxT("Query - ") + cbConnection->GetValue(); @@ -1421,17 +1405,18 @@ void frmQuery::OnRedo(wxCommandEvent &ev) void frmQuery::setExtendedTitle() { wxString chgStr; - if (changed) + if (sqlQuery->IsChanged()) chgStr = wxT(" *"); - if (lastPath.IsNull()) + wxString filename = sqlQuery->GetFilename(); + if (filename.IsNull()) SetTitle(title + chgStr); else { - SetTitle(title + wxT(" - [") + lastPath + wxT("]") + chgStr); + SetTitle(title + wxT(" - [") + filename + wxT("]") + chgStr); } // Allow to save initial queries though they are not changed - bool enableSave = changed || (origin == ORIGIN_INITIAL); + bool enableSave = sqlQuery->IsChanged() || (sqlQuery->GetOrigin() == ORIGIN_INITIAL); toolBar->EnableTool(MNU_SAVE, enableSave); fileMenu->Enable(MNU_SAVE, enableSave); } @@ -1668,11 +1653,12 @@ void frmQuery::OnSelectFavourite(wxCommandEvent &event) bool frmQuery::CheckChanged(bool canVeto) { - if (changed && settings->GetAskSaveConfirmation()) + if (sqlQuery->IsChanged() && settings->GetAskSaveConfirmation()) { - wxString fn; - if (!lastPath.IsNull()) - fn = wxString::Format(_("The text in file %s has changed.\nDo you want to save changes?"), lastPath.c_str()); + wxString fn, filename; + filename = sqlQuery->GetFilename(); + if (!filename.IsNull()) + fn = wxString::Format(_("The text in file %s has changed.\nDo you want to save changes?"), filename.c_str()); else fn = _("The text has changed.\nDo you want to save changes?"); wxMessageDialog msg(this, fn, _("Query"), @@ -1683,12 +1669,12 @@ bool frmQuery::CheckChanged(bool canVeto) switch (msg.ShowModal()) { case wxID_YES: - if (lastPath.IsNull()) + if (filename.IsNull()) OnSaveAs(noEvent); else OnSave(noEvent); - return changed; + return sqlQuery->IsChanged(); case wxID_CANCEL: return true; @@ -1732,7 +1718,7 @@ void frmQuery::OnClose(wxCloseEvent &event) return; } - if (CheckChanged(event.CanVeto()) && event.CanVeto()) + if (SqlBookClose(event.CanVeto()) && event.CanVeto()) { event.Veto(); return; @@ -1749,7 +1735,6 @@ void frmQuery::OnClose(wxCloseEvent &event) Hide(); - sqlQuery->Disconnect(wxID_ANY, wxEVT_SET_FOCUS, wxFocusEventHandler(frmQuery::OnFocus)); sqlResult->Disconnect(wxID_ANY, wxEVT_SET_FOCUS, wxFocusEventHandler(frmQuery::OnFocus)); msgResult->Disconnect(wxID_ANY, wxEVT_SET_FOCUS, wxFocusEventHandler(frmQuery::OnFocus)); msgHistory->Disconnect(wxID_ANY, wxEVT_SET_FOCUS, wxFocusEventHandler(frmQuery::OnFocus)); @@ -1776,15 +1761,16 @@ void frmQuery::OnChangeStc(wxStyledTextEvent &event) // Sometimes there come events 20 and 520 AFTER the initial query was set by constructor. // Their occurrence is related to query's size and possibly international characters in it (??) // Filter them out to keep "initial" origin of query text. - (origin != ORIGIN_INITIAL || (event.m_modificationType != 20 && event.m_modificationType != 520))) + (sqlQuery->GetOrigin() != ORIGIN_INITIAL || (event.m_modificationType != 20 && event.m_modificationType != 520))) { // This is the default change origin. // In other cases the changer function will reset it after this event. - origin = ORIGIN_MANUAL; - if (!changed) + sqlQuery->SetOrigin(ORIGIN_MANUAL); + if (!sqlQuery->IsChanged()) { - changed = true; + sqlQuery->SetChanged(true); setExtendedTitle(); + SqlBookUpdatePageTitle(); } } // do not allow update of model size of GQB on input (key press) of each @@ -1826,9 +1812,11 @@ void frmQuery::OpenLastFile() sqlQuery->Colourise(0, str.Length()); sqlQuery->EmptyUndoBuffer(); wxSafeYield(); // needed to process sqlQuery modify event - changed = false; - origin = ORIGIN_FILE; + sqlQuery->SetFilename(lastPath); + sqlQuery->SetChanged(false); + sqlQuery->SetOrigin(ORIGIN_FILE); setExtendedTitle(); + SqlBookUpdatePageTitle(); SetLineEndingStyle(); UpdateRecentFiles(true); if(mainForm != NULL) @@ -1881,26 +1869,28 @@ void frmQuery::OnOpen(wxCommandEvent &event) void frmQuery::OnSave(wxCommandEvent &event) { bool modeUnicode = settings->GetUnicodeFile(); + wxString filename = sqlQuery->GetFilename(); - if (lastPath.IsNull()) + if (filename.IsNull()) { OnSaveAs(event); return; } - wxUtfFile file(lastPath, wxFile::write, modeUnicode ? wxFONTENCODING_UTF8 : wxFONTENCODING_DEFAULT); + wxUtfFile file(filename, wxFile::write, modeUnicode ? wxFONTENCODING_UTF8 : wxFONTENCODING_DEFAULT); if (file.IsOpened()) { if ((file.Write(sqlQuery->GetText()) == 0) && (!modeUnicode)) wxMessageBox(_("Query text incomplete.\nQuery contained characters that could not be converted to the local charset.\nPlease correct the data or try using UTF8 instead.")); file.Close(); - changed = false; + sqlQuery->SetChanged(false); setExtendedTitle(); UpdateRecentFiles(); + SqlBookUpdatePageTitle(); } else { - wxLogError(__("Could not write the file %s: Errcode=%d."), lastPath.c_str(), wxSysErrorCode()); + wxLogError(__("Could not write the file %s: Errcode=%d."), filename.c_str(), wxSysErrorCode()); } } @@ -1924,7 +1914,7 @@ void frmQuery::SetLineEndingStyle() { wxMessageBox(_("This file contains mixed line endings. They will be converted to the current setting."), _("Warning"), wxICON_INFORMATION | wxOK); sqlQuery->ConvertEOLs(mode); - changed = true; + sqlQuery->SetChanged(true); setExtendedTitle(); updateMenu(); } @@ -1940,27 +1930,7 @@ void frmQuery::SetLineEndingStyle() // Now set the status text, menu options, and the mode sqlQuery->SetEOLMode(mode); - switch(mode) - { - - case wxSTC_EOL_LF: - lineEndMenu->Check(MNU_LF, true); - SetStatusText(_("Unix"), STATUSPOS_FORMAT); - break; - - case wxSTC_EOL_CRLF: - lineEndMenu->Check(MNU_CRLF, true); - SetStatusText(_("DOS"), STATUSPOS_FORMAT); - break; - - case wxSTC_EOL_CR: - lineEndMenu->Check(MNU_CR, true); - SetStatusText(_("Mac"), STATUSPOS_FORMAT); - break; - - default: - wxLogError(wxT("Someone created a new line ending style! Run, run for your lives!!")); - } + SetEOLModeDisplay(mode); delete reCRLF; delete reCR; @@ -1992,12 +1962,14 @@ void frmQuery::OnSetEOLMode(wxCommandEvent &event) SetEOLModeDisplay(mode); - if (!changed) + if (!sqlQuery->IsChanged()) { - changed = true; + sqlQuery->SetChanged(true); setExtendedTitle(); } + setExtendedTitle(); + // TODO: Figure out why this is here? pgScript->SetConnection(conn); } @@ -2031,11 +2003,16 @@ void frmQuery::SetEOLModeDisplay(int mode) void frmQuery::OnSaveAs(wxCommandEvent &event) { + wxString filename; + filename = sqlQuery->GetFilename(); + if (filename.IsNull()) + filename = lastFilename; + #ifdef __WXMSW__ - wxFileDialog *dlg = new wxFileDialog(this, _("Save query file as"), lastDir, lastFilename, + wxFileDialog *dlg = new wxFileDialog(this, _("Save query file as"), lastDir, filename, /*lastFilename,*/ _("Query files (*.sql)|*.sql|All files (*.*)|*.*"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); #else - wxFileDialog *dlg = new wxFileDialog(this, _("Save query file as"), lastDir, lastFilename, + wxFileDialog *dlg = new wxFileDialog(this, _("Save query file as"), lastDir, filename, /*lastFilename,*/ _("Query files (*.sql)|*.sql|All files (*)|*"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); #endif if (dlg->ShowModal() == wxID_OK) @@ -2069,14 +2046,17 @@ void frmQuery::OnSaveAs(wxCommandEvent &event) if ((file.Write(sqlQuery->GetText()) == 0) && (!lastFileFormat)) wxMessageBox(_("Query text incomplete.\nQuery contained characters that could not be converted to the local charset.\nPlease correct the data or try using UTF8 instead.")); file.Close(); - changed = false; + sqlQuery->SetChanged(false); + sqlQuery->SetFilename(lastPath); // Forget about Initial origin thus making "Save" button behave as usual // (be enabled/disabled according to dirty flag only). - if (origin == ORIGIN_INITIAL) - origin = ORIGIN_FILE; + if (sqlQuery->GetOrigin() == ORIGIN_INITIAL) + sqlQuery->SetOrigin(ORIGIN_FILE); setExtendedTitle(); + SqlBookUpdatePageTitle(); + SetOutputPaneCaption(true); UpdateRecentFiles(); fileMenu->Enable(MNU_RECENT, (recentFileMenu->GetMenuItemCount() > 0)); } @@ -2257,7 +2237,7 @@ bool frmQuery::updateFromGqb(bool executing) // Only prompt the user if the dirty flag is set, and last modification wasn't from GQB, // and the textbox is not empty, and the new query is different. - if(changed && origin != ORIGIN_GQB && + if (sqlQuery->IsChanged() && sqlQuery->GetOrigin() != ORIGIN_GQB && !sqlQuery->GetText().Trim().IsEmpty() && sqlQuery->GetText() != newQuery + wxT("\n")) { wxString fn; @@ -2267,7 +2247,7 @@ bool frmQuery::updateFromGqb(bool executing) fn = _("The generated SQL query has changed.\nDo you want to update it?"); wxMessageDialog msg(this, fn, _("Query"), wxYES_NO | wxICON_EXCLAMATION); - if(msg.ShowModal() == wxID_YES && changed) + if(msg.ShowModal() == wxID_YES && sqlQuery->IsChanged()) { canGenerate = true; } @@ -2287,8 +2267,8 @@ bool frmQuery::updateFromGqb(bool executing) sqlQuery->Colourise(0, sqlQuery->GetText().Length()); wxSafeYield(); // needed to process sqlQuery modify event sqlNotebook->SetSelection(0); - changed = true; - origin = ORIGIN_GQB; + sqlQuery->SetChanged(true); + sqlQuery->SetOrigin(ORIGIN_GQB); setExtendedTitle(); gqbUpdateRunning = false; @@ -2437,6 +2417,7 @@ void frmQuery::OnMacroInvoke(wxCommandEvent &event) } query.Replace(wxT("$SELECTION$"), selection); } + execQuery(query); sqlQuery->SetFocus(); } @@ -2495,7 +2476,7 @@ void frmQuery::execQuery(const wxString &query, int resultToRetrieve, bool singl sqlQuery->StartStyling(0, wxSTC_INDICS_MASK); sqlQuery->SetStyling(sqlQuery->GetText().Length(), 0); - if (!changed) + if (!sqlQuery->IsChanged()) setExtendedTitle(); aborted = false; @@ -2519,6 +2500,12 @@ void frmQuery::execQuery(const wxString &query, int resultToRetrieve, bool singl } } + // Remember the tab from which execute was called. By the time query completes, SQL tab selection may change. + sqlQueryExec = sqlQuery; + // Because the output pane is clear during query execution, we clear the result source title on Output Pane. + sqlQueryExecLast = NULL; + SetOutputPaneCaption(true); + // We must do this lot before the query starts, otherwise // it might not happen once the main thread gets busy with // other stuff. @@ -2528,7 +2515,7 @@ void frmQuery::execQuery(const wxString &query, int resultToRetrieve, bool singl msgResult->Clear(); msgResult->SetFont(settings->GetSQLFont()); - msgHistory->AppendText(_("-- Executing query:\n")); + msgHistory->AppendText(wxString::Format(_("-- Executing query [%s]:\n"), sqlQueryExec->GetTitle(false).c_str())); msgHistory->AppendText(query); msgHistory->AppendText(wxT("\n")); Update(); @@ -2852,9 +2839,9 @@ void frmQuery::OnQueryComplete(pgQueryResultEvent &ev) if (!errMsg2.IsEmpty()) showMessage(errMsg2); - if (errPos > 0) + if (errPos > 0 && sqlQueryExec != NULL) { - int selStart = sqlQuery->GetSelectionStart(), selEnd = sqlQuery->GetSelectionEnd(); + int selStart = sqlQueryExec->GetSelectionStart(), selEnd = sqlQueryExec->GetSelectionEnd(); if (selStart == selEnd) selStart = 0; @@ -2862,29 +2849,29 @@ void frmQuery::OnQueryComplete(pgQueryResultEvent &ev) // Set an indicator on the error word (break on any kind of bracket, a space or full stop) int sPos = errPos + selStart - 1, wEnd = 1; - sqlQuery->StartStyling(sPos, wxSTC_INDICS_MASK); - int c = sqlQuery->GetCharAt(sPos + wEnd); - size_t len = sqlQuery->GetText().Length(); + sqlQueryExec->StartStyling(sPos, wxSTC_INDICS_MASK); + int c = sqlQueryExec->GetCharAt(sPos + wEnd); + size_t len = sqlQueryExec->GetText().Length(); while(c != ' ' && c != '(' && c != '{' && c != '[' && c != '.' && (unsigned int)(sPos + wEnd) < len) { wEnd++; - c = sqlQuery->GetCharAt(sPos + wEnd); + c = sqlQueryExec->GetCharAt(sPos + wEnd); } - sqlQuery->SetStyling(wEnd, wxSTC_INDIC0_MASK); + sqlQueryExec->SetStyling(wEnd, wxSTC_INDIC0_MASK); - int line = 0, maxLine = sqlQuery->GetLineCount(); - while (line < maxLine && sqlQuery->GetLineEndPosition(line) < errPos + selStart + 1) + int line = 0, maxLine = sqlQueryExec->GetLineCount(); + while (line < maxLine && sqlQueryExec->GetLineEndPosition(line) < errPos + selStart + 1) line++; if (line < maxLine) { - sqlQuery->GotoPos(sPos); - sqlQuery->MarkerAdd(line, 0); + sqlQueryExec->GotoPos(sPos); + sqlQueryExec->MarkerAdd(line, 0); - if (!changed) + if (!sqlQueryExec->IsChanged()) setExtendedTitle(); - sqlQuery->EnsureVisible(line); + sqlQueryExec->EnsureVisible(line); } } } @@ -2981,9 +2968,9 @@ void frmQuery::OnQueryComplete(pgQueryResultEvent &ev) if (sqlResult->RunStatus() == PGRES_TUPLES_OK || sqlResult->RunStatus() == PGRES_COMMAND_OK) { // Get the executed query - wxString executedQuery = sqlQuery->GetSelectedText(); + wxString executedQuery = sqlQueryExec->GetSelectedText(); if (executedQuery.IsNull()) - executedQuery = sqlQuery->GetText(); + executedQuery = sqlQueryExec->GetText(); // Same query, but without return feeds and carriage returns wxString executedQueryWithoutReturns = executedQuery; @@ -3176,6 +3163,11 @@ void frmQuery::completeQuery(bool done, bool explain, bool verbose) updateMenu(); } + // Change the output pane caption so the user knows which tab the result came from + sqlQueryExecLast = sqlQueryExec; + SetOutputPaneCaption(true); + + sqlQueryExec = NULL; sqlQuery->SetFocus(); } @@ -3478,8 +3470,8 @@ void frmQuery::OnChangeQuery(wxCommandEvent &event) sqlQuery->SetText(query); sqlQuery->Colourise(0, query.Length()); wxSafeYield(); // needed to process sqlQuery modify event - changed = true; - origin = ORIGIN_HISTORY; + sqlQuery->SetChanged(true); + sqlQuery->SetOrigin(ORIGIN_HISTORY); setExtendedTitle(); SetLineEndingStyle(); btnDeleteCurrent->Enable(true); @@ -3523,6 +3515,332 @@ void frmQuery::OnDeleteAll(wxCommandEvent &event) } } +void frmQuery::BeginPerspectiveChange() +{ + manager.GetPane(_("outputPane")).Caption(_("Output pane")); +} + +void frmQuery::EndPerspectiveChange(bool update) +{ + SetOutputPaneCaption(update); +} + +void frmQuery::SetOutputPaneCaption(bool update) +{ + wxString caption; + wxString title; + + if (sqlQueryExecLast == NULL) + caption = _("Output pane"); + else + { + // We don't want to make it look like Output Pane has been changed, + // so request the title without the change indicator + title = sqlQueryExecLast->GetTitle(false); + caption = wxString::Format(_("Output pane [%s]"), title.c_str()); + } + + manager.GetPane(wxT("outputPane")).Caption(caption); + + if (update) + manager.Update(); +} + +// Methods related to SQL tabs // + +void frmQuery::OnSqlBookPageChanged(wxAuiNotebookEvent &event) +{ + // Try to always keep sqlQuery variable pointing to the currently selected SQLBox. + // When closing and removing all tabs, page count may be zero. + if (sqlQueryBook->GetPageCount() > 0) + { + size_t curpage = sqlQueryBook->GetSelection(); + sqlQuery = wxDynamicCast(sqlQueryBook->GetPage(curpage), ctlSQLBox); + if (sqlQuery != NULL) + { + // Update UI with chosen query's info + SetEOLModeDisplay(sqlQuery->GetEOLMode()); + setExtendedTitle(); + + sqlQuery->SetFocus(); + } + } + else + { + // This should help us find bugs (such as using sqlQuery after closing all tabs) much faster. + sqlQuery = NULL; + } +} + +void frmQuery::OnSqlBookPageChanging(wxAuiNotebookEvent &event) +{ + // Veto event while page change is prohibited. + if (!SqlBookCanChangePage()) + { + event.Veto(); + wxMessageBox(_("Cannot change to selected SQL tab now. Try again a bit later.")); + } +} + +void frmQuery::OnSqlBookAddPage(wxCommandEvent &event) +{ + if (SqlBookCanChangePage()) + SqlBookAddPage(); + else + wxMessageBox(_("Cannot add a new SQL tab now. Try again a bit later.")); +} + +void frmQuery::OnSqlBookPageClose(wxAuiNotebookEvent &event) +{ + // Don't allow removal of the last SQL box via user generated event + size_t pagecnt = sqlQueryBook->GetPageCount(); + if (pagecnt == 1) + { + event.Veto(); + wxMessageBox(_("Cannot remove the last SQL tab")); + return; + } + + // Prevent removing the page that is currently being executed + if (sqlQueryExec != NULL && sqlQueryExec == sqlQuery) + { + event.Veto(); + wxMessageBox(_("The query on this SQL tab is still running.\nWait for it to finish or cancel it before closing the tab.")); + return; + } + + if (CheckChanged(true)) + { + event.Veto(); + return; + } + + // If removing the tab for which results are displayed, reset the output pane's caption + if (sqlQuery == sqlQueryExecLast) + { + sqlQueryExecLast = NULL; + SetOutputPaneCaption(true); + } + + SqlBookDisconnectPage(); +} + +bool frmQuery::SqlBookCanChangePage() +{ + return !(m_loadingfile || ms_pgScriptRunning); +} + +void frmQuery::SqlBookAddPage() +{ + ctlSQLBox *box; + wxString caption; + bool bVal; + + // All SQL boxes use the same wxID, CTL_SQLQUERY. + // This should probably be changed, but it works for now and has minimal impact on existing code. + box = new ctlSQLBox(sqlQueryBook, CTL_SQLQUERY, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxSIMPLE_BORDER | wxTE_RICH2); + box->SetDatabase(conn); + box->SetMarginWidth(1, 16); + box->SetDropTarget(new DnDFile(this)); + box->SetChanged(false); + box->SetOrigin(ORIGIN_MANUAL); + + bVal = editMenu->IsChecked(MNU_AUTOINDENT); + box->SetAutoIndent(bVal); + + bVal = viewMenu->IsChecked(MNU_WORDWRAP); + box->SetWrapMode(bVal ? wxSTC_WRAP_WORD : wxSTC_WRAP_NONE); + + bVal = viewMenu->IsChecked(MNU_SHOWINDENTGUIDES); + box->SetIndentationGuides(bVal); + + bVal = viewMenu->IsChecked(MNU_SHOWWHITESPACE); + box->SetViewWhiteSpace(bVal ? wxSTC_WS_VISIBLEALWAYS : wxSTC_WS_INVISIBLE); + + bVal = viewMenu->IsChecked(MNU_SHOWLINEENDS); + box->SetViewEOL(bVal ? 1 : 0); + + box->Connect(wxID_ANY, wxEVT_SET_FOCUS, wxFocusEventHandler(frmQuery::OnFocus)); + box->Connect(wxID_ANY, wxEVT_KILL_FOCUS, wxFocusEventHandler(frmQuery::OnFocus)); + + sqlQueryCounter ++; + caption = wxString::Format(_("Query %i"), sqlQueryCounter); + box->SetTitle(caption); + sqlQueryBook->AddPage(box, caption, true); + + // Probably not needed, as the line above should trigger the PageChange event + sqlQuery = box; +} + +void frmQuery::SqlBookDisconnectPage(ctlSQLBox *box) +{ + if (box == NULL) + box = sqlQuery; + + if (box != NULL) + { + box->Disconnect(wxID_ANY, wxEVT_SET_FOCUS, wxFocusEventHandler(frmQuery::OnFocus)); + box->Disconnect(wxID_ANY, wxEVT_KILL_FOCUS, wxFocusEventHandler(frmQuery::OnFocus)); + } +} + +bool frmQuery::SqlBookRemovePage() +{ + size_t pageidx; + if (sqlQueryBook->GetPageCount() > 0) + { + // If removing the tab for which results are displayed, reset the output pane's caption + if (sqlQuery == sqlQueryExecLast) + { + sqlQueryExecLast = NULL; + SetOutputPaneCaption(true); + } + + SqlBookDisconnectPage(); + pageidx = sqlQueryBook->GetSelection(); + return sqlQueryBook->DeletePage(pageidx); + } + return false; +} + +void frmQuery::SqlBookSetAutoIndent(bool b) +{ + size_t i, cnt; + ctlSQLBox *box; + + for (i = 0, cnt = sqlQueryBook->GetPageCount(); i < cnt; ++i) + { + box = wxDynamicCast(sqlQueryBook->GetPage(i), ctlSQLBox); + if (box != NULL) + box->SetAutoIndent(b); + } +} + +void frmQuery::SqlBookSetWrapMode(bool b) +{ + size_t i, cnt; + ctlSQLBox *box; + + for (i = 0, cnt = sqlQueryBook->GetPageCount(); i < cnt; ++i) + { + box = wxDynamicCast(sqlQueryBook->GetPage(i), ctlSQLBox); + if (box != NULL) + box->SetWrapMode(b ? wxSTC_WRAP_WORD : wxSTC_WRAP_NONE); + } +} + +void frmQuery::SqlBookSetIndentGuides(bool b) +{ + size_t i, cnt; + ctlSQLBox *box; + + for (i = 0, cnt = sqlQueryBook->GetPageCount(); i < cnt; ++i) + { + box = wxDynamicCast(sqlQueryBook->GetPage(i), ctlSQLBox); + if (box != NULL) + box->SetIndentationGuides(b); + } +} + +void frmQuery::SqlBookSetViewWhiteSpace(bool b) +{ + size_t i, cnt; + ctlSQLBox *box; + + for (i = 0, cnt = sqlQueryBook->GetPageCount(); i < cnt; ++i) + { + box = wxDynamicCast(sqlQueryBook->GetPage(i), ctlSQLBox); + if (box != NULL) + box->SetViewWhiteSpace(b ? wxSTC_WS_VISIBLEALWAYS : wxSTC_WS_INVISIBLE); + } +} + +void frmQuery::SqlBookSetViewEOL(bool b) +{ + size_t i, cnt; + ctlSQLBox *box; + + for (i = 0, cnt = sqlQueryBook->GetPageCount(); i < cnt; ++i) + { + box = wxDynamicCast(sqlQueryBook->GetPage(i), ctlSQLBox); + if (box != NULL) + box->SetViewEOL(b ? 1 : 0); + } +} + +void frmQuery::SqlBookSetViewLineNumbers(bool b) +{ + size_t i, cnt; + ctlSQLBox *box; + + for (i = 0, cnt = sqlQueryBook->GetPageCount(); i < cnt; ++i) + { + box = wxDynamicCast(sqlQueryBook->GetPage(i), ctlSQLBox); + if (box != NULL) + box->UpdateLineNumber(); + } +} + +void frmQuery::SqlBookSetDatabase(pgConn *con) +{ + size_t i, cnt; + ctlSQLBox *box; + + for (i = 0, cnt = sqlQueryBook->GetPageCount(); i < cnt; ++i) + { + box = wxDynamicCast(sqlQueryBook->GetPage(i), ctlSQLBox); + if (box != NULL) + box->SetDatabase(con); + } +} + +void frmQuery::SqlBookUpdatePageTitle() +{ + size_t index; + wxString title; + + if (sqlQueryBook->GetPageCount() > 0 && sqlQuery != NULL) + { + index = sqlQueryBook->GetPageIndex(sqlQuery); + if (index == wxNOT_FOUND) + return; + + title = sqlQuery->GetTitle(); + if (sqlQueryBook->GetPageText(index) != title) + sqlQueryBook->SetPageText(index, title); + } +} + +// Returns true if any SQL tab attempts to veto the closing, false otherwise. +bool frmQuery::SqlBookClose(bool canVeto) +{ + size_t i = 0; + size_t cnt = sqlQueryBook->GetPageCount(); + ctlSQLBox *box; + + // See if we have any unsaved changes and prompt to save them + for (i = 0; i < cnt; ++i) + { + box = wxDynamicCast(sqlQueryBook->GetPage(i), ctlSQLBox); + if (box != NULL && box->IsChanged()) + { + sqlQueryBook->SetSelection(i); + if (CheckChanged(canVeto)) + return true; + } + } + + // If we got here, the window will be closed + for (i = 0; i < cnt; ++i) + { + box = wxDynamicCast(sqlQueryBook->GetPage(i), ctlSQLBox); + if (box != NULL) + SqlBookDisconnectPage(box); + } + + return false; +} + /////////////////////////////////////////////////////// diff --git a/pgadmin/include/ctl/ctlSQLBox.h b/pgadmin/include/ctl/ctlSQLBox.h index bb26442..e635de4 100644 --- a/pgadmin/include/ctl/ctlSQLBox.h +++ b/pgadmin/include/ctl/ctlSQLBox.h @@ -74,11 +74,23 @@ public: CharacterRange RegexFindText(int minPos, int maxPos, const wxString &text); + // Having multiple SQL tabs warrants the following properties to be tracked per tab + void SetChanged(bool b); + bool IsChanged(); + void SetOrigin(int origin); + int GetOrigin(); + void SetFilename(wxString &filename); + wxString GetFilename(); + void SetTitle(wxString &title); + wxString GetTitle(bool withChangeInd = true); + wxString GetChangeIndicator(); + DECLARE_DYNAMIC_CLASS(ctlSQLBox) DECLARE_EVENT_TABLE() protected: void OnEndProcess(wxProcessEvent &ev); + void UpdateTitle(); sysProcess *process; long processID; @@ -93,6 +105,13 @@ private: pgConn *m_database; bool m_autoIndent, m_autocompDisabled; + // Variables to track info per SQL box + wxString m_filename; + wxString m_title; + wxString m_changestr; + bool m_changed; + int m_origin; + friend class QueryPrintout; }; diff --git a/pgadmin/include/frm/frmQuery.h b/pgadmin/include/frm/frmQuery.h index 63213d9..e23c0cb 100644 --- a/pgadmin/include/frm/frmQuery.h +++ b/pgadmin/include/frm/frmQuery.h @@ -101,11 +101,11 @@ public: } void SetChanged(bool p_changed) { - changed = p_changed; + sqlQuery->SetChanged(p_changed); } void SetOrigin(int p_origin) { - origin = p_origin; + sqlQuery->SetOrigin(p_origin); } void SetLastPath(wxString p_lastpath) { @@ -135,6 +135,11 @@ private: wxButton *btnDeleteAll; wxArrayString histoQueries; + ctlAuiNotebook *sqlQueryBook; //container for all SQL tabs + size_t sqlQueryCounter; //for initial tab names + ctlSQLBox *sqlQueryExec; //currently executing SQL tab + ctlSQLBox *sqlQueryExecLast; //output pane shows results for this SQL tab + // Query timing/status update wxTimer timer; wxLongLong elapsedQuery, startTimeQuery; @@ -261,6 +266,30 @@ private: bool relatesToWindow(wxWindow *which, wxWindow *related); + // Methods related to SQL tabs + void SqlBookAddPage(); + bool SqlBookRemovePage(); + bool SqlBookCanChangePage(); + void SqlBookSetAutoIndent(bool b); + void SqlBookSetWrapMode(bool b); + void SqlBookSetIndentGuides(bool b); + void SqlBookSetViewWhiteSpace(bool b); + void SqlBookSetViewEOL(bool b); + void SqlBookSetViewLineNumbers(bool b); + void SqlBookSetDatabase(pgConn *con); + void SqlBookUpdatePageTitle(); + void SqlBookDisconnectPage(ctlSQLBox *box = NULL); + bool SqlBookClose(bool canVeto); + // SQL tabs event handlers + void OnSqlBookAddPage(wxCommandEvent &event); + void OnSqlBookPageClose(wxAuiNotebookEvent &event); + void OnSqlBookPageChanged(wxAuiNotebookEvent &event); + void OnSqlBookPageChanging(wxAuiNotebookEvent &event); + + void BeginPerspectiveChange(); + void EndPerspectiveChange(bool update = false); + void SetOutputPaneCaption(bool update = false); + wxWindow *currentControl(); wxMenu *queryMenu; wxMenu *favouritesMenu; @@ -279,9 +308,6 @@ private: bool lastFileFormat; bool m_loadingfile; - // Complements dirty flag, showing last origin of query's modification (see enum ORIGIN_..) - int origin; - // A simple mutex-like flag to prevent concurrent script execution. // Required because the pgScript parser isn't currently thread-safe :-( static bool ms_pgScriptRunning; diff --git a/pgadmin/include/frm/menu.h b/pgadmin/include/frm/menu.h index 9b22490..f8c5162 100644 --- a/pgadmin/include/frm/menu.h +++ b/pgadmin/include/frm/menu.h @@ -44,6 +44,7 @@ enum MNU_OPEN, MNU_SAVE, MNU_SAVEAS, + MNU_NEWSQLTAB, MNU_EXPORT, MNU_OPTIONS, MNU_CUT,