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