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