1 #include "tablewindow.h"
2 #include "ui_tablewindow.h"
3 #include "services/dbmanager.h"
4 #include "services/notifymanager.h"
5 #include "sqlitestudio.h"
6 #include "common/unused.h"
7 #include "schemaresolver.h"
8 #include "iconmanager.h"
9 #include "common/intvalidator.h"
10 #include "common/extlineedit.h"
11 #include "datagrid/sqltablemodel.h"
12 #include "common/extaction.h"
13 #include "mainwindow.h"
14 #include "tablestructuremodel.h"
15 #include "tableconstraintsmodel.h"
16 #include "dialogs/columndialog.h"
17 #include "dialogs/constraintdialog.h"
18 #include "mdiarea.h"
19 #include "sqlitesyntaxhighlighter.h"
20 #include "dialogs/newconstraintdialog.h"
21 #include "db/chainexecutor.h"
22 #include "common/widgetcover.h"
23 #include "mdiwindow.h"
24 #include "dbtree/dbtree.h"
25 #include "constrainttabmodel.h"
26 #include "parser/ast/sqlitecreateindex.h"
27 #include "parser/ast/sqlitecreatetrigger.h"
28 #include "dialogs/messagelistdialog.h"
29 #include "services/codeformatter.h"
30 #include "uiconfig.h"
31 #include "dialogs/ddlpreviewdialog.h"
32 #include "services/config.h"
33 #include "services/importmanager.h"
34 #include "dbobjectdialogs.h"
35 #include "dialogs/exportdialog.h"
36 #include "common/centerediconitemdelegate.h"
37 #include "themetuner.h"
38 #include "dialogs/importdialog.h"
39 #include "dialogs/populatedialog.h"
40 #include "datagrid/sqlqueryitem.h"
41 #include "common/dbcombobox.h"
42 #include <QMenu>
43 #include <QToolButton>
44 #include <QLabel>
45 #include <QDebug>
46 #include <QMessageBox>
47 #include <tablemodifier.h>
48 #include <QProgressBar>
49 #include <QPushButton>
50 #include <QDebug>
51 #include <QStyleFactory>
52 
53 // TODO extend QTableView for columns and constraints, so they show full-row-width drop indicator,
54 // instead of single column drop indicator.
55 
CFG_KEYS_DEFINE(TableWindow)56 CFG_KEYS_DEFINE(TableWindow)
57 
58 TableWindow::TableWindow(QWidget* parent) :
59     MdiChild(parent),
60     ui(new Ui::TableWindow)
61 {
62     init();
63     applyInitialTab();
64 }
65 
TableWindow(Db * db,QWidget * parent)66 TableWindow::TableWindow(Db* db, QWidget* parent) :
67     MdiChild(parent),
68     db(db),
69     ui(new Ui::TableWindow)
70 {
71     newTable();
72     init();
73     initDbAndTable();
74     applyInitialTab();
75 }
76 
TableWindow(const TableWindow & win)77 TableWindow::TableWindow(const TableWindow& win) :
78     MdiChild(win.parentWidget()),
79     db(win.db),
80     database(win.database),
81     table(win.table),
82     ui(new Ui::TableWindow)
83 {
84     init();
85     initDbAndTable();
86     applyInitialTab();
87 }
88 
TableWindow(QWidget * parent,Db * db,const QString & database,const QString & table)89 TableWindow::TableWindow(QWidget *parent, Db* db, const QString& database, const QString& table) :
90     MdiChild(parent),
91     db(db),
92     database(database),
93     table(table),
94     ui(new Ui::TableWindow)
95 {
96     init();
97     initDbAndTable();
98     applyInitialTab();
99 }
100 
~TableWindow()101 TableWindow::~TableWindow()
102 {
103     delete ui;
104 
105     if (tableModifier)
106     {
107         delete tableModifier;
108         tableModifier = nullptr;
109     }
110 }
111 
staticInit()112 void TableWindow::staticInit()
113 {
114     qRegisterMetaType<TableWindow>("TableWindow");
115 }
116 
insertAction(ExtActionPrototype * action,TableWindow::ToolBar toolbar)117 void TableWindow::insertAction(ExtActionPrototype* action, TableWindow::ToolBar toolbar)
118 {
119     return ExtActionContainer::insertAction<TableWindow>(action, toolbar);
120 }
121 
insertActionBefore(ExtActionPrototype * action,TableWindow::Action beforeAction,TableWindow::ToolBar toolbar)122 void TableWindow::insertActionBefore(ExtActionPrototype* action, TableWindow::Action beforeAction, TableWindow::ToolBar toolbar)
123 {
124     return ExtActionContainer::insertActionBefore<TableWindow>(action, beforeAction, toolbar);
125 }
126 
insertActionAfter(ExtActionPrototype * action,TableWindow::Action afterAction,TableWindow::ToolBar toolbar)127 void TableWindow::insertActionAfter(ExtActionPrototype* action, TableWindow::Action afterAction, TableWindow::ToolBar toolbar)
128 {
129     return ExtActionContainer::insertActionAfter<TableWindow>(action, afterAction, toolbar);
130 }
131 
removeAction(ExtActionPrototype * action,TableWindow::ToolBar toolbar)132 void TableWindow::removeAction(ExtActionPrototype* action, TableWindow::ToolBar toolbar)
133 {
134     ExtActionContainer::removeAction<TableWindow>(action, toolbar);
135 }
136 
newTable()137 void TableWindow::newTable()
138 {
139     existingTable = false;
140     table = "";
141 }
142 
init()143 void TableWindow::init()
144 {
145     ui->setupUi(this);
146     ui->structureSplitter->setStretchFactor(0, 2);
147     ui->structureView->horizontalHeader()->setSectionsClickable(false);
148     ui->structureView->verticalHeader()->setSectionsClickable(false);
149     constraintColumnsDelegate = new CenteredIconItemDelegate(this);
150 
151 #ifdef Q_OS_MACX
152     QStyle *fusion = QStyleFactory::create("Fusion");
153     ui->structureToolBar->setStyle(fusion);
154     ui->structureTab->layout()->setSpacing(0);
155     ui->tableConstraintsToolbar->setStyle(fusion);
156     ui->constraintsWidget->layout()->setSpacing(0);
157     ui->indexToolBar->setStyle(fusion);
158     ui->indexesTab->layout()->setSpacing(0);
159     ui->triggerToolBar->setStyle(fusion);
160     ui->triggersTab->layout()->setSpacing(0);
161 #endif
162 
163     dataModel = new SqlTableModel(this);
164     ui->dataView->init(dataModel);
165 
166     initActions();
167     updateTabsOrder();
168     createDbCombo();
169 
170     connect(dataModel, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful()));
171     connect(dataModel, SIGNAL(executionFailed(QString)), this, SLOT(executionFailed(QString)));
172     connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
173     connect(this, SIGNAL(modifyStatusChanged()), this, SLOT(updateStructureCommitState()));
174     connect(ui->tableNameEdit, SIGNAL(textChanged(QString)), this, SIGNAL(modifyStatusChanged()));
175     connect(ui->tableNameEdit, SIGNAL(textChanged(QString)), this, SLOT(nameChanged()));
176     connect(ui->indexList, SIGNAL(itemSelectionChanged()), this, SLOT(updateIndexesState()));
177     connect(ui->triggerList, SIGNAL(itemSelectionChanged()), this, SLOT(updateTriggersState()));
178     connect(CFG_UI.General.DataTabAsFirstInTables, SIGNAL(changed(const QVariant&)), this, SLOT(updateTabsOrder()));
179     connect(ui->structureView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(structureViewDoubleClicked(const QModelIndex&)));
180     connect(ui->tableConstraintsView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(constraintsViewDoubleClicked(const QModelIndex&)));
181     connect(CFG_UI.Fonts.DataView, SIGNAL(changed(QVariant)), this, SLOT(updateFont()));
182 
183     structureExecutor = new ChainExecutor(this);
184     connect(structureExecutor, SIGNAL(success(SqlQueryPtr)), this, SLOT(changesSuccessfullyCommitted()));
185     connect(structureExecutor, SIGNAL(failure(int,QString)), this, SLOT(changesFailedToCommit(int,QString)));
186 
187     THEME_TUNER->manageCompactLayout({
188                                          ui->structureTab,
189                                          ui->constraintsWidget,
190                                          ui->dataTab,
191                                          ui->constraintsTab,
192                                          ui->indexesTab,
193                                          ui->triggersTab,
194                                          ui->ddlTab
195                                      });
196 
197     updateFont();
198     setupCoverWidget();
199     updateAfterInit();
200 }
201 
createActions()202 void TableWindow::createActions()
203 {
204     createAction(EXPORT, ICONS.TABLE_EXPORT, tr("Export table", "table window"), this, SLOT(exportTable()), this);
205     createAction(IMPORT, ICONS.TABLE_IMPORT, tr("Import data to table", "table window"), this, SLOT(importTable()), this);
206     createAction(POPULATE, ICONS.TABLE_POPULATE, tr("Populate table", "table window"), this, SLOT(populateTable()), this);
207 
208     createStructureActions();
209     createDataGridActions();
210     createDataFormActions();
211     createIndexActions();
212     createTriggerActions();
213 
214     createAction(NEXT_TAB, "next tab", this, SLOT(nextTab()), this);
215     createAction(PREV_TAB, "prev tab", this, SLOT(prevTab()), this);
216 }
217 
createStructureActions()218 void TableWindow::createStructureActions()
219 {
220     createAction(REFRESH_STRUCTURE, ICONS.RELOAD, tr("Refresh structure", "table window"), this, SLOT(refreshStructure()), ui->structureToolBar);
221     ui->structureToolBar->addSeparator();
222     createAction(COMMIT_STRUCTURE, ICONS.COMMIT, tr("Commit structure changes", "table window"), this, SLOT(commitStructure()), ui->structureToolBar);
223     createAction(ROLLBACK_STRUCTURE, ICONS.ROLLBACK, tr("Rollback structure changes", "table window"), this, SLOT(rollbackStructure()), ui->structureToolBar);
224     createAction(ADD_COLUMN, ICONS.TABLE_COLUMN_ADD, tr("Add column", "table window"), this, SLOT(addColumn()), ui->structureToolBar, ui->structureView);
225     createAction(EDIT_COLUMN, ICONS.TABLE_COLUMN_EDIT, tr("Edit column", "table window"), this, SLOT(editColumn()), ui->structureToolBar, ui->structureView);
226     createAction(DEL_COLUMN, ICONS.TABLE_COLUMN_DELETE, tr("Delete column", "table window"), this, SLOT(delColumn()), ui->structureToolBar, ui->structureView);
227     createAction(MOVE_COLUMN_UP, ICONS.MOVE_UP, tr("Move column up", "table window"), this, SLOT(moveColumnUp()), ui->structureToolBar, ui->structureView);
228     createAction(MOVE_COLUMN_DOWN, ICONS.MOVE_DOWN, tr("Move column down", "table window"), this, SLOT(moveColumnDown()), ui->structureToolBar, ui->structureView);
229     ui->structureToolBar->addSeparator();
230     createAction(ADD_INDEX_STRUCT, ICONS.INDEX_ADD, tr("Create index", "table window"), this, SLOT(addIndex()), ui->structureToolBar, ui->structureView);
231     createAction(ADD_TRIGGER_STRUCT, ICONS.TRIGGER_ADD, tr("Create trigger", "table window"), this, SLOT(addTrigger()), ui->structureToolBar, ui->structureView);
232     ui->structureToolBar->addSeparator();
233     ui->structureToolBar->addAction(actionMap[IMPORT]);
234     ui->structureToolBar->addAction(actionMap[EXPORT]);
235     ui->structureToolBar->addAction(actionMap[POPULATE]);
236     ui->structureToolBar->addSeparator();
237     createAction(CREATE_SIMILAR, ICONS.TABLE_CREATE_SIMILAR, tr("Create similar table", "table window"), this, SLOT(createSimilarTable()), ui->structureToolBar);
238     createAction(RESET_AUTOINCREMENT, ICONS.RESET_AUTOINCREMENT, tr("Reset autoincrement value", "table window"), this, SLOT(resetAutoincrement()), ui->structureToolBar);
239 
240     // Table constraints
241     createAction(ADD_TABLE_CONSTRAINT, ICONS.TABLE_CONSTRAINT_ADD, tr("Add table constraint", "table window"), this, SLOT(addConstraint()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
242     createAction(EDIT_TABLE_CONSTRAINT, ICONS.TABLE_CONSTRAINT_EDIT, tr("Edit table constraint", "table window"), this, SLOT(editConstraint()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
243     createAction(DEL_TABLE_CONSTRAINT, ICONS.TABLE_COLUMN_DELETE, tr("Delete table constraint", "table window"), this, SLOT(delConstraint()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
244     createAction(MOVE_CONSTRAINT_UP, ICONS.MOVE_UP, tr("Move table constraint up", "table window"), this, SLOT(moveConstraintUp()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
245     createAction(MOVE_CONSTRAINT_DOWN, ICONS.MOVE_DOWN, tr("Move table constraint down", "table window"), this, SLOT(moveConstraintDown()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
246     ui->tableConstraintsToolbar->addSeparator();
247     createAction(ADD_TABLE_PK, ICONS.CONSTRAINT_PRIMARY_KEY_ADD, tr("Add table primary key", "table window"), this, SLOT(addPk()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
248     createAction(ADD_TABLE_FK, ICONS.CONSTRAINT_FOREIGN_KEY_ADD, tr("Add table foreign key", "table window"), this, SLOT(addFk()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
249     createAction(ADD_TABLE_UNIQUE, ICONS.CONSTRAINT_UNIQUE_ADD, tr("Add table unique constraint", "table window"), this, SLOT(addUnique()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
250     createAction(ADD_TABLE_CHECK, ICONS.CONSTRAINT_CHECK_ADD, tr("Add table check constraint", "table window"), this, SLOT(addCheck()), ui->tableConstraintsToolbar, ui->tableConstraintsView);
251 }
252 
createDataGridActions()253 void TableWindow::createDataGridActions()
254 {
255     QAction* before = ui->dataView->getAction(DataView::FILTER_VALUE);
256     ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertAction(before, actionMap[IMPORT]);
257     ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertAction(before, actionMap[EXPORT]);
258     ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertAction(before, actionMap[POPULATE]);
259     ui->dataView->getToolBar(DataView::TOOLBAR_GRID)->insertSeparator(before);
260 }
261 
createDataFormActions()262 void TableWindow::createDataFormActions()
263 {
264 }
265 
createIndexActions()266 void TableWindow::createIndexActions()
267 {
268     createAction(REFRESH_INDEXES, ICONS.RELOAD, tr("Refresh index list", "table window"), this, SLOT(updateIndexes()), ui->indexToolBar, ui->indexList);
269     ui->indexToolBar->addSeparator();
270     createAction(ADD_INDEX, ICONS.INDEX_ADD, tr("Create index", "table window"), this, SLOT(addIndex()), ui->indexToolBar, ui->indexList);
271     createAction(EDIT_INDEX, ICONS.INDEX_EDIT, tr("Edit index", "table window"), this, SLOT(editCurrentIndex()), ui->indexToolBar, ui->indexList);
272     createAction(DEL_INDEX, ICONS.INDEX_DEL, tr("Delete index", "table window"), this, SLOT(delIndex()), ui->indexToolBar, ui->indexList);
273     connect(ui->indexList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(indexViewDoubleClicked(QModelIndex)));
274 }
275 
createTriggerActions()276 void TableWindow::createTriggerActions()
277 {
278     createAction(REFRESH_TRIGGERS, ICONS.RELOAD, tr("Refresh trigger list", "table window"), this, SLOT(updateTriggers()), ui->triggerToolBar, ui->triggerList);
279     ui->triggerToolBar->addSeparator();
280     createAction(ADD_TRIGGER, ICONS.TRIGGER_ADD, tr("Create trigger", "table window"), this, SLOT(addTrigger()), ui->triggerToolBar, ui->triggerList);
281     createAction(EDIT_TRIGGER, ICONS.TRIGGER_EDIT, tr("Edit trigger", "table window"), this, SLOT(editTrigger()), ui->triggerToolBar, ui->triggerList);
282     createAction(DEL_TRIGGER, ICONS.TRIGGER_DEL, tr("Delete trigger", "table window"), this, SLOT(delTrigger()), ui->triggerToolBar, ui->triggerList);
283     connect(ui->triggerList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(triggerViewDoubleClicked(QModelIndex)));
284 }
285 
editColumn(const QModelIndex & idx)286 void TableWindow::editColumn(const QModelIndex& idx)
287 {
288     if (!idx.isValid())
289     {
290         addColumn();
291         return;
292     }
293 
294     SqliteCreateTable::Column* column = structureModel->getColumn(idx.row());
295     ColumnDialog columnDialog(db, this);
296     columnDialog.setColumn(column);
297     if (hasAnyPkDefined() && !column->hasConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY))
298         columnDialog.disableConstraint(ConstraintDialog::Constraint::PK);
299 
300     if (columnDialog.exec() != QDialog::Accepted)
301         return;
302 
303     SqliteCreateTable::Column* modifiedColumn = columnDialog.getModifiedColumn();
304     structureModel->replaceColumn(idx.row(), modifiedColumn);
305     resizeStructureViewColumns();
306     updateTableConstraintsToolbarState();
307 }
308 
delColumn(const QModelIndex & idx)309 void TableWindow::delColumn(const QModelIndex& idx)
310 {
311     if (!idx.isValid())
312     {
313         qWarning() << "Called TableWindow::delColumn() with invalid index.";
314         return;
315     }
316 
317     SqliteCreateTable::Column* column = structureModel->getColumn(idx.row());
318 
319     QString msg = tr("Are you sure you want to delete column '%1'?", "table window").arg(column->name);
320     int btn = QMessageBox::question(this, tr("Delete column", "table window"), msg);
321     if (btn != QMessageBox::Yes)
322         return;
323 
324     structureModel->delColumn(idx.row());
325     resizeStructureViewColumns();
326     updateTableConstraintsToolbarState();
327 }
328 
executeStructureChanges()329 void TableWindow::executeStructureChanges()
330 {
331     QStringList sqls;
332 
333     createTable->rebuildTokens();
334     if (!existingTable)
335     {
336         sqls << createTable->detokenize();
337     }
338     else
339     {
340         if (tableModifier)
341             delete tableModifier;
342 
343         tableModifier = new TableModifier(db, database, table);
344         tableModifier->alterTable(createTable);
345 
346         if (tableModifier->hasMessages())
347         {
348             MessageListDialog dialog(tr("Following problems will take place while modifying the table.\n"
349                                         "Would you like to proceed?", "table window"));
350             dialog.setWindowTitle(tr("Table modification", "table window"));
351             for (const QString& error : tableModifier->getErrors())
352                 dialog.addError(error);
353 
354             for (const QString& warn : tableModifier->getWarnings())
355                 dialog.addWarning(warn);
356 
357             if (dialog.exec() != QDialog::Accepted)
358                 return;
359         }
360 
361         sqls = tableModifier->generateSqls();
362     }
363 
364     if (!CFG_UI.General.DontShowDdlPreview.get())
365     {
366         DdlPreviewDialog dialog(db, this);
367         dialog.setDdl(sqls);
368         if (dialog.exec() != QDialog::Accepted)
369             return;
370     }
371 
372     modifyingThisTable = true;
373     structureExecutor->setDb(db);
374     structureExecutor->setQueries(sqls);
375     structureExecutor->setDisableForeignKeys(true);
376     structureExecutor->setDisableObjectDropsDetection(true);
377     widgetCover->show();
378     structureExecutor->exec();
379 }
380 
updateAfterInit()381 void TableWindow::updateAfterInit()
382 {
383     updateStructureCommitState();
384     updateStructureToolbarState();
385     updateTableConstraintsToolbarState();
386     updateNewTableState();
387     updateIndexesState();
388     updateTriggersState();
389 }
390 
structureCurrentIndex() const391 QModelIndex TableWindow::structureCurrentIndex() const
392 {
393     return ui->structureView->selectionModel()->currentIndex();
394 }
395 
updateStructureToolbarState()396 void TableWindow::updateStructureToolbarState()
397 {
398     QItemSelectionModel *selModel = ui->structureView->selectionModel();
399     bool validIdx = false;
400     bool isFirst = false;
401     bool isLast = false;
402     if (selModel)
403     {
404         QModelIndex currIdx = selModel->currentIndex();
405         if (currIdx.isValid())
406         {
407             validIdx = true;
408             if (currIdx.row() == 0)
409                 isFirst = true;
410 
411             if (currIdx.row() == (structureModel->rowCount() - 1))
412                 isLast = true;
413         }
414     }
415 
416     actionMap[EDIT_COLUMN]->setEnabled(validIdx);
417     actionMap[DEL_COLUMN]->setEnabled(validIdx);
418     actionMap[MOVE_COLUMN_UP]->setEnabled(validIdx && !isFirst);
419     actionMap[MOVE_COLUMN_DOWN]->setEnabled(validIdx && !isLast);
420 }
421 
updateStructureCommitState()422 void TableWindow::updateStructureCommitState()
423 {
424     bool modified = isModified();
425     actionMap[COMMIT_STRUCTURE]->setEnabled(modified);
426     actionMap[ROLLBACK_STRUCTURE]->setEnabled(modified && existingTable);
427 }
428 
updateTableConstraintsToolbarState()429 void TableWindow::updateTableConstraintsToolbarState()
430 {
431     QItemSelectionModel *selModel = ui->tableConstraintsView->selectionModel();
432     bool anyColumn = structureModel && structureModel->rowCount() > 0;
433     bool validIdx = false;
434     bool isFirst = false;
435     bool isLast = false;
436     if (selModel)
437     {
438         QModelIndex currIdx = selModel->currentIndex();
439         if (currIdx.isValid())
440         {
441             validIdx = true;
442             if (currIdx.row() == 0)
443                 isFirst = true;
444 
445             if (currIdx.row() == (structureConstraintsModel->rowCount() - 1))
446                 isLast = true;
447         }
448     }
449 
450     actionMap[EDIT_TABLE_CONSTRAINT]->setEnabled(anyColumn && validIdx);
451     actionMap[DEL_TABLE_CONSTRAINT]->setEnabled(anyColumn && validIdx);
452     actionMap[MOVE_CONSTRAINT_UP]->setEnabled(anyColumn && validIdx && !isFirst);
453     actionMap[MOVE_CONSTRAINT_DOWN]->setEnabled(anyColumn && validIdx && !isLast);
454     actionMap[ADD_TABLE_PK]->setEnabled(!hasAnyPkDefined());
455 }
456 
setupDefShortcuts()457 void TableWindow::setupDefShortcuts()
458 {
459     // Widget context
460     setShortcutContext({
461                            REFRESH_STRUCTURE,
462                            REFRESH_INDEXES,
463                            REFRESH_TRIGGERS,
464                            ADD_COLUMN,
465                            EDIT_COLUMN,
466                            DEL_COLUMN,
467                            ADD_TABLE_CONSTRAINT,
468                            EDIT_TABLE_CONSTRAINT,
469                            DEL_TABLE_CONSTRAINT,
470                            ADD_INDEX,
471                            EDIT_INDEX,
472                            DEL_INDEX,
473                            ADD_TRIGGER,
474                            EDIT_TRIGGER,
475                            DEL_TRIGGER,
476                        },
477                        Qt::WidgetWithChildrenShortcut);
478 
479     BIND_SHORTCUTS(TableWindow, Action);
480 }
481 
executionSuccessful()482 void TableWindow::executionSuccessful()
483 {
484     dataLoaded = true;
485 }
486 
executionFailed(const QString & errorText)487 void TableWindow::executionFailed(const QString& errorText)
488 {
489     notifyError(tr("Could not load data for table %1. Error details: %2").arg(table).arg(errorText));
490 }
491 
initDbAndTable()492 void TableWindow::initDbAndTable()
493 {
494     for (int colIdx = 2; colIdx < 9; colIdx++)
495         ui->structureView->setItemDelegateForColumn(colIdx, constraintColumnsDelegate);
496 
497     ui->dbCombo->setCurrentDb(db);
498     if (existingTable)
499     {
500         dataModel->setDb(db);
501         dataModel->setDatabaseAndTable(database, table);
502         ui->dbCombo->setDisabled(true);
503     }
504 
505     ui->tableNameEdit->setText(table); // TODO no attached/temp db name support here
506 
507     if (structureModel)
508     {
509         delete structureModel;
510         structureModel = nullptr;
511     }
512 
513     if (structureConstraintsModel)
514     {
515         delete structureConstraintsModel;
516         structureConstraintsModel = nullptr;
517     }
518 
519     if (constraintTabModel)
520     {
521         delete constraintTabModel;
522         constraintTabModel = nullptr;
523     }
524 
525     structureModel = new TableStructureModel(this);
526     structureConstraintsModel = new TableConstraintsModel(this);
527     constraintTabModel = new ConstraintTabModel(this);
528 
529     // Columns model signals
530     connect(structureModel, SIGNAL(columnModified(QString,SqliteCreateTable::Column*)),
531             structureConstraintsModel, SLOT(columnModified(QString,SqliteCreateTable::Column*)));
532     connect(structureModel, SIGNAL(columnDeleted(QString)),
533             structureConstraintsModel, SLOT(columnDeleted(QString)));
534     connect(structureModel, SIGNAL(columnsOrderChanged()), this, SLOT(updateStructureToolbarState()));
535 
536     connect(structureModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
537     connect(structureModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(updateDdlTab()));
538     connect(structureModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
539     connect(structureModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateDdlTab()));
540     connect(structureModel, SIGNAL(modifiyStateChanged()), this, SIGNAL(modifyStatusChanged()));
541 
542     ui->structureView->setModel(structureModel);
543     ui->structureView->verticalHeader()->setDefaultSectionSize(ui->structureView->fontMetrics().height() + 8);
544 
545     // Constraints model signals
546     connect(structureConstraintsModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
547     connect(structureConstraintsModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(updateDdlTab()));
548     connect(structureConstraintsModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateDdlTab()));
549     connect(structureConstraintsModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateDdlTab()));
550 
551     connect(structureConstraintsModel, SIGNAL(modifiyStateChanged()), this, SIGNAL(modifyStatusChanged()));
552     connect(structureConstraintsModel, SIGNAL(constraintOrderChanged()), this, SLOT(updateTableConstraintsToolbarState()));
553 
554     ui->tableConstraintsView->setModel(structureConstraintsModel);
555     ui->tableConstraintsView->verticalHeader()->setDefaultSectionSize(ui->tableConstraintsView->fontMetrics().height() + 8);
556 
557     // Constraint tab model signals
558     connect(structureModel, SIGNAL(rowsInserted(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
559     connect(structureModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), constraintTabModel, SLOT(updateModel()));
560     connect(structureModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
561     connect(structureModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), constraintTabModel, SLOT(updateModel()));
562     connect(structureConstraintsModel, SIGNAL(rowsInserted(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
563     connect(structureConstraintsModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), constraintTabModel, SLOT(updateModel()));
564     connect(structureConstraintsModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), constraintTabModel, SLOT(updateModel()));
565     connect(structureConstraintsModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), constraintTabModel, SLOT(updateModel()));
566 
567     ui->constraintsView->setModel(constraintTabModel);
568 
569     connect(ui->withoutRowIdCheck, SIGNAL(clicked()), this, SLOT(withOutRowIdChanged()));
570 
571     parseDdl();
572     updateIndexes();
573     updateTriggers();
574 
575     // (Re)connect to DB signals
576     connect(db, SIGNAL(dbObjectDeleted(QString,QString,DbObjectType)), this, SLOT(checkIfTableDeleted(QString,QString,DbObjectType)));
577 
578     // Selection model is recreated when setModel() is called on the view
579     connect(ui->structureView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
580             this, SLOT(updateStructureToolbarState()));
581     connect(ui->tableConstraintsView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
582             this, SLOT(updateTableConstraintsToolbarState()));
583 }
584 
setupCoverWidget()585 void TableWindow::setupCoverWidget()
586 {
587     widgetCover = new WidgetCover(this);
588     widgetCover->initWithInterruptContainer();
589     widgetCover->hide();
590     connect(widgetCover, SIGNAL(cancelClicked()), structureExecutor, SLOT(interrupt()));
591 }
592 
parseDdl()593 void TableWindow::parseDdl()
594 {
595     if (existingTable)
596     {
597         SchemaResolver resolver(db);
598         SqliteQueryPtr parsedObject = resolver.getParsedObject(database, table, SchemaResolver::TABLE);
599         if (!parsedObject.dynamicCast<SqliteCreateTable>())
600         {
601             notifyError(tr("Could not process the %1 table correctly. Unable to open a table window.").arg(table));
602             invalid = true;
603             return;
604         }
605 
606         createTable = parsedObject.dynamicCast<SqliteCreateTable>();
607     }
608     else
609     {
610         createTable = SqliteCreateTablePtr::create();
611         createTable->table = table;
612     }
613     originalCreateTable = SqliteCreateTablePtr::create(*createTable);
614     structureModel->setCreateTable(createTable.data());
615     structureConstraintsModel->setCreateTable(createTable.data());
616     constraintTabModel->setCreateTable(createTable.data());
617     ui->withoutRowIdCheck->setChecked(!createTable->withOutRowId.isNull());
618     ui->tableConstraintsView->resizeColumnsToContents();
619     ui->structureView->resizeColumnsToContents();
620     ui->constraintsView->resizeColumnsToContents();
621 
622     updateStructureToolbarState();
623     updateTableConstraintsToolbarState();
624     updateDdlTab();
625 }
626 
createDbCombo()627 void TableWindow::createDbCombo()
628 {
629     ui->dbCombo->setFixedWidth(100);
630     ui->dbCombo->setToolTip(tr("Database"));
631     connect(ui->dbCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(dbChanged()));
632 }
633 
changeEvent(QEvent * e)634 void TableWindow::changeEvent(QEvent *e)
635 {
636     QWidget::changeEvent(e);
637     switch (e->type()) {
638         case QEvent::LanguageChange:
639             ui->retranslateUi(this);
640             break;
641         default:
642             break;
643     }
644 }
645 
saveSession()646 QVariant TableWindow::saveSession()
647 {
648     if (!db || DBLIST->isTemporary(db))
649         return QVariant();
650 
651     QHash<QString,QVariant> sessionValue;
652     sessionValue["table"] = table;
653     sessionValue["db"] = db->getName();
654     return sessionValue;
655 }
656 
restoreSession(const QVariant & sessionValue)657 bool TableWindow::restoreSession(const QVariant& sessionValue)
658 {
659     QHash<QString, QVariant> value = sessionValue.toHash();
660     if (value.size() == 0)
661     {
662         notifyWarn(tr("Could not restore window %1, because no database or table was stored in session for this window.").arg(value["title"].toString()));
663         return false;
664     }
665 
666     if (!value.contains("db") || !value.contains("table"))
667     {
668         notifyWarn(tr("Could not restore window '%1', because no database or table was stored in session for this window.").arg(value["title"].toString()));
669         return false;
670     }
671 
672     db = DBLIST->getByName(value["db"].toString());
673     if (!db || !db->isValid() || (!db->isOpen() && !db->open()))
674     {
675         notifyWarn(tr("Could not restore window '%1', because database %2 could not be resolved.").arg(value["title"].toString(), value["db"].toString()));
676         return false;
677     }
678 
679     table = value["table"].toString();
680     database = value["database"].toString();
681     SchemaResolver resolver(db);
682     if (!resolver.getTables(database).contains(table, Qt::CaseInsensitive))
683     {
684         notifyWarn(tr("Could not restore window '%1'', because the table %2 doesn't exist in the database %3.").arg(value["title"].toString(), table, db->getName()));
685         return false;
686     }
687 
688     initDbAndTable();
689     applyInitialTab();
690     return true;
691 }
692 
getIconNameForMdiWindow()693 Icon* TableWindow::getIconNameForMdiWindow()
694 {
695     return ICONS.TABLE;
696 }
697 
getTitleForMdiWindow()698 QString TableWindow::getTitleForMdiWindow()
699 {
700     QString dbSuffix = (!db ? "" : (" (" + db->getName() + ")"));
701     if (existingTable)
702         return table + dbSuffix;
703 
704     QStringList existingNames = MainWindow::getInstance()->getMdiArea()->getWindowTitles();
705     if (existingNames.contains(windowTitle()))
706         return windowTitle();
707 
708     // Generate new name
709     QString title = tr("New table %1").arg(newTableWindowNum++);
710     while (existingNames.contains(title))
711         title = tr("New table %1").arg(newTableWindowNum++);
712 
713     title += dbSuffix;
714     return title;
715 }
716 
getDb() const717 Db* TableWindow::getDb() const
718 {
719     return db;
720 }
721 
getTable() const722 QString TableWindow::getTable() const
723 {
724     return table;
725 }
726 
dbClosedFinalCleanup()727 void TableWindow::dbClosedFinalCleanup()
728 {
729     db = nullptr;
730     dataModel->setDb(nullptr);
731     structureExecutor->setDb(nullptr);
732 }
733 
checkIfTableDeleted(const QString & database,const QString & object,DbObjectType type)734 void TableWindow::checkIfTableDeleted(const QString& database, const QString& object, DbObjectType type)
735 {
736     UNUSED(database);
737 
738     // TODO uncomment below when dbnames are supported
739 //    if (this->database != database)
740 //        return;
741 
742     switch (type)
743     {
744         case DbObjectType::TABLE:
745             break;
746         case DbObjectType::INDEX:
747             checkIfIndexDeleted(object);
748             return;
749         case DbObjectType::TRIGGER:
750             checkIfTriggerDeleted(object);
751             return;
752         case DbObjectType::VIEW:
753             return;
754     }
755 
756     if (modifyingThisTable)
757         return;
758 
759     if (object.compare(table, Qt::CaseInsensitive) == 0)
760     {
761         dbClosedFinalCleanup();
762         getMdiWindow()->close();
763     }
764 }
765 
checkIfIndexDeleted(const QString & object)766 void TableWindow::checkIfIndexDeleted(const QString& object)
767 {
768     for (int i = 0, total = ui->indexList->rowCount(); i < total; ++i)
769     {
770         if (ui->indexList->item(i, 0)->text().compare(object, Qt::CaseInsensitive) == 0)
771         {
772             ui->indexList->removeRow(i);
773             return;
774         }
775     }
776 }
777 
checkIfTriggerDeleted(const QString & object)778 void TableWindow::checkIfTriggerDeleted(const QString& object)
779 {
780     for (int i = 0, total = ui->triggerList->rowCount(); i < total; ++i)
781     {
782         if (ui->triggerList->item(i, 0)->text().compare(object, Qt::CaseInsensitive) == 0)
783         {
784             ui->triggerList->removeRow(i);
785             return;
786         }
787     }
788 }
789 
refreshStructure()790 void TableWindow::refreshStructure()
791 {
792     parseDdl();
793     updateIndexes();
794     updateTriggers();
795 }
796 
commitStructure(bool skipWarning)797 void TableWindow::commitStructure(bool skipWarning)
798 {
799     if (!isModified())
800     {
801         qWarning() << "Called TableWindow::commitStructure(), but isModified() returned false.";
802         updateStructureCommitState();
803         return;
804     }
805 
806     if (!validate(skipWarning))
807         return;
808 
809     executeStructureChanges();
810 }
811 
changesSuccessfullyCommitted()812 void TableWindow::changesSuccessfullyCommitted()
813 {
814     modifyingThisTable = false;
815 
816     QStringList sqls = structureExecutor->getQueries();
817     CFG->addDdlHistory(sqls.join("\n"), db->getName(), db->getPath());
818 
819     widgetCover->hide();
820 
821     originalCreateTable = createTable;
822     structureModel->setCreateTable(createTable.data());
823     structureConstraintsModel->setCreateTable(createTable.data());
824     dataLoaded = false;
825 
826     QString oldTable = table;
827     database = createTable->database;
828     table = createTable->table;
829     existingTable = true;
830     initDbAndTable();
831     updateStructureCommitState();
832     updateNewTableState();
833     updateWindowTitle();
834 
835     NotifyManager* notifyManager = NotifyManager::getInstance();
836     if (oldTable.compare(table, Qt::CaseInsensitive) == 0 || oldTable.isEmpty())
837     {
838         notifyInfo(tr("Committed changes for table '%1' successfully.").arg(table));
839     }
840     else
841     {
842         notifyInfo(tr("Committed changes for table '%1' (named before '%2') successfully.").arg(table, oldTable));
843         notifyManager->renamed(db, database, oldTable, table);
844     }
845     notifyManager->modified(db, database, table);
846 
847     DBTREE->refreshSchema(db);
848 
849     if (tableModifier)
850     {
851         QList<QStringList> modifiedObjects = {
852             tableModifier->getModifiedTables(),
853             tableModifier->getModifiedIndexes(),
854             tableModifier->getModifiedTriggers(),
855             tableModifier->getModifiedViews()
856         };
857         for (const QStringList& objList : modifiedObjects)
858         {
859             for (const QString& obj : objList)
860             {
861                 if (obj.compare(oldTable, Qt::CaseInsensitive) == 0)
862                     continue;
863 
864                 notifyManager->modified(db, database, obj);
865             }
866         }
867     }
868 }
869 
changesFailedToCommit(int errorCode,const QString & errorText)870 void TableWindow::changesFailedToCommit(int errorCode, const QString& errorText)
871 {
872     qDebug() << "TableWindow::changesFailedToCommit:" << errorCode << errorText;
873 
874     modifyingThisTable = false;
875     widgetCover->hide();
876     notifyError(tr("Could not commit table structure. Error message: %1", "table window").arg(errorText));
877 }
878 
rollbackStructure()879 void TableWindow::rollbackStructure()
880 {
881     createTable = SqliteCreateTablePtr::create(*originalCreateTable.data());
882     structureModel->setCreateTable(createTable.data());
883     structureConstraintsModel->setCreateTable(createTable.data());
884     constraintTabModel->setCreateTable(createTable.data());
885     ui->tableNameEdit->setText(createTable->table);
886 
887     updateStructureCommitState();
888     updateStructureToolbarState();
889     updateTableConstraintsToolbarState();
890     updateDdlTab();
891 }
892 
resetAutoincrement()893 void TableWindow::resetAutoincrement()
894 {
895     if (!existingTable)
896         return;
897 
898     QMessageBox::StandardButton btn = QMessageBox::question(this, tr("Reset autoincrement"), tr("Are you sure you want to reset autoincrement value for table '%1'?")
899                                                             .arg(table));
900     if (btn != QMessageBox::Yes)
901         return;
902 
903     SqlQueryPtr res = db->exec("DELETE FROM sqlite_sequence WHERE name = ?;", {table});
904     if (res->isError())
905         notifyError(tr("An error occurred while trying to reset autoincrement value for table '%1': %2").arg(table, res->getErrorText()));
906     else
907         notifyInfo(tr("Autoincrement value for table '%1' has been reset successfully.").arg(table));
908 }
909 
addColumn()910 void TableWindow::addColumn()
911 {
912     SqliteCreateTable::Column column;
913     column.setParent(createTable.data());
914 
915     ColumnDialog columnDialog(db, this);
916     columnDialog.setColumn(&column);
917     if (hasAnyPkDefined())
918         columnDialog.disableConstraint(ConstraintDialog::Constraint::PK);
919 
920     if (columnDialog.exec() != QDialog::Accepted)
921         return;
922 
923     SqliteCreateTable::Column* modifiedColumn = columnDialog.getModifiedColumn();
924     structureModel->appendColumn(modifiedColumn);
925     ui->structureView->resizeColumnToContents(0);
926 
927     ui->structureView->setCurrentIndex(structureModel->index(structureModel->rowCount()-1, 0));
928     resizeStructureViewColumns();
929     updateTableConstraintsToolbarState();
930 }
931 
editColumn()932 void TableWindow::editColumn()
933 {
934     editColumn(structureCurrentIndex());
935 }
936 
delColumn()937 void TableWindow::delColumn()
938 {
939     QModelIndex idx = structureCurrentIndex();
940     delColumn(idx);
941 }
942 
moveColumnUp()943 void TableWindow::moveColumnUp()
944 {
945     QModelIndex idx = structureCurrentIndex();
946     if (!idx.isValid())
947     {
948         qWarning() << "Called TableWindow::moveColumnUp() with invalid index.";
949         return;
950     }
951 
952     structureModel->moveColumnUp(idx.row());
953 }
954 
moveColumnDown()955 void TableWindow::moveColumnDown()
956 {
957     QModelIndex idx = structureCurrentIndex();
958     if (!idx.isValid())
959     {
960         qWarning() << "Called TableWindow::moveColumnDown() with invalid index.";
961         return;
962     }
963 
964     structureModel->moveColumnDown(idx.row());
965 }
966 
967 
addConstraint(ConstraintDialog::Constraint mode)968 void TableWindow::addConstraint(ConstraintDialog::Constraint mode)
969 {
970     NewConstraintDialog dialog(mode, createTable.data(), db, this);
971     if (hasAnyPkDefined())
972         dialog.disableMode(ConstraintDialog::PK);
973 
974     if (dialog.exec() != QDialog::Accepted)
975         return;
976 
977     SqliteStatement* constrStmt = dialog.getConstraint();
978     SqliteCreateTable::Constraint* tableConstr = dynamic_cast<SqliteCreateTable::Constraint*>(constrStmt);
979     if (!tableConstr)
980     {
981         qCritical() << "Constraint returned from ConstraintDialog was not of table type, while we're trying to add table constraint.";
982         return;
983     }
984 
985     structureConstraintsModel->appendConstraint(tableConstr);
986     ui->tableConstraintsView->resizeColumnToContents(0);
987     ui->tableConstraintsView->resizeColumnToContents(1);
988     updateTableConstraintsToolbarState();
989 }
990 
validate(bool skipWarning)991 bool TableWindow::validate(bool skipWarning)
992 {
993     if (!existingTable && !skipWarning && ui->tableNameEdit->text().isEmpty())
994     {
995         int res = QMessageBox::warning(this, tr("Empty name"), tr("A blank name for the table is allowed in SQLite, but it is not recommended.\n"
996             "Are you sure you want to create a table with blank name?"), QMessageBox::Yes, QMessageBox::No);
997 
998         if (res != QMessageBox::Yes)
999             return false;
1000     }
1001 
1002     if (structureModel->rowCount() == 0)
1003     {
1004         notifyError(tr("Cannot create a table without at least one column."));
1005         return false;
1006     }
1007 
1008     if (ui->withoutRowIdCheck->isChecked())
1009     {
1010         bool hasPk = false;
1011         bool isPkAutoIncr = false;
1012 
1013         if (createTable->getConstraints(SqliteCreateTable::Constraint::PRIMARY_KEY).size() > 0)
1014             hasPk = true;
1015 
1016         SqliteCreateTable::Column::Constraint* colConstraint = nullptr;
1017         for (SqliteCreateTable::Column* column : createTable->columns)
1018         {
1019             colConstraint = column->getConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY);
1020             if (colConstraint)
1021             {
1022                 hasPk = true;
1023                 if (colConstraint->autoincrKw)
1024                     isPkAutoIncr = true;
1025             }
1026         }
1027 
1028         if (!hasPk)
1029         {
1030             notifyError(tr("Cannot create table %1, if it has no primary key defined."
1031                            " Either uncheck the %2, or define a primary key.").arg("WITHOUT ROWID", "WITHOUT ROWID"));
1032             return false;
1033         }
1034 
1035         if (isPkAutoIncr)
1036         {
1037             notifyError(tr("Cannot use autoincrement for primary key when %1 clause is used."
1038                            " Either uncheck the %2, or the autoincrement in a primary key.").arg("WITHOUT ROWID", "WITHOUT ROWID"));
1039             return false;
1040         }
1041     }
1042 
1043     return true;
1044 }
1045 
isModified() const1046 bool TableWindow::isModified() const
1047 {
1048     return (structureModel && structureModel->isModified()) ||
1049             (structureConstraintsModel && structureConstraintsModel->isModified()) ||
1050             (originalCreateTable &&
1051                 (originalCreateTable->table != ui->tableNameEdit->text() ||
1052                  originalCreateTable->withOutRowId != createTable->withOutRowId)
1053             ) ||
1054             !existingTable;
1055 }
1056 
indexColumnTokens(SqliteCreateIndexPtr index)1057 TokenList TableWindow::indexColumnTokens(SqliteCreateIndexPtr index)
1058 {
1059     if (index->indexedColumns.size() == 0)
1060         return TokenList();
1061 
1062     SqliteOrderBy* firstCol = index->indexedColumns.first();
1063     SqliteOrderBy* lastCol = index->indexedColumns.last();
1064     if (firstCol->tokens.size() == 0)
1065         return TokenList();
1066 
1067     if (lastCol->tokens.size() == 0)
1068         return TokenList();
1069 
1070     int firstIdx = index->tokens.indexOf(firstCol->tokens.first());
1071     int lastIdx = index->tokens.indexOf(lastCol->tokens.last());
1072 
1073     return index->tokens.mid(firstIdx, lastIdx-firstIdx+1);
1074 }
1075 
getCurrentIndex() const1076 QString TableWindow::getCurrentIndex() const
1077 {
1078     int row = ui->indexList->currentRow();
1079     QTableWidgetItem* item = ui->indexList->item(row, 0);
1080     if (!item)
1081         return QString();
1082 
1083     return item->text();
1084 }
1085 
getCurrentTrigger() const1086 QString TableWindow::getCurrentTrigger() const
1087 {
1088     int row = ui->triggerList->currentRow();
1089     QTableWidgetItem* item = ui->triggerList->item(row, 0);
1090     if (!item)
1091         return QString();
1092 
1093     return item->text();
1094 }
1095 
applyInitialTab()1096 void TableWindow::applyInitialTab()
1097 {
1098     if (existingTable && !table.isNull() && CFG_UI.General.OpenTablesOnData.get())
1099         ui->tabWidget->setCurrentIndex(getDataTabIdx());
1100     else
1101         ui->tabWidget->setCurrentIndex(getStructureTabIdx());
1102 }
1103 
resizeStructureViewColumns()1104 void TableWindow::resizeStructureViewColumns()
1105 {
1106     // Resize all except last one, to avoid shrinking the "extend to end" column.
1107     for (int c = 0, total = (ui->structureView->horizontalHeader()->count() - 1); c < total; ++c)
1108         ui->structureView->resizeColumnToContents(c);
1109 }
1110 
getDataTabIdx() const1111 int TableWindow::getDataTabIdx() const
1112 {
1113     return ui->tabWidget->indexOf(ui->dataTab);
1114 }
1115 
getStructureTabIdx() const1116 int TableWindow::getStructureTabIdx() const
1117 {
1118     return ui->tabWidget->indexOf(ui->structureTab);
1119 }
1120 
hasAnyPkDefined() const1121 bool TableWindow::hasAnyPkDefined() const
1122 {
1123     if (structureConstraintsModel)
1124     {
1125         for (int i = 0, total = structureConstraintsModel->rowCount(); i < total; ++i)
1126         {
1127             SqliteCreateTable::Constraint* constraint = structureConstraintsModel->getConstraint(i);
1128             if (constraint->type == SqliteCreateTable::Constraint::PRIMARY_KEY)
1129                 return true;
1130         }
1131     }
1132 
1133     if (structureModel)
1134     {
1135         for (int i = 0, total = structureModel->rowCount(); i < total; ++i)
1136         {
1137             SqliteCreateTable::Column* column = structureModel->getColumn(i);
1138             if (column->hasConstraint(SqliteCreateTable::Column::Constraint::PRIMARY_KEY))
1139                 return true;
1140         }
1141     }
1142 
1143     return false;
1144 }
1145 
updateDdlTab()1146 void TableWindow::updateDdlTab()
1147 {
1148     createTable->rebuildTokens();
1149     QString ddl = createTable->detokenize();
1150     if (createTable->columns.size() > 0)
1151         ddl = SQLITESTUDIO->getCodeFormatter()->format("sql", ddl, db);
1152 
1153     ui->ddlEdit->setPlainText(ddl);
1154 }
1155 
updateNewTableState()1156 void TableWindow::updateNewTableState()
1157 {
1158     for (QWidget* tab : {ui->dataTab, ui->constraintsTab, ui->indexesTab, ui->triggersTab})
1159         ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), existingTable);
1160 
1161     actionMap[EXPORT]->setEnabled(existingTable);
1162     actionMap[IMPORT]->setEnabled(existingTable);
1163     actionMap[POPULATE]->setEnabled(existingTable);
1164     actionMap[CREATE_SIMILAR]->setEnabled(existingTable);
1165     actionMap[RESET_AUTOINCREMENT]->setEnabled(existingTable);
1166     actionMap[REFRESH_STRUCTURE]->setEnabled(existingTable);
1167     actionMap[ADD_INDEX_STRUCT]->setEnabled(existingTable);
1168     actionMap[ADD_TRIGGER_STRUCT]->setEnabled(existingTable);
1169 }
1170 
addConstraint()1171 void TableWindow::addConstraint()
1172 {
1173     addConstraint(ConstraintDialog::UNKNOWN);
1174 }
1175 
editConstraint()1176 void TableWindow::editConstraint()
1177 {
1178     QModelIndex idx = ui->tableConstraintsView->currentIndex();
1179     editConstraint(idx);
1180 }
1181 
delConstraint()1182 void TableWindow::delConstraint()
1183 {
1184     QModelIndex idx = ui->tableConstraintsView->currentIndex();
1185     delConstraint(idx);
1186 }
1187 
editConstraint(const QModelIndex & idx)1188 void TableWindow::editConstraint(const QModelIndex& idx)
1189 {
1190     if (!idx.isValid())
1191     {
1192         addConstraint();
1193         return;
1194     }
1195 
1196     SqliteCreateTable::Constraint* constr = structureConstraintsModel->getConstraint(idx.row());
1197     ConstraintDialog dialog(ConstraintDialog::EDIT, constr, createTable.data(), db, this);
1198     if (dialog.exec() != QDialog::Accepted)
1199         return;
1200 
1201     structureConstraintsModel->constraintModified(idx.row());
1202     ui->tableConstraintsView->resizeColumnToContents(0);
1203     ui->tableConstraintsView->resizeColumnToContents(1);
1204 }
1205 
delConstraint(const QModelIndex & idx)1206 void TableWindow::delConstraint(const QModelIndex& idx)
1207 {
1208     if (!idx.isValid())
1209         return;
1210 
1211     SqliteCreateTable::Constraint* constr = structureConstraintsModel->getConstraint(idx.row());
1212 
1213     QString arg = constr->name.isNull() ? constr->typeString() : constr->name;
1214     QString msg = tr("Are you sure you want to delete table constraint '%1'?", "table window").arg(arg);
1215     int btn = QMessageBox::question(this, tr("Delete constraint", "table window"), msg);
1216     if (btn != QMessageBox::Yes)
1217         return;
1218 
1219     structureConstraintsModel->delConstraint(idx.row());
1220     ui->structureView->resizeColumnToContents(0);
1221     updateTableConstraintsToolbarState();
1222 }
1223 
moveConstraintUp()1224 void TableWindow::moveConstraintUp()
1225 {
1226     QModelIndex idx = ui->tableConstraintsView->currentIndex();
1227     if (!idx.isValid())
1228         return;
1229 
1230     structureConstraintsModel->moveConstraintUp(idx.row());
1231     updateTableConstraintsToolbarState();
1232     updateStructureCommitState();
1233 }
1234 
moveConstraintDown()1235 void TableWindow::moveConstraintDown()
1236 {
1237     QModelIndex idx = ui->tableConstraintsView->currentIndex();
1238     if (!idx.isValid())
1239         return;
1240 
1241     structureConstraintsModel->moveConstraintDown(idx.row());
1242     updateTableConstraintsToolbarState();
1243     updateStructureCommitState();
1244 }
1245 
addPk()1246 void TableWindow::addPk()
1247 {
1248     addConstraint(ConstraintDialog::PK);
1249 }
1250 
addFk()1251 void TableWindow::addFk()
1252 {
1253     addConstraint(ConstraintDialog::FK);
1254 }
1255 
addUnique()1256 void TableWindow::addUnique()
1257 {
1258     addConstraint(ConstraintDialog::UNIQUE);
1259 }
1260 
addCheck()1261 void TableWindow::addCheck()
1262 {
1263     addConstraint(ConstraintDialog::CHECK);
1264 }
1265 
exportTable()1266 void TableWindow::exportTable()
1267 {
1268     if (!ExportManager::isAnyPluginAvailable())
1269     {
1270         notifyError(tr("Cannot export, because no export plugin is loaded."));
1271         return;
1272     }
1273 
1274     ExportDialog dialog(this);
1275     dialog.setTableMode(db, table);
1276     dialog.exec();
1277 }
1278 
importTable()1279 void TableWindow::importTable()
1280 {
1281     if (!ImportManager::isAnyPluginAvailable())
1282     {
1283         notifyError(tr("Cannot import, because no import plugin is loaded."));
1284         return;
1285     }
1286 
1287     ImportDialog dialog(this);
1288     dialog.setDbAndTable(db, table);
1289     if (dialog.exec() == QDialog::Accepted && dataLoaded)
1290         ui->dataView->refreshData();
1291 }
1292 
populateTable()1293 void TableWindow::populateTable()
1294 {
1295     PopulateDialog dialog(this);
1296     dialog.setDbAndTable(db, table);
1297     if (dialog.exec() == QDialog::Accepted && dataLoaded)
1298         ui->dataView->refreshData();
1299 }
1300 
createSimilarTable()1301 void TableWindow::createSimilarTable()
1302 {
1303     DbObjectDialogs dialog(db);
1304     dialog.addTableSimilarTo(QString(), table);
1305 }
1306 
tabChanged(int newTab)1307 void TableWindow::tabChanged(int newTab)
1308 {
1309     if (tabsMoving)
1310         return;
1311 
1312     if (newTab == getDataTabIdx())
1313     {
1314         if (isModified())
1315         {
1316             int res = QMessageBox::question(this, tr("Uncommitted changes"),
1317                                             tr("There are uncommitted structure modifications. You cannot browse or edit data until you have "
1318                                                "table structure settled.\n"
1319                                                "Do you want to commit the structure, or do you want to go back to the structure tab?"),
1320                                             tr("Go back to structure tab"), tr("Commit modifications and browse data."));
1321 
1322             ui->tabWidget->setCurrentIndex(0);
1323             if (res == 1)
1324                 commitStructure(true);
1325 
1326             return;
1327         }
1328 
1329         if (!dataLoaded)
1330             ui->dataView->refreshData();
1331     }
1332 }
1333 
structureViewDoubleClicked(const QModelIndex & index)1334 void TableWindow::structureViewDoubleClicked(const QModelIndex &index)
1335 {
1336     editColumn(index);
1337 }
1338 
constraintsViewDoubleClicked(const QModelIndex & index)1339 void TableWindow::constraintsViewDoubleClicked(const QModelIndex &index)
1340 {
1341     editConstraint(index);
1342 }
1343 
nameChanged()1344 void TableWindow::nameChanged()
1345 {
1346     if (!createTable)
1347         return;
1348 
1349     createTable->table = ui->tableNameEdit->text();
1350     updateDdlTab();
1351 }
1352 
withOutRowIdChanged()1353 void TableWindow::withOutRowIdChanged()
1354 {
1355     if (!createTable)
1356         return;
1357 
1358     createTable->withOutRowId = ui->withoutRowIdCheck->isChecked() ? QStringLiteral("ROWID") : QString();
1359     updateDdlTab();
1360     emit modifyStatusChanged();
1361 }
1362 
addIndex()1363 void TableWindow::addIndex()
1364 {
1365     DbObjectDialogs dialogs(db, this);
1366     dialogs.addIndex(table);
1367     updateIndexes();
1368 }
1369 
editCurrentIndex()1370 void TableWindow::editCurrentIndex()
1371 {
1372     QString index = getCurrentIndex();
1373     if (index.isNull())
1374         return;
1375 
1376     DbObjectDialogs dialogs(db, this);
1377     dialogs.editIndex(index);
1378     updateIndexes();
1379 }
1380 
indexViewDoubleClicked(const QModelIndex & idx)1381 void TableWindow::indexViewDoubleClicked(const QModelIndex& idx)
1382 {
1383     if (!idx.isValid())
1384     {
1385         addIndex();
1386         return;
1387     }
1388 
1389     QString index = ui->indexList->item(idx.row(), 0)->text();
1390 
1391     DbObjectDialogs dialogs(db, this);
1392     dialogs.editIndex(index);
1393     updateIndexes();
1394 }
1395 
1396 
delIndex()1397 void TableWindow::delIndex()
1398 {
1399     QString index = getCurrentIndex();
1400     if (index.isNull())
1401         return;
1402 
1403     DbObjectDialogs dialogs(db, this);
1404     dialogs.dropObject(index);
1405     updateIndexes();
1406 }
1407 
addTrigger()1408 void TableWindow::addTrigger()
1409 {
1410     DbObjectDialogs dialogs(db, this);
1411     dialogs.addTriggerOnTable(table);
1412     updateTriggers();
1413 }
1414 
editTrigger()1415 void TableWindow::editTrigger()
1416 {
1417     QString trigger = getCurrentTrigger();
1418     if (trigger.isNull())
1419     {
1420         addTrigger();
1421         return;
1422     }
1423 
1424     DbObjectDialogs dialogs(db, this);
1425     dialogs.editTrigger(trigger);
1426     updateTriggers();
1427 }
1428 
triggerViewDoubleClicked(const QModelIndex & idx)1429 void TableWindow::triggerViewDoubleClicked(const QModelIndex& idx)
1430 {
1431     if (!idx.isValid())
1432     {
1433         addTrigger();
1434         return;
1435     }
1436 
1437     QString trigger = ui->triggerList->item(idx.row(), 0)->text();
1438 
1439     DbObjectDialogs dialogs(db, this);
1440     dialogs.editTrigger(trigger);
1441     updateTriggers();
1442 }
1443 
delTrigger()1444 void TableWindow::delTrigger()
1445 {
1446     QString trigger = getCurrentTrigger();
1447     if (trigger.isNull())
1448         return;
1449 
1450     DbObjectDialogs dialogs(db, this);
1451     dialogs.dropObject(trigger);
1452     updateTriggers();
1453 }
1454 
updateIndexesState()1455 void TableWindow::updateIndexesState()
1456 {
1457     bool editDel = ui->indexList->currentItem() != nullptr;
1458     actionMap[REFRESH_INDEXES]->setEnabled(existingTable);
1459     actionMap[ADD_INDEX]->setEnabled(existingTable);
1460     actionMap[EDIT_INDEX]->setEnabled(editDel);
1461     actionMap[DEL_INDEX]->setEnabled(editDel);
1462 }
1463 
updateTriggersState()1464 void TableWindow::updateTriggersState()
1465 {
1466     bool editDel = ui->triggerList->currentItem() != nullptr;
1467     actionMap[REFRESH_TRIGGERS]->setEnabled(existingTable);
1468     actionMap[ADD_TRIGGER]->setEnabled(existingTable);
1469     actionMap[EDIT_TRIGGER]->setEnabled(editDel);
1470     actionMap[DEL_TRIGGER]->setEnabled(editDel);
1471 }
1472 
nextTab()1473 void TableWindow::nextTab()
1474 {
1475     int idx = ui->tabWidget->currentIndex();
1476     idx++;
1477     ui->tabWidget->setCurrentIndex(idx);
1478 }
1479 
prevTab()1480 void TableWindow::prevTab()
1481 {
1482     int idx = ui->tabWidget->currentIndex();
1483     idx--;
1484     ui->tabWidget->setCurrentIndex(idx);
1485 }
1486 
updateIndexes()1487 void TableWindow::updateIndexes()
1488 {
1489     ui->indexList->clear();
1490 
1491     if (!db || !db->isValid())
1492         return;
1493 
1494     SchemaResolver resolver(db);
1495     resolver.setIgnoreSystemObjects(false);
1496     QList<SqliteCreateIndexPtr> indexes = resolver.getParsedIndexesForTable(database, table);
1497 
1498     ui->indexList->setColumnCount(4);
1499     ui->indexList->setRowCount(indexes.size());
1500     ui->indexList->setHorizontalHeaderLabels({
1501                                                  tr("Name", "table window indexes"),
1502                                                  tr("Unique", "table window indexes"),
1503                                                  tr("Columns", "table window indexes"),
1504                                                  tr("Partial index condition", "table window indexes"),
1505                                              });
1506 
1507     ui->indexList->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
1508 
1509     QTableWidgetItem* item = nullptr;
1510     int row = 0;
1511     for (SqliteCreateIndexPtr index : indexes)
1512     {
1513         item = new QTableWidgetItem(index->index);
1514         item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
1515         ui->indexList->setItem(row, 0, item);
1516 
1517         // TODO a delegate to make the checkbox in the center, or use setCellWidget()
1518         item = new QTableWidgetItem();
1519         item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
1520         item->setCheckState(index->uniqueKw ? Qt::Checked : Qt::Unchecked);
1521         ui->indexList->setItem(row, 1, item);
1522 
1523         item = new QTableWidgetItem(indexColumnTokens(index).detokenize());
1524         item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
1525         ui->indexList->setItem(row, 2, item);
1526 
1527         item = new QTableWidgetItem(index->where ? index->where->detokenize() : "");
1528         item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
1529         ui->indexList->setItem(row, 3, item);
1530 
1531         row++;
1532     }
1533 
1534     ui->indexList->resizeColumnsToContents();
1535     ui->indexList->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
1536     updateIndexesState();
1537 }
1538 
updateTriggers()1539 void TableWindow::updateTriggers()
1540 {
1541     if (!db || !db->isValid())
1542         return;
1543 
1544     SchemaResolver resolver(db);
1545     QList<SqliteCreateTriggerPtr> triggers = resolver.getParsedTriggersForTable(database, table);
1546 
1547     ui->triggerList->setColumnCount(4);
1548     ui->triggerList->setRowCount(triggers.size());
1549     ui->triggerList->horizontalHeader()->setMaximumSectionSize(200);
1550     ui->triggerList->setHorizontalHeaderLabels({
1551                                                  tr("Name", "table window triggers"),
1552                                                  tr("Event", "table window triggers"),
1553                                                  tr("Condition", "table window triggers"),
1554                                                  tr("Details", "table window triggers")
1555                                              });
1556 
1557     QTableWidgetItem* item = nullptr;
1558     QString timeAndEvent;
1559     int row = 0;
1560     for (SqliteCreateTriggerPtr trig : triggers)
1561     {
1562         item = new QTableWidgetItem(trig->trigger);
1563         item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
1564         ui->triggerList->setItem(row, 0, item);
1565 
1566         timeAndEvent = trig->tokensMap["trigger_time"].detokenize() + trig->tokensMap["trigger_event"].detokenize();
1567         item = new QTableWidgetItem(timeAndEvent);
1568         item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
1569         ui->triggerList->setItem(row, 1, item);
1570 
1571         item = new QTableWidgetItem(trig->precondition ? trig->precondition->detokenize().trimmed() : "");
1572         item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
1573         ui->triggerList->setItem(row, 2, item);
1574 
1575         item = new QTableWidgetItem(trig->tokensMap["trigger_cmd_list"].detokenize().trimmed());
1576         item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
1577         ui->triggerList->setItem(row, 3, item);
1578 
1579         row++;
1580     }
1581 
1582     ui->triggerList->resizeColumnsToContents();
1583     ui->triggerList->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch);
1584     updateTriggersState();
1585 }
1586 
editColumn(const QString & columnName)1587 void TableWindow::editColumn(const QString& columnName)
1588 {
1589     QModelIndex colIdx = structureModel->findColumn(columnName);
1590     if (!colIdx.isValid())
1591         return;
1592 
1593     editColumn(colIdx);
1594 }
1595 
delColumn(const QString & columnName)1596 void TableWindow::delColumn(const QString& columnName)
1597 {
1598     QModelIndex colIdx = structureModel->findColumn(columnName);
1599     if (!colIdx.isValid())
1600         return;
1601 
1602     delColumn(colIdx);
1603 }
1604 
updateTabsOrder()1605 void TableWindow::updateTabsOrder()
1606 {
1607     tabsMoving = true;
1608     ui->tabWidget->removeTab(getDataTabIdx());
1609     int idx = 1;
1610     if (CFG_UI.General.DataTabAsFirstInTables.get())
1611         idx = 0;
1612 
1613     ui->tabWidget->insertTab(idx, ui->dataTab, tr("Data"));
1614     tabsMoving = false;
1615 }
1616 
restoreSessionNextTime()1617 bool TableWindow::restoreSessionNextTime()
1618 {
1619     return existingTable && db && !DBLIST->isTemporary(db);
1620 }
1621 
getToolBar(int toolbar) const1622 QToolBar* TableWindow::getToolBar(int toolbar) const
1623 {
1624     switch (static_cast<ToolBar>(toolbar))
1625     {
1626         case TOOLBAR_STRUCTURE:
1627             return ui->structureToolBar;
1628         case TOOLBAR_INDEXES:
1629             return ui->indexToolBar;
1630         case TOOLBAR_TRIGGERS:
1631             return ui->triggerToolBar;
1632     }
1633     return nullptr;
1634 }
1635 
handleInitialFocus()1636 bool TableWindow::handleInitialFocus()
1637 {
1638     if (!existingTable)
1639     {
1640         ui->tableNameEdit->setFocus();
1641         return true;
1642     }
1643     return false;
1644 }
1645 
isUncommitted() const1646 bool TableWindow::isUncommitted() const
1647 {
1648     return ui->dataView->isUncommitted() || isModified();
1649 }
1650 
getQuitUncommittedConfirmMessage() const1651 QString TableWindow::getQuitUncommittedConfirmMessage() const
1652 {
1653     QString title = getMdiWindow()->windowTitle();
1654     if (ui->dataView->isUncommitted() && isModified())
1655         return tr("Table window \"%1\" has uncommitted structure modifications and data.").arg(title);
1656     else if (ui->dataView->isUncommitted())
1657         return tr("Table window \"%1\" has uncommitted data.").arg(title);
1658     else if (isModified())
1659         return tr("Table window \"%1\" has uncommitted structure modifications.").arg(title);
1660     else
1661     {
1662         qCritical() << "Unhandled message case in TableWindow::getQuitUncommittedConfirmMessage().";
1663         return QString();
1664     }
1665 }
1666 
useCurrentTableAsBaseForNew()1667 void TableWindow::useCurrentTableAsBaseForNew()
1668 {
1669     newTable();
1670     ui->tableNameEdit->clear();
1671     updateWindowTitle();
1672     ui->tableNameEdit->setFocus();
1673     updateAfterInit();
1674 }
1675 
getAssociatedDb() const1676 Db* TableWindow::getAssociatedDb() const
1677 {
1678     return db;
1679 }
1680 
updateFont()1681 void TableWindow::updateFont()
1682 {
1683     QFont f = CFG_UI.Fonts.DataView.get();
1684     QFontMetrics fm(f);
1685 
1686     QTableView* views[] = {ui->structureView, ui->tableConstraintsView, ui->indexList, ui->triggerList};
1687     for (QTableView* view : views)
1688     {
1689         view->setFont(f);
1690         view->horizontalHeader()->setFont(f);
1691         view->verticalHeader()->setFont(f);
1692         view->verticalHeader()->setDefaultSectionSize(fm.height() + 4);
1693     }
1694 }
1695 
dbChanged()1696 void TableWindow::dbChanged()
1697 {
1698     disconnect(db, SIGNAL(dbObjectDeleted(QString,QString,DbObjectType)), this, SLOT(checkIfTableDeleted(QString,QString,DbObjectType)));
1699 
1700     db = ui->dbCombo->currentDb();
1701     dataModel->setDb(db);
1702 
1703     connect(db, SIGNAL(dbObjectDeleted(QString,QString,DbObjectType)), this, SLOT(checkIfTableDeleted(QString,QString,DbObjectType)));
1704 }
1705