1 #include "viewwindow.h"
2 #include "ui_viewwindow.h"
3 #include "common/unused.h"
4 #include "schemaresolver.h"
5 #include "services/notifymanager.h"
6 #include "services/dbmanager.h"
7 #include "mainwindow.h"
8 #include "mdiarea.h"
9 #include "sqlitesyntaxhighlighter.h"
10 #include "datagrid/sqlquerymodel.h"
11 #include "common/utils_sql.h"
12 #include "viewmodifier.h"
13 #include "common/widgetcover.h"
14 #include "db/chainexecutor.h"
15 #include "dbtree/dbtree.h"
16 #include "parser/ast/sqlitecreatetrigger.h"
17 #include "dialogs/messagelistdialog.h"
18 #include "dbobjectdialogs.h"
19 #include "dialogs/ddlpreviewdialog.h"
20 #include "uiconfig.h"
21 #include "services/config.h"
22 #include "services/codeformatter.h"
23 #include "themetuner.h"
24 #include "datagrid/sqlviewmodel.h"
25 #include <QPushButton>
26 #include <QProgressBar>
27 #include <QDebug>
28 #include <QMessageBox>
29 #include <QCheckBox>
30
CFG_KEYS_DEFINE(ViewWindow)31 CFG_KEYS_DEFINE(ViewWindow)
32
33 ViewWindow::ViewWindow(QWidget *parent) :
34 MdiChild(parent),
35 ui(new Ui::ViewWindow)
36 {
37 init();
38 applyInitialTab();
39 }
40
ViewWindow(Db * db,QWidget * parent)41 ViewWindow::ViewWindow(Db* db, QWidget* parent) :
42 MdiChild(parent),
43 db(db),
44 ui(new Ui::ViewWindow)
45 {
46 newView();
47 init();
48 ui->dbCombo->setCurrentDb(db);
49 applyInitialTab();
50 }
51
ViewWindow(const ViewWindow & win)52 ViewWindow::ViewWindow(const ViewWindow& win) :
53 MdiChild(win.parentWidget()),
54 db(win.db),
55 database(win.database),
56 view(win.view),
57 ui(new Ui::ViewWindow)
58 {
59 init();
60 initView();
61 applyInitialTab();
62 }
63
ViewWindow(QWidget * parent,Db * db,const QString & database,const QString & view)64 ViewWindow::ViewWindow(QWidget* parent, Db* db, const QString& database, const QString& view) :
65 MdiChild(parent),
66 db(db),
67 database(database),
68 view(view),
69 ui(new Ui::ViewWindow)
70 {
71 init();
72 initView();
73 applyInitialTab();
74 }
75
~ViewWindow()76 ViewWindow::~ViewWindow()
77 {
78 delete ui;
79 }
80
changeEvent(QEvent * e)81 void ViewWindow::changeEvent(QEvent *e)
82 {
83 QWidget::changeEvent(e);
84 switch (e->type()) {
85 case QEvent::LanguageChange:
86 ui->retranslateUi(this);
87 break;
88 default:
89 break;
90 }
91 }
92
saveSession()93 QVariant ViewWindow::saveSession()
94 {
95 if (!db || DBLIST->isTemporary(db))
96 return QVariant();
97
98 QHash<QString,QVariant> sessionValue;
99 sessionValue["view"] = view;
100 sessionValue["db"] = db->getName();
101 return sessionValue;
102 }
103
restoreSession(const QVariant & sessionValue)104 bool ViewWindow::restoreSession(const QVariant& sessionValue)
105 {
106 QHash<QString, QVariant> value = sessionValue.toHash();
107 if (value.size() == 0)
108 {
109 notifyWarn(tr("Could not restore window '%1', because no database or view was stored in session for this window.").arg(value["title"].toString()));
110 return false;
111 }
112
113 if (!value.contains("db") || !value.contains("view"))
114 {
115 notifyWarn(tr("Could not restore window '%1', because no database or view was stored in session for this window.").arg(value["title"].toString()));
116 return false;
117 }
118
119 db = DBLIST->getByName(value["db"].toString());
120 if (!db)
121 {
122 notifyWarn(tr("Could not restore window '%1', because database %2 could not be resolved.").arg(value["title"].toString(), value["db"].toString()));
123 return false;
124 }
125
126 if (!db->isOpen() && !db->open())
127 {
128 notifyWarn(tr("Could not restore window '%1', because database %2 could not be open.").arg(value["title"].toString(), value["db"].toString()));
129 return false;
130 }
131
132 view = value["view"].toString();
133 database = value["database"].toString();
134 SchemaResolver resolver(db);
135 if (!resolver.getViews(database).contains(view, Qt::CaseInsensitive))
136 {
137 notifyWarn(tr("Could not restore window '%1', because the view %2 doesn't exist in the database %3.").arg(value["title"].toString(), view, db->getName()));
138 return false;
139 }
140
141 initView();
142 applyInitialTab();
143 return true;
144 }
145
getIconNameForMdiWindow()146 Icon* ViewWindow::getIconNameForMdiWindow()
147 {
148 return ICONS.VIEW;
149 }
150
getTitleForMdiWindow()151 QString ViewWindow::getTitleForMdiWindow()
152 {
153 QString dbSuffix = (!db ? "" : (" (" + db->getName() + ")"));
154 if (existingView)
155 return view + dbSuffix;
156
157 QStringList existingNames = MDIAREA->getWindowTitles();
158 if (existingNames.contains(windowTitle()))
159 return windowTitle();
160
161 // Generate new name
162 QString title = tr("New view %1").arg(newViewWindowNum++);
163 while (existingNames.contains(title))
164 title = tr("New view %1").arg(newViewWindowNum++);
165
166 title += dbSuffix;
167 return title;
168 }
169
createActions()170 void ViewWindow::createActions()
171 {
172 createQueryTabActions();
173 createTriggersTabActions();
174
175 createAction(NEXT_TAB, "next tab", this, SLOT(nextTab()), this);
176 createAction(PREV_TAB, "prev tab", this, SLOT(prevTab()), this);
177 }
178
setupDefShortcuts()179 void ViewWindow::setupDefShortcuts()
180 {
181 // Widget context
182 setShortcutContext({
183 REFRESH_TRIGGERS,
184 ADD_TRIGGER,
185 EDIT_TRIGGER,
186 DEL_TRIGGER,
187 },
188 Qt::WidgetWithChildrenShortcut);
189
190 BIND_SHORTCUTS(ViewWindow, Action);
191 }
192
restoreSessionNextTime()193 bool ViewWindow::restoreSessionNextTime()
194 {
195 return existingView && db && !DBLIST->isTemporary(db);
196 }
197
getToolBar(int toolbar) const198 QToolBar* ViewWindow::getToolBar(int toolbar) const
199 {
200 switch (static_cast<ToolBar>(toolbar))
201 {
202 case TOOLBAR_QUERY:
203 return ui->queryToolbar;
204 case TOOLBAR_TRIGGERS:
205 return ui->triggersToolbar;
206 }
207 return nullptr;
208 }
209
init()210 void ViewWindow::init()
211 {
212 ui->setupUi(this);
213
214 THEME_TUNER->manageCompactLayout({
215 ui->queryTab,
216 ui->dataTab,
217 ui->triggersTab,
218 ui->ddlTab
219 });
220
221 dataModel = new SqlViewModel(this);
222 ui->dataView->init(dataModel);
223
224 ui->queryEdit->setVirtualSqlExpression("CREATE VIEW name AS %1");
225 ui->queryEdit->setDb(db);
226
227 connect(dataModel, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful()));
228 connect(dataModel, SIGNAL(executionFailed(QString)), this, SLOT(executionFailed(QString)));
229 connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
230 connect(ui->nameEdit, SIGNAL(textChanged(QString)), this, SLOT(updateQueryToolbarStatus()));
231 connect(ui->queryEdit, SIGNAL(textChanged()), this, SLOT(updateQueryToolbarStatus()));
232 connect(ui->queryEdit, SIGNAL(errorsChecked(bool)), this, SLOT(updateQueryToolbarStatus()));
233 connect(ui->triggersList, SIGNAL(itemSelectionChanged()), this, SLOT(updateTriggersState()));
234 connect(ui->triggersList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(triggerViewDoubleClicked(QModelIndex)));
235 connect(ui->outputColumnsTable, SIGNAL(currentRowChanged(int)), this, SLOT(updateColumnButtons()));
236 connect(ui->outputColumnsTable->model(), SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int)), this, SLOT(updateColumnButtons()));
237 connect(ui->outputColumnsTable->model(), SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int)), this, SLOT(updateQueryToolbarStatus()));
238 connect(ui->outputColumnsTable, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(updateQueryToolbarStatus()));
239 connect(CFG_UI.General.DataTabAsFirstInViews, SIGNAL(changed(const QVariant&)), this, SLOT(updateTabsOrder()));
240 connect(CFG_UI.Fonts.DataView, SIGNAL(changed(QVariant)), this, SLOT(updateFont()));
241
242 structureExecutor = new ChainExecutor(this);
243 connect(structureExecutor, SIGNAL(success(SqlQueryPtr)), this, SLOT(changesSuccessfullyCommitted()));
244 connect(structureExecutor, SIGNAL(failure(int,QString)), this, SLOT(changesFailedToCommit(int,QString)));
245
246 setupCoverWidget();
247
248 initActions();
249
250 ui->splitter->setStretchFactor(0, 1);
251 ui->splitter->setStretchFactor(1, 3);
252
253 updateOutputColumnsVisibility();
254
255 updateTabsOrder();
256 createDbCombo();
257
258 updateFont();
259 refreshTriggers();
260 updateQueryToolbarStatus();
261 updateTriggersState();
262 updateColumnButtons();
263 updateAfterInit();
264 }
265
updateAfterInit()266 void ViewWindow::updateAfterInit()
267 {
268 for (QWidget* tab : {ui->dataTab, ui->triggersTab})
269 ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), existingView);
270 }
271
createDbCombo()272 void ViewWindow::createDbCombo()
273 {
274 ui->dbCombo->setFixedWidth(100);
275 ui->dbCombo->setToolTip(tr("Database"));
276 connect(ui->dbCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(dbChanged()));
277 }
278
newView()279 void ViewWindow::newView()
280 {
281 existingView = false;
282 view = "";
283 }
284
initView()285 void ViewWindow::initView()
286 {
287 ui->nameEdit->setText(view);
288
289 parseDdl();
290
291 if (!createView)
292 return; // error occured while parsing ddl, window will be closed
293
294 ui->dbCombo->setCurrentDb(db);
295 if (existingView)
296 {
297 dataModel->setDb(db);
298 dataModel->setQuery(originalCreateView->select->detokenize());
299 dataModel->setView(view);
300 ui->dbCombo->setDisabled(true);
301 }
302 ui->queryEdit->setDb(db);
303
304 ui->queryEdit->setPlainText(createView->select->detokenize());
305
306 if (createView->columns.size() > 0)
307 {
308 columnsFromViewToList();
309 outputColumnsCheck->setChecked(true);
310 }
311
312 updateDdlTab();
313
314 refreshTriggers();
315
316 connect(db, SIGNAL(dbObjectDeleted(QString,QString,DbObjectType)), this, SLOT(checkIfViewDeleted(QString,QString,DbObjectType)));
317 }
318
setupCoverWidget()319 void ViewWindow::setupCoverWidget()
320 {
321 widgetCover = new WidgetCover(this);
322 widgetCover->hide();
323 connect(widgetCover, SIGNAL(cancelClicked()), structureExecutor, SLOT(interrupt()));
324 }
325
createQueryTabActions()326 void ViewWindow::createQueryTabActions()
327 {
328 createAction(REFRESH_QUERY, ICONS.RELOAD, tr("Refresh the view", "view window"), this, SLOT(refreshView()), ui->queryToolbar);
329 ui->queryToolbar->addSeparator();
330 createAction(COMMIT_QUERY, ICONS.COMMIT, tr("Commit the view changes", "view window"), this, SLOT(commitView()), ui->queryToolbar);
331 createAction(ROLLBACK_QUERY, ICONS.ROLLBACK, tr("Rollback the view changes", "view window"), this, SLOT(rollbackView()), ui->queryToolbar);
332 ui->queryToolbar->addSeparator();
333 ui->queryToolbar->addAction(ui->queryEdit->getAction(SqlEditor::FORMAT_SQL));
334
335 outputColumnsCheck = new QAction(ICONS.COLUMNS, tr("Explicit column names"), this);
336 outputColumnsCheck->setCheckable(true);
337 connect(outputColumnsCheck, SIGNAL(toggled(bool)), this, SLOT(updateOutputColumnsVisibility()));
338
339 outputColumnsSeparator = ui->queryToolbar->addSeparator();
340 ui->queryToolbar->addAction(outputColumnsCheck);
341 createAction(GENERATE_OUTPUT_COLUMNS, ICONS.GENERATE_COLUMNS, tr("Generate output column names automatically basing on result columns of the view."), this, SLOT(generateOutputColumns()), ui->queryToolbar);
342 createAction(ADD_COLUMN, ICONS.TABLE_COLUMN_ADD, tr("Add column", "view window"), this, SLOT(addColumn()), ui->queryToolbar);
343 createAction(EDIT_COLUMN, ICONS.TABLE_COLUMN_EDIT, tr("Edit column", "view window"), this, SLOT(editColumn()), ui->queryToolbar);
344 createAction(DEL_COLUMN, ICONS.TABLE_COLUMN_DELETE, tr("Delete column", "view window"), this, SLOT(delColumn()), ui->queryToolbar);
345 createAction(MOVE_COLUMN_UP, ICONS.MOVE_UP, tr("Move column up", "view window"), this, SLOT(moveColumnUp()), ui->queryToolbar);
346 createAction(MOVE_COLUMN_DOWN, ICONS.MOVE_DOWN, tr("Move column down", "view window"), this, SLOT(moveColumnDown()), ui->queryToolbar);
347 }
348
createTriggersTabActions()349 void ViewWindow::createTriggersTabActions()
350 {
351 createAction(REFRESH_TRIGGERS, ICONS.RELOAD, tr("Refresh trigger list", "view window"), this, SLOT(refreshTriggers()), ui->triggersToolbar, ui->triggersList);
352 ui->triggersToolbar->addSeparator();
353 createAction(ADD_TRIGGER, ICONS.TRIGGER_ADD, tr("Create new trigger", "view window"), this, SLOT(addTrigger()), ui->triggersToolbar, ui->triggersList);
354 createAction(EDIT_TRIGGER, ICONS.TRIGGER_EDIT, tr("Edit selected trigger", "view window"), this, SLOT(editTrigger()), ui->triggersToolbar, ui->triggersList);
355 createAction(DEL_TRIGGER, ICONS.TRIGGER_DEL, tr("Delete selected trigger", "view window"), this, SLOT(deleteTrigger()), ui->triggersToolbar, ui->triggersList);
356 }
getView() const357 QString ViewWindow::getView() const
358 {
359 return view;
360 }
361
setSelect(const QString & selectSql)362 void ViewWindow::setSelect(const QString &selectSql)
363 {
364 ui->queryEdit->setPlainText(selectSql);
365 }
366
isUncommitted() const367 bool ViewWindow::isUncommitted() const
368 {
369 return ui->dataView->isUncommitted() || isModified();
370 }
371
getQuitUncommittedConfirmMessage() const372 QString ViewWindow::getQuitUncommittedConfirmMessage() const
373 {
374 QString title = getMdiWindow()->windowTitle();
375 if (ui->dataView->isUncommitted() && isModified())
376 return tr("View window \"%1\" has uncommitted structure modifications and data.").arg(title);
377 else if (ui->dataView->isUncommitted())
378 return tr("View window \"%1\" has uncommitted data.").arg(title);
379 else if (isModified())
380 return tr("View window \"%1\" has uncommitted structure modifications.").arg(title);
381 else
382 {
383 qCritical() << "Unhandled message case in ViewWindow::getQuitUncommittedConfirmMessage().";
384 return QString();
385 }
386 }
387
getAssociatedDb() const388 Db* ViewWindow::getAssociatedDb() const
389 {
390 return db;
391 }
392
staticInit()393 void ViewWindow::staticInit()
394 {
395 qRegisterMetaType<ViewWindow>("ViewWindow");
396 }
397
insertAction(ExtActionPrototype * action,ViewWindow::ToolBar toolbar)398 void ViewWindow::insertAction(ExtActionPrototype* action, ViewWindow::ToolBar toolbar)
399 {
400 return ExtActionContainer::insertAction<ViewWindow>(action, toolbar);
401 }
402
insertActionBefore(ExtActionPrototype * action,ViewWindow::Action beforeAction,ViewWindow::ToolBar toolbar)403 void ViewWindow::insertActionBefore(ExtActionPrototype* action, ViewWindow::Action beforeAction, ViewWindow::ToolBar toolbar)
404 {
405 return ExtActionContainer::insertActionBefore<ViewWindow>(action, beforeAction, toolbar);
406 }
407
insertActionAfter(ExtActionPrototype * action,ViewWindow::Action afterAction,ViewWindow::ToolBar toolbar)408 void ViewWindow::insertActionAfter(ExtActionPrototype* action, ViewWindow::Action afterAction, ViewWindow::ToolBar toolbar)
409 {
410 return ExtActionContainer::insertActionAfter<ViewWindow>(action, afterAction, toolbar);
411 }
412
removeAction(ExtActionPrototype * action,ViewWindow::ToolBar toolbar)413 void ViewWindow::removeAction(ExtActionPrototype* action, ViewWindow::ToolBar toolbar)
414 {
415 ExtActionContainer::removeAction<ViewWindow>(action, toolbar);
416 }
417
getDatabase() const418 QString ViewWindow::getDatabase() const
419 {
420 return database;
421 }
422
getDb() const423 Db* ViewWindow::getDb() const
424 {
425 return db;
426 }
427
refreshView()428 void ViewWindow::refreshView()
429 {
430 initView();
431 updateTriggersState();
432 }
433
commitView(bool skipWarnings)434 void ViewWindow::commitView(bool skipWarnings)
435 {
436 if (!isModified())
437 {
438 qWarning() << "Called ViewWindow::commitView(), but isModified() returned false.";
439 updateQueryToolbarStatus();
440 return;
441 }
442
443 if (!validate(skipWarnings))
444 return;
445
446 executeStructureChanges();
447 }
448
rollbackView()449 void ViewWindow::rollbackView()
450 {
451 createView = SqliteCreateViewPtr::create(*originalCreateView.data());
452 ui->nameEdit->setText(createView->view);
453 ui->queryEdit->setPlainText(createView->select->detokenize());
454
455 columnsFromViewToList();
456 updateQueryToolbarStatus();
457 updateDdlTab();
458 }
459
getCurrentTrigger() const460 QString ViewWindow::getCurrentTrigger() const
461 {
462 int row = ui->triggersList->currentRow();
463 QTableWidgetItem* item = ui->triggersList->item(row, 0);
464 if (!item)
465 return QString();
466
467 return item->text();
468 }
469
applyInitialTab()470 void ViewWindow::applyInitialTab()
471 {
472 if (existingView && !view.isNull() && CFG_UI.General.OpenViewsOnData.get())
473 ui->tabWidget->setCurrentIndex(getDataTabIdx());
474 else
475 ui->tabWidget->setCurrentIndex(getQueryTabIdx());
476 }
477
getCurrentDdl() const478 QString ViewWindow::getCurrentDdl() const
479 {
480 static_qstring(ddlTpl, "CREATE VIEW %1%2 AS %3");
481 QString columnsStr = "";
482 if (outputColumnsCheck->isChecked() && ui->outputColumnsTable->count() > 0)
483 columnsStr = "(" + collectColumnNames().join(", ") + ")";
484
485 return ddlTpl.arg(
486 wrapObjIfNeeded(ui->nameEdit->text()),
487 columnsStr,
488 ui->queryEdit->toPlainText()
489 );
490 }
491
indexedColumnsToNamesOnly(const QList<SqliteIndexedColumn * > & columns) const492 QStringList ViewWindow::indexedColumnsToNamesOnly(const QList<SqliteIndexedColumn*>& columns) const
493 {
494 QStringList names;
495 for (SqliteIndexedColumn* col : columns)
496 names << col->name;
497
498 return names;
499 }
500
collectColumnNames() const501 QStringList ViewWindow::collectColumnNames() const
502 {
503 QStringList cols;
504 for (int row = 0; row < ui->outputColumnsTable->count(); row++)
505 cols << wrapObjIfNeeded(ui->outputColumnsTable->item(row)->text());
506
507 return cols;
508 }
509
columnsFromViewToList()510 void ViewWindow::columnsFromViewToList()
511 {
512 ui->outputColumnsTable->clear();
513 ui->outputColumnsTable->addItems(indexedColumnsToNamesOnly(createView->columns));
514
515 QListWidgetItem* item = nullptr;
516 for (int row = 0; row < ui->outputColumnsTable->count(); row++)
517 {
518 item = ui->outputColumnsTable->item(row);
519 item->setFlags(item->flags() | Qt::ItemIsEditable);
520 }
521 }
522
getDataTabIdx() const523 int ViewWindow::getDataTabIdx() const
524 {
525 return ui->tabWidget->indexOf(ui->dataTab);
526 }
527
getQueryTabIdx() const528 int ViewWindow::getQueryTabIdx() const
529 {
530 return ui->tabWidget->indexOf(ui->queryTab);
531 }
532
getDdlTabIdx() const533 int ViewWindow::getDdlTabIdx() const
534 {
535 return ui->tabWidget->indexOf(ui->ddlTab);
536 }
537
addTrigger()538 void ViewWindow::addTrigger()
539 {
540 DbObjectDialogs dialogs(db, this);
541 dialogs.addTriggerOnView(view);
542 refreshTriggers();
543 }
544
editTrigger()545 void ViewWindow::editTrigger()
546 {
547 QString trigger = getCurrentTrigger();
548 if (trigger.isNull())
549 return;
550
551 DbObjectDialogs dialogs(db, this);
552 dialogs.editTrigger(trigger);
553 refreshTriggers();
554 }
555
deleteTrigger()556 void ViewWindow::deleteTrigger()
557 {
558 QString trigger = getCurrentTrigger();
559 if (trigger.isNull())
560 return;
561
562 DbObjectDialogs dialogs(db, this);
563 dialogs.dropObject(trigger);
564 refreshTriggers();
565 }
566
executionSuccessful()567 void ViewWindow::executionSuccessful()
568 {
569 modifyingThisView = false;
570 dataLoaded = true;
571 }
572
executionFailed(const QString & errorMessage)573 void ViewWindow::executionFailed(const QString& errorMessage)
574 {
575 modifyingThisView = false;
576 notifyError(tr("Could not load data for view %1. Error details: %2").arg(view).arg(errorMessage));
577 }
578
tabChanged(int tabIdx)579 void ViewWindow::tabChanged(int tabIdx)
580 {
581 if (tabsMoving)
582 return;
583
584 if (tabIdx == getDataTabIdx())
585 {
586 if (isModified())
587 {
588 int res = QMessageBox::question(this, tr("Uncommitted changes"),
589 tr("There are uncommitted structure modifications. You cannot browse or edit data until you have "
590 "the view structure settled.\n"
591 "Do you want to commit the structure, or do you want to go back to the structure tab?"),
592 tr("Go back to structure tab"), tr("Commit modifications and browse data."));
593
594 ui->tabWidget->setCurrentIndex(0);
595 if (res == 1)
596 commitView(true);
597
598 return;
599 }
600
601 if (!dataLoaded)
602 ui->dataView->refreshData();
603
604 return;
605 }
606
607 if (tabIdx == getDdlTabIdx())
608 {
609 updateDdlTab();
610 return;
611 }
612 }
613
updateQueryToolbarStatus()614 void ViewWindow::updateQueryToolbarStatus()
615 {
616 bool modified = isModified();
617 bool queryOk = ui->queryEdit->isSyntaxChecked() && !ui->queryEdit->haveErrors();
618 actionMap[COMMIT_QUERY]->setEnabled(modified && queryOk);
619 actionMap[ROLLBACK_QUERY]->setEnabled(modified && existingView);
620 actionMap[REFRESH_QUERY]->setEnabled(existingView);
621 }
622
changesSuccessfullyCommitted()623 void ViewWindow::changesSuccessfullyCommitted()
624 {
625 QStringList sqls = structureExecutor->getQueries();
626 CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath());
627
628 widgetCover->hide();
629
630 originalCreateView = createView;
631 dataLoaded = false;
632
633 //QString oldView = view; // uncomment when implementing notify manager call
634 database = createView->database;
635 QString oldView = view;
636 view = createView->view;
637
638 if (!existingView)
639 notifyInfo(tr("View '%1' was committed successfully.").arg(view));
640 else if (oldView.compare(view, Qt::CaseInsensitive) == 0)
641 notifyInfo(tr("Committed changes for view '%1' successfully.").arg(view));
642 else
643 notifyInfo(tr("Committed changes for view '%1' (named before '%2') successfully.").arg(view, oldView));
644
645 existingView = true;
646 initView();
647 updateQueryToolbarStatus();
648 updateWindowTitle();
649 updateAfterInit();
650
651 DBTREE->refreshSchema(db);
652 }
653
changesFailedToCommit(int errorCode,const QString & errorText)654 void ViewWindow::changesFailedToCommit(int errorCode, const QString& errorText)
655 {
656 qDebug() << "ViewWindow::changesFailedToCommit:" << errorCode << errorText;
657
658 widgetCover->hide();
659
660 NotifyManager::getInstance()->error(tr("Could not commit view changes. Error message: %1", "view window").arg(errorText));
661 }
662
updateTriggersState()663 void ViewWindow::updateTriggersState()
664 {
665 bool editDel = ui->triggersList->currentItem() != nullptr;
666 actionMap[REFRESH_TRIGGERS]->setEnabled(existingView);
667 actionMap[ADD_TRIGGER]->setEnabled(existingView);
668 actionMap[EDIT_TRIGGER]->setEnabled(editDel);
669 actionMap[DEL_TRIGGER]->setEnabled(editDel);
670 }
671
nextTab()672 void ViewWindow::nextTab()
673 {
674 int idx = ui->tabWidget->currentIndex();
675 idx++;
676 ui->tabWidget->setCurrentIndex(idx);
677 }
678
prevTab()679 void ViewWindow::prevTab()
680 {
681 int idx = ui->tabWidget->currentIndex();
682 idx--;
683 ui->tabWidget->setCurrentIndex(idx);
684 }
685
dbClosedFinalCleanup()686 void ViewWindow::dbClosedFinalCleanup()
687 {
688 db = nullptr;
689 dataModel->setDb(nullptr);
690 ui->queryEdit->setDb(nullptr);
691 structureExecutor->setDb(nullptr);
692 }
693
checkIfViewDeleted(const QString & database,const QString & object,DbObjectType type)694 void ViewWindow::checkIfViewDeleted(const QString& database, const QString& object, DbObjectType type)
695 {
696 UNUSED(database);
697
698 if (type == DbObjectType::TRIGGER)
699 {
700 for (int i = 0, total = ui->triggersList->rowCount(); i < total; ++i)
701 {
702 if (ui->triggersList->item(i, 0)->text().compare(object, Qt::CaseInsensitive) == 0)
703 {
704 ui->triggersList->removeRow(i);
705 return;
706 }
707 }
708 }
709
710 if (type != DbObjectType::VIEW)
711 return;
712
713 if (modifyingThisView)
714 return;
715
716 // TODO uncomment below when dbnames are supported
717 // if (this->database != database)
718 // return;
719
720 if (object.compare(view, Qt::CaseInsensitive) == 0)
721 {
722 dbClosedFinalCleanup();
723 getMdiWindow()->close();
724 }
725 }
726
updateOutputColumnsVisibility()727 void ViewWindow::updateOutputColumnsVisibility()
728 {
729 bool enabled = outputColumnsCheck->isChecked();
730
731 ui->outputColumnsContainer->setVisible(enabled);
732 actionMap[Action::GENERATE_OUTPUT_COLUMNS]->setVisible(enabled);
733 actionMap[Action::ADD_COLUMN]->setVisible(enabled);
734 actionMap[Action::EDIT_COLUMN]->setVisible(enabled);
735 actionMap[Action::DEL_COLUMN]->setVisible(enabled);
736 actionMap[Action::MOVE_COLUMN_UP]->setVisible(enabled);
737 actionMap[Action::MOVE_COLUMN_DOWN]->setVisible(enabled);
738
739 updateQueryToolbarStatus();
740 }
741
addColumn()742 void ViewWindow::addColumn()
743 {
744 QListWidgetItem* item = new QListWidgetItem();
745 item->setFlags(item->flags() | Qt::ItemIsEditable);
746 ui->outputColumnsTable->addItem(item);
747 ui->outputColumnsTable->editItem(item);
748 ui->outputColumnsTable->setCurrentItem(item);
749 updateColumnButtons();
750 }
751
editColumn()752 void ViewWindow::editColumn()
753 {
754 QListWidgetItem* item = ui->outputColumnsTable->currentItem();
755 ui->outputColumnsTable->editItem(item);
756 updateColumnButtons();
757 }
758
delColumn()759 void ViewWindow::delColumn()
760 {
761 QListWidgetItem* item = ui->outputColumnsTable->takeItem(ui->outputColumnsTable->currentRow());
762 delete item;
763 updateColumnButtons();
764 }
765
moveColumnUp()766 void ViewWindow::moveColumnUp()
767 {
768 int row = ui->outputColumnsTable->currentRow();
769 if (row <= 0)
770 return;
771
772 QListWidgetItem* item = ui->outputColumnsTable->takeItem(row);
773 ui->outputColumnsTable->insertItem(--row, item);
774 ui->outputColumnsTable->setCurrentItem(item);
775 }
776
moveColumnDown()777 void ViewWindow::moveColumnDown()
778 {
779 int row = ui->outputColumnsTable->currentRow();
780 if (row + 1 >= ui->outputColumnsTable->count())
781 return;
782
783 QListWidgetItem* item = ui->outputColumnsTable->takeItem(row);
784 ui->outputColumnsTable->insertItem(++row, item);
785 ui->outputColumnsTable->setCurrentItem(item);
786 }
787
updateColumnButtons()788 void ViewWindow::updateColumnButtons()
789 {
790 QListWidgetItem* item = ui->outputColumnsTable->currentItem();
791 int row = ui->outputColumnsTable->currentRow();
792
793 actionMap[MOVE_COLUMN_UP]->setEnabled(row > 0);
794 actionMap[MOVE_COLUMN_DOWN]->setEnabled(row + 1 < ui->outputColumnsTable->count());
795 actionMap[EDIT_COLUMN]->setEnabled(item != nullptr);
796 actionMap[DEL_COLUMN]->setEnabled(item != nullptr);
797 }
798
generateOutputColumns()799 void ViewWindow::generateOutputColumns()
800 {
801 if (ui->outputColumnsTable->count() > 0)
802 {
803 QMessageBox::StandardButton res = QMessageBox::question(this, tr("Override columns"), tr("Currently defined columns will be overriden. Do you want to continue?"));
804 if (res != QMessageBox::Yes)
805 return;
806 }
807
808 // Validate and generate fresh createView instance
809 bool validated = validate(true);
810 if (!validated)
811 return;
812
813 // Make copy of CREATE statement and remove columns
814 SqliteCreateView* stmt = dynamic_cast<SqliteCreateView*>(createView->clone());
815 for (SqliteIndexedColumn* col : stmt->columns)
816 delete col;
817
818 stmt->columns.clear();
819
820 // Indentify columns
821 SchemaResolver resolver(db);
822 QStringList columns = resolver.getColumnsUsingPragma(stmt);
823 delete stmt;
824 if (columns.isEmpty())
825 {
826 notifyWarn(tr("Could not determinate columns returned from the view. The query is problably incomplete or contains errors."));
827 return;
828 }
829
830 ui->outputColumnsTable->clear();
831 ui->outputColumnsTable->addItems(columns);
832
833 QListWidgetItem* item = nullptr;
834 for (int row = 0; row < columns.size(); row++)
835 {
836 item = ui->outputColumnsTable->item(row);
837 item->setFlags(item->flags() | Qt::ItemIsEditable);
838 }
839 }
840
updateTabsOrder()841 void ViewWindow::updateTabsOrder()
842 {
843 tabsMoving = true;
844 ui->tabWidget->removeTab(getDataTabIdx());
845 int idx = 1;
846 if (CFG_UI.General.DataTabAsFirstInViews.get())
847 idx = 0;
848
849 ui->tabWidget->insertTab(idx, ui->dataTab, tr("Data"));
850 tabsMoving = false;
851 }
852
triggerViewDoubleClicked(const QModelIndex & idx)853 void ViewWindow::triggerViewDoubleClicked(const QModelIndex& idx)
854 {
855 if (!idx.isValid())
856 {
857 addTrigger();
858 return;
859 }
860
861 QString trigger = ui->triggersList->item(idx.row(), 0)->text();
862
863 DbObjectDialogs dialogs(db, this);
864 dialogs.editTrigger(trigger);
865 refreshTriggers();
866 }
867
refreshTriggers()868 void ViewWindow::refreshTriggers()
869 {
870 if (!db || !db->isValid())
871 return;
872
873 SchemaResolver resolver(db);
874 QList<SqliteCreateTriggerPtr> triggers = resolver.getParsedTriggersForView(database, view);
875
876 ui->triggersList->setColumnCount(4);
877 ui->triggersList->setRowCount(triggers.size());
878 ui->triggersList->horizontalHeader()->setMaximumSectionSize(200);
879 ui->triggersList->setHorizontalHeaderLabels({
880 tr("Name", "view window triggers"),
881 tr("Instead of", "view window triggers"),
882 tr("Condition", "view window triggers"),
883 tr("Details", "table window triggers")
884 });
885
886 QTableWidgetItem* item = nullptr;
887 QString event;
888 int row = 0;
889 for (SqliteCreateTriggerPtr trig : triggers)
890 {
891 item = new QTableWidgetItem(trig->trigger);
892 item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
893 ui->triggersList->setItem(row, 0, item);
894
895 event = trig->tokensMap["trigger_event"].detokenize();
896 item = new QTableWidgetItem(event);
897 item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
898 ui->triggersList->setItem(row, 1, item);
899
900 item = new QTableWidgetItem(trig->precondition ? trig->precondition->detokenize().trimmed() : "");
901 item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
902 ui->triggersList->setItem(row, 2, item);
903
904 item = new QTableWidgetItem(trig->tokensMap["trigger_cmd_list"].detokenize().trimmed());
905 item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
906 ui->triggersList->setItem(row, 3, item);
907
908 row++;
909 }
910
911 ui->triggersList->resizeColumnsToContents();
912 updateTriggersState();
913 }
914
parseDdl()915 void ViewWindow::parseDdl()
916 {
917 if (existingView)
918 {
919 SchemaResolver resolver(db);
920 SqliteQueryPtr parsedObject = resolver.getParsedObject(database, view, SchemaResolver::VIEW);
921 if (!parsedObject.dynamicCast<SqliteCreateView>())
922 {
923 notifyError(tr("Could not process the %1 view correctly. Unable to open a view window.").arg(view));
924 invalid = true;
925 return;
926 }
927
928 createView = parsedObject.dynamicCast<SqliteCreateView>();
929 }
930 else
931 {
932 createView = SqliteCreateViewPtr::create();
933 createView->view = view;
934 }
935 originalCreateView = SqliteCreateViewPtr::create(*createView);
936
937 // Replacing \r\n with \n, cause \r\n can be carried over from version 2.x.x, which did this incorrectly.
938 originalQuery = originalCreateView->select->detokenize().replace("\r\n", "\n");
939 }
940
updateDdlTab()941 void ViewWindow::updateDdlTab()
942 {
943 ui->ddlEdit->setPlainText(FORMATTER->format("sql", getCurrentDdl(), db));
944 }
945
isModified() const946 bool ViewWindow::isModified() const
947 {
948 // Quick checks first
949 bool modified = !existingView || (originalCreateView && originalCreateView->view != ui->nameEdit->text()) ||
950 ui->queryEdit->toPlainText() != originalQuery;
951
952 if (modified)
953 return modified;
954
955 // And now a bit slower check
956 QStringList origCols = createView ? indexedColumnsToNamesOnly(createView->columns) : QStringList();
957 QStringList currentCols;
958 if (outputColumnsCheck->isChecked())
959 currentCols = collectColumnNames();
960
961 bool colsModified = origCols != currentCols;
962 return colsModified;
963 }
964
validate(bool skipWarnings)965 bool ViewWindow::validate(bool skipWarnings)
966 {
967 if (!existingView && !skipWarnings && ui->nameEdit->text().isEmpty())
968 {
969 int res = QMessageBox::warning(this, tr("Empty name"), tr("A blank name for the view is allowed in SQLite, but it is not recommended.\n"
970 "Are you sure you want to create a view with blank name?"), QMessageBox::Yes, QMessageBox::No);
971
972 if (res != QMessageBox::Yes)
973 return false;
974 }
975
976 // Rebuilding createView statement and validating it on the fly.
977 QString ddl = getCurrentDdl();
978 Parser parser;
979 if (!parser.parse(ddl) || parser.getQueries().size() < 1)
980 {
981 notifyError(tr("The SELECT statement could not be parsed. Please correct the query and retry.\nDetails: %1").arg(parser.getErrorString()));
982 return false;
983 }
984
985 SqliteQueryPtr query = parser.getQueries().first();
986 SqliteCreateViewPtr viewStmt = query.dynamicCast<SqliteCreateView>();
987 if (!viewStmt)
988 {
989 notifyError(tr("The view could not be modified due to internal SQLiteStudio error. Please report this!"));
990 qCritical() << "Could not parse new view, because parsed object is of different type. The type is"
991 << sqliteQueryTypeToString(query->queryType) << "for following query:" << ddl;
992 return false;
993 }
994
995 createView = viewStmt;
996 return true;
997 }
998
executeStructureChanges()999 void ViewWindow::executeStructureChanges()
1000 {
1001 QStringList sqls;
1002 QList<bool> sqlMandatoryFlags;
1003
1004 QString theDdl = getCurrentDdl();
1005 if (!existingView)
1006 {
1007 sqls << theDdl;
1008 }
1009 else
1010 {
1011 Parser parser;
1012 if (!parser.parse(theDdl))
1013 {
1014 qCritical() << "Could not re-parse the view for executing it:" << parser.getErrorString();
1015 notifyError(tr("The view code could not be parsed properly for execution. This is a SQLiteStudio's bug. Please report it."));
1016 return;
1017 }
1018
1019 createView = parser.getQueries().first().dynamicCast<SqliteCreateView>();
1020 if (viewModifier)
1021 delete viewModifier;
1022
1023 viewModifier = new ViewModifier(db, database, view);
1024 viewModifier->alterView(createView);
1025
1026 if (viewModifier->hasMessages())
1027 {
1028 MessageListDialog dialog(tr("Following problems will take place while modifying the view.\n"
1029 "Would you like to proceed?", "view window"));
1030 dialog.setWindowTitle(tr("View modification", "view window"));
1031 for (const QString& error : viewModifier->getErrors())
1032 dialog.addError(error);
1033
1034 for (const QString& warn : viewModifier->getWarnings())
1035 dialog.addWarning(warn);
1036
1037 if (dialog.exec() != QDialog::Accepted)
1038 return;
1039 }
1040
1041 sqls = viewModifier->generateSqls();
1042 sqlMandatoryFlags = viewModifier->getMandatoryFlags();
1043 }
1044
1045 if (!CFG_UI.General.DontShowDdlPreview.get())
1046 {
1047 DdlPreviewDialog dialog(db, this);
1048 dialog.setDdl(sqls);
1049 if (dialog.exec() != QDialog::Accepted)
1050 return;
1051 }
1052
1053 modifyingThisView = true;
1054 structureExecutor->setDb(db);
1055 structureExecutor->setQueries(sqls);
1056 structureExecutor->setMandatoryQueries(sqlMandatoryFlags);
1057 structureExecutor->exec();
1058 widgetCover->show();
1059 }
1060
updateFont()1061 void ViewWindow::updateFont()
1062 {
1063 QFont f = CFG_UI.Fonts.DataView.get();
1064 QFontMetrics fm(f);
1065
1066 QTableView* views[] = {ui->triggersList};
1067 for (QTableView* view : views)
1068 {
1069 view->setFont(f);
1070 view->horizontalHeader()->setFont(f);
1071 view->verticalHeader()->setFont(f);
1072 view->verticalHeader()->setDefaultSectionSize(fm.height() + 4);
1073 }
1074 }
1075
dbChanged()1076 void ViewWindow::dbChanged()
1077 {
1078 disconnect(db, SIGNAL(dbObjectDeleted(QString,QString,DbObjectType)), this, SLOT(checkIfViewDeleted(QString,QString,DbObjectType)));
1079
1080 db = ui->dbCombo->currentDb();
1081 dataModel->setDb(db);
1082 ui->queryEdit->setDb(db);
1083
1084 connect(db, SIGNAL(dbObjectDeleted(QString,QString,DbObjectType)), this, SLOT(checkIfViewDeleted(QString,QString,DbObjectType)));
1085 }
1086