#include "editorwindow.h" #include "ui_editorwindow.h" #include "uiutils.h" #include "datagrid/sqlquerymodel.h" #include "iconmanager.h" #include "dblistmodel.h" #include "services/notifymanager.h" #include "dbtree/dbtree.h" #include "datagrid/sqlqueryitem.h" #include "datagrid/sqlqueryview.h" #include "mainwindow.h" #include "mdiarea.h" #include "common/unused.h" #include "common/extaction.h" #include "uiconfig.h" #include "services/config.h" #include "parser/lexer.h" #include "common/utils_sql.h" #include "parser/parser.h" #include "dbobjectdialogs.h" #include "dialogs/exportdialog.h" #include "themetuner.h" #include "dialogs/bindparamsdialog.h" #include "common/bindparam.h" #include "common/dbcombobox.h" #include #include #include #include #include CFG_KEYS_DEFINE(EditorWindow) EditorWindow::ResultsDisplayMode EditorWindow::resultsDisplayMode; QHash EditorWindow::staticActions; QHash EditorWindow::staticActionGroups; EditorWindow::EditorWindow(QWidget *parent) : MdiChild(parent), ui(new Ui::EditorWindow) { ui->setupUi(this); init(); } EditorWindow::EditorWindow(const EditorWindow& editor) : MdiChild(editor.parentWidget()), ui(new Ui::EditorWindow) { ui->setupUi(this); init(); ui->sqlEdit->setAutoCompletion(false); ui->sqlEdit->setPlainText(editor.ui->sqlEdit->toPlainText()); ui->sqlEdit->setAutoCompletion(true); } EditorWindow::~EditorWindow() { delete ui; } void EditorWindow::staticInit() { qRegisterMetaType("EditorWindow"); resultsDisplayMode = ResultsDisplayMode::BELOW_QUERY; loadTabsMode(); createStaticActions(); } void EditorWindow::insertAction(ExtActionPrototype* action, EditorWindow::ToolBar toolbar) { return ExtActionContainer::insertAction(action, toolbar); } void EditorWindow::insertActionBefore(ExtActionPrototype* action, EditorWindow::Action beforeAction, EditorWindow::ToolBar toolbar) { return ExtActionContainer::insertActionBefore(action, beforeAction, toolbar); } void EditorWindow::insertActionAfter(ExtActionPrototype* action, EditorWindow::Action afterAction, EditorWindow::ToolBar toolbar) { return ExtActionContainer::insertActionAfter(action, afterAction, toolbar); } void EditorWindow::removeAction(ExtActionPrototype* action, EditorWindow::ToolBar toolbar) { ExtActionContainer::removeAction(action, toolbar); } void EditorWindow::init() { setFocusProxy(ui->sqlEdit); updateResultsDisplayMode(); THEME_TUNER->manageCompactLayout({ ui->query, ui->results, ui->history }); resultsModel = new SqlQueryModel(this); ui->dataView->init(resultsModel); createDbCombo(); initActions(); updateShortcutTips(); setupSqlHistoryMenu(); Db* treeSelectedDb = DBTREE->getSelectedOpenDb(); if (treeSelectedDb) dbCombo->setCurrentDb(treeSelectedDb); Db* currentDb = getCurrentDb(); resultsModel->setDb(currentDb); ui->sqlEdit->setDb(currentDb); connect(CFG_UI.General.SqlEditorCurrQueryHighlight, SIGNAL(changed(QVariant)), this, SLOT(queryHighlightingConfigChanged(QVariant))); if (CFG_UI.General.SqlEditorCurrQueryHighlight.get()) ui->sqlEdit->setCurrentQueryHighlighting(true); connect(ui->sqlEdit, SIGNAL(textChanged()), this, SLOT(checkTextChangedForSession())); connect(resultsModel, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful())); connect(resultsModel, SIGNAL(executionFailed(QString)), this, SLOT(executionFailed(QString))); connect(resultsModel, SIGNAL(storeExecutionInHistory()), this, SLOT(storeExecutionInHistory())); // SQL history list ui->historyList->setModel(CFG->getSqlHistoryModel()); ui->historyList->hideColumn(0); ui->historyList->resizeColumnToContents(1); connect(ui->historyList->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), this, SLOT(historyEntrySelected(QModelIndex,QModelIndex))); connect(ui->historyList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(historyEntryActivated(QModelIndex))); connect(ui->historyList, &QWidget::customContextMenuRequested, this, &EditorWindow::sqlHistoryContextMenuRequested); updateState(); } void EditorWindow::loadTabsMode() { QString tabsString = CFG_UI.General.SqlEditorTabs.get(); if (tabsString == "SEPARATE_TAB") resultsDisplayMode = ResultsDisplayMode::SEPARATE_TAB; else if (tabsString == "BELOW_QUERY") resultsDisplayMode = ResultsDisplayMode::BELOW_QUERY; } void EditorWindow::createStaticActions() { staticActions[RESULTS_IN_TAB] = new ExtAction(ICONS.RESULTS_IN_TAB, tr("Results in the separate tab"), MainWindow::getInstance()); staticActions[RESULTS_BELOW] = new ExtAction(ICONS.RESULTS_BELOW, tr("Results below the query"), MainWindow::getInstance()); staticActionGroups[ActionGroup::RESULTS_POSITIONING] = new QActionGroup(MainWindow::getInstance()); staticActionGroups[ActionGroup::RESULTS_POSITIONING]->addAction(staticActions[RESULTS_IN_TAB]); staticActionGroups[ActionGroup::RESULTS_POSITIONING]->addAction(staticActions[RESULTS_BELOW]); connect(staticActions[RESULTS_BELOW], &QAction::triggered, [=]() { resultsDisplayMode = ResultsDisplayMode::BELOW_QUERY; CFG_UI.General.SqlEditorTabs.set("BELOW_QUERY"); }); connect(staticActions[RESULTS_IN_TAB], &QAction::triggered, [=]() { resultsDisplayMode = ResultsDisplayMode::SEPARATE_TAB; CFG_UI.General.SqlEditorTabs.set("SEPARATE_TAB"); }); staticActions[RESULTS_BELOW]->setCheckable(true); staticActions[RESULTS_IN_TAB]->setCheckable(true); if (resultsDisplayMode == ResultsDisplayMode::BELOW_QUERY) staticActions[RESULTS_BELOW]->setChecked(true); else staticActions[RESULTS_IN_TAB]->setChecked(true); } Icon* EditorWindow::getIconNameForMdiWindow() { return ICONS.OPEN_SQL_EDITOR; } QString EditorWindow::getTitleForMdiWindow() { QStringList existingNames = MainWindow::getInstance()->getMdiArea()->getWindowTitles(); QString title = tr("SQL editor %1").arg(sqlEditorNum++); while (existingNames.contains(title)) title = tr("SQL editor %1").arg(sqlEditorNum++); return title; } QSize EditorWindow::sizeHint() const { return QSize(500, 400); } QAction* EditorWindow::getAction(EditorWindow::Action action) { switch (action) { case RESULTS_BELOW: case RESULTS_IN_TAB: { if (!staticActions.contains(action)) return nullptr; return staticActions.value(action); } default: break; } return ExtActionContainer::getAction(action); } QString EditorWindow::getQueryToExecute(bool doSelectCurrentQuery, QueryExecMode querySelectionMode) { QString sql; if (ui->sqlEdit->textCursor().hasSelection()) { sql = ui->sqlEdit->textCursor().selectedText(); fixTextCursorSelectedText(sql); } else if (querySelectionMode == ALL) { sql = ui->sqlEdit->toPlainText(); } else if (CFG_UI.General.ExecuteCurrentQueryOnly.get() || querySelectionMode == SINGLE) { ui->sqlEdit->saveSelection(); selectCurrentQuery(true); sql = ui->sqlEdit->textCursor().selectedText(); fixTextCursorSelectedText(sql); if (!doSelectCurrentQuery) ui->sqlEdit->restoreSelection(); } else { sql = ui->sqlEdit->toPlainText(); } return sql; } bool EditorWindow::setCurrentDb(Db *db) { dbCombo->setCurrentDb(db); return dbCombo->currentIndex() > -1; } void EditorWindow::setContents(const QString &sql) { settingSqlContents = true; ui->sqlEdit->setPlainText(sql); settingSqlContents = false; } QString EditorWindow::getContents() const { return ui->sqlEdit->toPlainText(); } void EditorWindow::execute() { execQuery(); } QToolBar* EditorWindow::getToolBar(int toolbar) const { UNUSED(toolbar); return ui->toolBar; } SqlEditor* EditorWindow::getEditor() const { return ui->sqlEdit; } QVariant EditorWindow::saveSession() { QHash sessionValue; sessionValue["query"] = ui->sqlEdit->toPlainText(); sessionValue["curPos"] = ui->sqlEdit->textCursor().position(); Db* db = getCurrentDb(); if (db) sessionValue["db"] = db->getName(); return sessionValue; } bool EditorWindow::restoreSession(const QVariant& sessionValue) { QHash value = sessionValue.toHash(); if (value.size() == 0) return true; if (value.contains("query")) { ui->sqlEdit->setAutoCompletion(false); ui->sqlEdit->setPlainText(value["query"].toString()); ui->sqlEdit->setAutoCompletion(true); } if (value.contains("curPos")) { QTextCursor cursor = ui->sqlEdit->textCursor(); cursor.setPosition(value["curPos"].toInt()); ui->sqlEdit->setTextCursor(cursor); } if (value.contains("db")) { dbCombo->setCurrentText(value["db"].toString()); if (dbCombo->currentText().isEmpty() && dbCombo->count() > 0) dbCombo->setCurrentIndex(0); } return true; } void EditorWindow::changeEvent(QEvent *e) { QWidget::changeEvent(e); switch (e->type()) { case QEvent::LanguageChange: ui->retranslateUi(this); break; default: break; } } Db* EditorWindow::getCurrentDb() { return dbCombo->currentDb(); } void EditorWindow::updateResultsDisplayMode() { switch (resultsDisplayMode) { case EditorWindow::ResultsDisplayMode::SEPARATE_TAB: { // Remove old view ui->resultsContainer->hide(); ui->resultsContainer->layout()->removeWidget(ui->resultsFrame); // Add new view ui->tabWidget->insertTab(1, ui->results, tr("Results")); ui->resultsFrame->setParent(ui->results); ui->results->layout()->addWidget(ui->resultsFrame); break; } case EditorWindow::ResultsDisplayMode::BELOW_QUERY: { int currIdx = ui->tabWidget->currentIndex(); // Remove old view ui->tabWidget->removeTab(1); ui->results->layout()->removeWidget(ui->resultsFrame); // Add new view ui->resultsContainer->show(); ui->resultsFrame->setParent(ui->resultsContainer); ui->resultsContainer->layout()->addWidget(ui->resultsFrame); // If results tab was selected before, switch to first tab if (currIdx == 1) { ui->tabWidget->setCurrentIndex(0); ui->dataView->setCurrentIndex(0); ui->dataView->getGridView()->setFocus(); } break; } } } void EditorWindow::createActions() { // SQL editor toolbar actionMap[CURRENT_DB] = ui->toolBar->addWidget(dbCombo); ui->toolBar->addSeparator(); createAction(EXEC_QUERY, ICONS.EXEC_QUERY, tr("Execute query"), this, SLOT(execQuery()), ui->toolBar, ui->sqlEdit); createAction(EXPLAIN_QUERY, ICONS.EXPLAIN_QUERY, tr("Explain query"), this, SLOT(explainQuery()), ui->toolBar, ui->sqlEdit); ui->toolBar->addSeparator(); ui->toolBar->addAction(ui->sqlEdit->getAction(SqlEditor::FORMAT_SQL)); createAction(CLEAR_HISTORY, ICONS.CLEAR_HISTORY, tr("Clear execution history", "sql editor"), this, SLOT(clearHistory()), ui->toolBar); ui->toolBar->addSeparator(); createAction(EXPORT_RESULTS, ICONS.TABLE_EXPORT, tr("Export results", "sql editor"), this, SLOT(exportResults()), ui->toolBar); ui->toolBar->addSeparator(); createAction(CREATE_VIEW_FROM_QUERY, ICONS.VIEW_ADD, tr("Create view from query", "sql editor"), this, SLOT(createViewFromQuery()), ui->toolBar); ui->toolBar->addSeparator(); ui->toolBar->addAction(ui->sqlEdit->getAction(SqlEditor::SAVE_SQL_FILE)); attachActionInMenu(ui->sqlEdit->getAction(SqlEditor::SAVE_SQL_FILE), ui->sqlEdit->getAction(SqlEditor::SAVE_AS_SQL_FILE), ui->toolBar); ui->toolBar->addAction(ui->sqlEdit->getAction(SqlEditor::OPEN_SQL_FILE)); ui->toolBar->addSeparator(); ui->toolBar->addAction(ui->sqlEdit->getAction(SqlEditor::FIND)); ui->toolBar->addAction(ui->sqlEdit->getAction(SqlEditor::REPLACE)); ui->toolBar->addSeparator(); ui->toolBar->addAction(staticActions[RESULTS_IN_TAB]); ui->toolBar->addAction(staticActions[RESULTS_BELOW]); createAction(PREV_DB, tr("Previous database"), this, SLOT(prevDb()), this); createAction(NEXT_DB, tr("Next database"), this, SLOT(nextDb()), this); // Other actions createAction(SHOW_NEXT_TAB, tr("Show next tab", "sql editor"), this, SLOT(showNextTab()), this); createAction(SHOW_PREV_TAB, tr("Show previous tab", "sql editor"), this, SLOT(showPrevTab()), this); createAction(FOCUS_RESULTS_BELOW, tr("Focus results below", "sql editor"), this, SLOT(focusResultsBelow()), this); createAction(FOCUS_EDITOR_ABOVE, tr("Focus SQL editor above", "sql editor"), this, SLOT(focusEditorAbove()), this); createAction(DELETE_SINGLE_HISTORY_SQL, tr("Delete selected SQL history entries", "sql editor"), this, SLOT(deleteSelectedSqlHistory()), ui->historyList); createAction(EXEC_ONE_QUERY, ICONS.EXEC_QUERY, tr("Execute single query under cursor"), this, SLOT(execOneQuery()), this); createAction(EXEC_ALL_QUERIES, ICONS.EXEC_QUERY, tr("Execute all queries in editor"), this, SLOT(execAllQueries()), this); // Static action triggers connect(staticActions[RESULTS_IN_TAB], SIGNAL(triggered()), this, SLOT(updateResultsDisplayMode())); connect(staticActions[RESULTS_BELOW], SIGNAL(triggered()), this, SLOT(updateResultsDisplayMode())); } void EditorWindow::createDbCombo() { dbCombo = new DbComboBox(this); dbCombo->setEditable(false); dbCombo->setFixedWidth(100); connect(dbCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(dbChanged())); } void EditorWindow::setupDefShortcuts() { // Widget context setShortcutContext({EXEC_QUERY, EXEC_QUERY, SHOW_NEXT_TAB, SHOW_PREV_TAB, FOCUS_RESULTS_BELOW, FOCUS_EDITOR_ABOVE}, Qt::WidgetWithChildrenShortcut); BIND_SHORTCUTS(EditorWindow, Action); } void EditorWindow::selectCurrentQuery(bool fallBackToPreviousIfNecessary) { QTextCursor cursor = ui->sqlEdit->textCursor(); int pos = cursor.position(); QString contents = ui->sqlEdit->toPlainText(); QPair boundries = getQueryBoundriesForPosition(contents, pos, fallBackToPreviousIfNecessary); if (boundries.second < 0) { qWarning() << "No tokens to select in EditorWindow::selectCurrentQuery()."; return; } cursor.clearSelection(); cursor.setPosition(boundries.first); cursor.setPosition(boundries.second, QTextCursor::KeepAnchor); ui->sqlEdit->setTextCursor(cursor); } void EditorWindow::updateShortcutTips() { if (actionMap.contains(PREV_DB) && actionMap.contains(NEXT_DB)) { QString prevDbKey = actionMap[PREV_DB]->shortcut().toString(QKeySequence::NativeText); QString nextDbKey = actionMap[NEXT_DB]->shortcut().toString(QKeySequence::NativeText); dbCombo->setToolTip(tr("Active database (%1/%2)").arg(prevDbKey, nextDbKey)); } } void EditorWindow::execQuery(bool explain, QueryExecMode querySelectionMode) { QString sql = getQueryToExecute(true, querySelectionMode); QHash bindParams; bool proceed = processBindParams(sql, bindParams); if (!proceed) return; resultsModel->setDb(getCurrentDb()); resultsModel->setExplainMode(explain); resultsModel->setQuery(sql); resultsModel->setParams(bindParams); resultsModel->setQueryCountLimitForSmartMode(queryLimitForSmartExecution); ui->dataView->refreshData(); updateState(); if (resultsDisplayMode == ResultsDisplayMode::SEPARATE_TAB) { ui->tabWidget->setCurrentIndex(1); ui->dataView->setCurrentIndex(0); ui->dataView->getGridView()->setFocus(); } } void EditorWindow::execOneQuery() { execQuery(false, SINGLE); } void EditorWindow::execAllQueries() { execQuery(false, ALL); } void EditorWindow::explainQuery() { execQuery(true); } bool EditorWindow::processBindParams(QString& sql, QHash& queryParams) { // Get all bind parameters from the query TokenList tokens = Lexer::tokenize(sql); TokenList bindTokens = tokens.filter(Token::BIND_PARAM); // No bind tokens? Return fast. if (bindTokens.isEmpty()) return true; // Process bind tokens, prepare list for a dialog. static_qstring(paramTpl, ":arg%1"); QVector bindParams; QHash namedBindParams; BindParam* bindParam = nullptr; bool isNamed = false; bool nameAlreadyInList = false; int i = 0; for (const TokenPtr& token : bindTokens) { isNamed = (token->value != "?"); nameAlreadyInList = isNamed && namedBindParams.contains(token->value); bindParam = new BindParam(); bindParam->position = i; bindParam->originalName = token->value; bindParam->newName = (isNamed && nameAlreadyInList) ? namedBindParams[token->value] : paramTpl.arg(i); token->value = bindParam->newName; if (!isNamed || !nameAlreadyInList) bindParams << bindParam; if (isNamed && !nameAlreadyInList) namedBindParams[bindParam->originalName] = bindParam->newName; i++; } // Show dialog to query user for values BindParamsDialog dialog(MAINWINDOW); dialog.setBindParams(bindParams); bool accepted = (dialog.exec() == QDialog::Accepted); // Transfer values from dialog to arguments for query if (accepted) { for (BindParam*& bindParam : bindParams) queryParams[bindParam->newName] = bindParam->value; sql = tokens.detokenize(); } // Cleanup for (BindParam*& bindParam : bindParams) delete bindParam; return accepted; } void EditorWindow::dbChanged() { Db* currentDb = getCurrentDb(); ui->sqlEdit->setDb(currentDb); } void EditorWindow::executionSuccessful() { double secs = ((double)resultsModel->getExecutionTime()) / 1000; QString time = QString::number(secs, 'f', 3); if (resultsModel->wasDataModifyingQuery()) { QString rowsAffected = QString::number(resultsModel->getTotalRowsAffected()); notifyInfo(tr("Query finished in %1 second(s). Rows affected: %2").arg(time, rowsAffected)); } else { notifyInfo(tr("Query finished in %1 second(s).").arg(time)); } lastQueryHistoryId = CFG->addSqlHistory(resultsModel->getQuery(), resultsModel->getDb()->getName(), resultsModel->getExecutionTime(), 0); // If we added first history entry - resize dates column. if (ui->historyList->model()->rowCount() == 1) ui->historyList->resizeColumnToContents(1); Db* currentDb = getCurrentDb(); if (currentDb && resultsModel->wasSchemaModified()) DBTREE->refreshSchema(currentDb); lastSuccessfulQuery = resultsModel->getQuery(); updateState(); } void EditorWindow::executionFailed(const QString &errorText) { notifyError(errorText); updateState(); } void EditorWindow::storeExecutionInHistory() { qint64 rowsReturned = resultsModel->getTotalRowsReturned(); qint64 rowsAffected = resultsModel->getTotalRowsAffected(); qint64 rows; if (rowsReturned > 0) rows = rowsReturned; else rows = rowsAffected; CFG->updateSqlHistory(lastQueryHistoryId, resultsModel->getQuery(), resultsModel->getDb()->getName(), resultsModel->getExecutionTime(), rows); } void EditorWindow::prevDb() { int idx = dbCombo->currentIndex() - 1; if (idx < 0) return; dbCombo->setCurrentIndex(idx); } void EditorWindow::nextDb() { int idx = dbCombo->currentIndex() + 1; if (idx >= dbCombo->count()) return; dbCombo->setCurrentIndex(idx); } void EditorWindow::showNextTab() { int tabIdx = ui->tabWidget->currentIndex(); tabIdx++; ui->tabWidget->setCurrentIndex(tabIdx); } void EditorWindow::showPrevTab() { int tabIdx = ui->tabWidget->currentIndex(); tabIdx--; ui->tabWidget->setCurrentIndex(tabIdx); } void EditorWindow::focusResultsBelow() { if (resultsDisplayMode != ResultsDisplayMode::BELOW_QUERY) return; ui->dataView->setCurrentIndex(0); ui->dataView->getGridView()->setFocus(); } void EditorWindow::focusEditorAbove() { if (resultsDisplayMode != ResultsDisplayMode::BELOW_QUERY) return; ui->sqlEdit->setFocus(); } void EditorWindow::historyEntrySelected(const QModelIndex& current, const QModelIndex& previous) { UNUSED(previous); QString sql = ui->historyList->model()->index(current.row(), 5).data().toString(); ui->historyContents->setPlainText(sql); } void EditorWindow::historyEntryActivated(const QModelIndex& current) { QString sql = ui->historyList->model()->index(current.row(), 5).data().toString(); ui->sqlEdit->setPlainText(sql); ui->tabWidget->setCurrentIndex(0); } void EditorWindow::deleteSelectedSqlHistory() { if (ui->historyList->selectionModel()->selectedIndexes().isEmpty()) return; QList ids; for (QModelIndex& idx : ui->historyList->selectionModel()->selectedRows(0)) ids += idx.data().toLongLong(); CFG->deleteSqlHistory(ids); } void EditorWindow::clearHistory() { QMessageBox::StandardButton res = QMessageBox::question(this, tr("Clear execution history"), tr("Are you sure you want to erase the entire SQL execution history? " "This cannot be undone.")); if (res != QMessageBox::Yes) return; CFG->clearSqlHistory(); } void EditorWindow::sqlHistoryContextMenuRequested(const QPoint &pos) { actionMap[DELETE_SINGLE_HISTORY_SQL]->setEnabled(!ui->historyList->selectionModel()->selectedIndexes().isEmpty()); sqlHistoryMenu->popup(ui->historyList->mapToGlobal(pos)); } void EditorWindow::setupSqlHistoryMenu() { sqlHistoryMenu = new QMenu(this); sqlHistoryMenu->addAction(actionMap[DELETE_SINGLE_HISTORY_SQL]); } void EditorWindow::exportResults() { if (!ExportManager::isAnyPluginAvailable()) { notifyError(tr("Cannot export, because no export plugin is loaded.")); return; } QString query = lastSuccessfulQuery.isEmpty() ? getQueryToExecute() : lastSuccessfulQuery; QStringList queries = splitQueries(query, false, true); if (queries.size() == 0) { qWarning() << "No queries after split in EditorWindow::exportResults()"; return; } ExportDialog dialog(this); dialog.setQueryMode(getCurrentDb(), queries.last().trimmed()); dialog.exec(); } void EditorWindow::createViewFromQuery() { if (!getCurrentDb()) { notifyError(tr("No database selected in the SQL editor. Cannot create a view for unknown database.")); return; } QString sql = getQueryToExecute(true); DbObjectDialogs dialogs(getCurrentDb()); dialogs.addView(sql); } void EditorWindow::updateState() { bool executionInProgress = resultsModel->isExecutionInProgress(); actionMap[CURRENT_DB]->setEnabled(!executionInProgress); actionMap[EXEC_QUERY]->setEnabled(!executionInProgress); actionMap[EXPLAIN_QUERY]->setEnabled(!executionInProgress); } void EditorWindow::checkTextChangedForSession() { if (!ui->sqlEdit->getHighlightingSyntax() && !settingSqlContents) emit sessionValueChanged(); } void EditorWindow::queryHighlightingConfigChanged(const QVariant& enabled) { ui->sqlEdit->setCurrentQueryHighlighting(enabled.toBool()); } void EditorWindow::refreshValidDbObjects() { ui->sqlEdit->refreshValidObjects(); } int qHash(EditorWindow::ActionGroup actionGroup) { return static_cast(actionGroup); } bool EditorWindow::isUncommitted() const { return ui->dataView->isUncommitted(); } QString EditorWindow::getQuitUncommittedConfirmMessage() const { return tr("Editor window \"%1\" has uncommitted data.").arg(getMdiWindow()->windowTitle()); }