1 #include "dataview.h"
2 #include "datagrid/sqltablemodel.h"
3 #include "datagrid/sqlquerymodel.h"
4 #include "datagrid/sqlqueryview.h"
5 #include "formview.h"
6 #include "common/extlineedit.h"
7 #include "mainwindow.h"
8 #include "statusfield.h"
9 #include "common/intvalidator.h"
10 #include "common/extaction.h"
11 #include "iconmanager.h"
12 #include "themetuner.h"
13 #include "uiconfig.h"
14 #include "datagrid/sqlqueryitem.h"
15 #include "common/widgetcover.h"
16 #include "common/unused.h"
17 #include <QDebug>
18 #include <QHeaderView>
19 #include <QVBoxLayout>
20 #include <QToolBar>
21 #include <QLabel>
22 #include <QAction>
23 #include <QTime>
24 #include <QStyleFactory>
25 #include <QLineEdit>
26 #include <QSizePolicy>
27 #include <QScrollBar>
28
29 CFG_KEYS_DEFINE(DataView)
30 DataView::TabsPosition DataView::tabsPosition;
31 QHash<DataView::Action,QAction*> DataView::staticActions;
32 QHash<DataView::ActionGroup,QActionGroup*> DataView::staticActionGroups;
33 static const char* DATA_VIEW_FILTER_PROP = "filter";
34
DataView(QWidget * parent)35 DataView::DataView(QWidget *parent) :
36 QTabWidget(parent)
37 {
38 }
39
init(SqlQueryModel * model)40 void DataView::init(SqlQueryModel* model)
41 {
42 createContents();
43
44 this->model = model;
45 this->model->setView(gridView);
46
47 rowCountLabel = new QLabel();
48 formViewRowCountLabel = new QLabel();
49 formViewCurrentRowLabel = new QLabel();
50
51 initWidgetCover();
52 initFormView();
53 initPageEdit();
54 initFilter();
55 initActions();
56 initUpdates();
57 initSlots();
58 updateTabsMode();
59 }
60
initUpdates()61 void DataView::initUpdates()
62 {
63 updatePageEdit();
64 updateFormNavigationState();
65 updateGridNavigationState();
66 updateCommitRollbackActions(false);
67 }
68
initSlots()69 void DataView::initSlots()
70 {
71 connect(model, SIGNAL(executionSuccessful()), this, SLOT(executionSuccessful()));
72 connect(model, SIGNAL(loadingEnded(bool)), this, SLOT(dataLoadingEnded(bool)));
73 connect(model, SIGNAL(commitStatusChanged(bool)), this, SLOT(updateCommitRollbackActions(bool)));
74 connect(gridView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
75 model, SLOT(updateSelectiveCommitRollbackActions(QItemSelection,QItemSelection)));
76 connect(model, SIGNAL(selectiveCommitStatusChanged(bool)), this, SLOT(updateSelectiveCommitRollbackActions(bool)));
77 connect(model, SIGNAL(executionStarted()), gridView, SLOT(executionStarted()));
78 connect(model, SIGNAL(loadingEnded(bool)), gridView, SLOT(executionEnded()));
79 connect(model, SIGNAL(totalRowsAndPagesAvailable()), this, SLOT(totalRowsAndPagesAvailable()));
80 connect(gridView->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(columnsHeaderClicked(int)));
81 connect(this, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
82 connect(model, SIGNAL(itemEditionEnded(SqlQueryItem*)), this, SLOT(adjustColumnWidth(SqlQueryItem*)));
83 connect(gridView, SIGNAL(scrolledBy(int, int)), this, SLOT(syncFilterScrollPosition()));
84 connect(gridView->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(resizeFilter(int, int, int)));
85 }
86
initFormView()87 void DataView::initFormView()
88 {
89 formView = new FormView();
90 formWidget->layout()->addWidget(formView);
91 formView->setModel(model);
92 formView->setGridView(gridView);
93 connect(formView, SIGNAL(commitStatusChanged()), this, SLOT(updateFormCommitRollbackActions()));
94 connect(formView, SIGNAL(currentRowChanged()), this, SLOT(updateFormNavigationState()));
95 updateCurrentFormViewRow();
96 }
97
initFilter()98 void DataView::initFilter()
99 {
100 filterEdit = new ExtLineEdit();
101 filterEdit->setExpandingMinWidth(100);
102 filterEdit->setExpandingMaxWidth(200);
103 filterEdit->setExpanding(true);
104 filterEdit->setClearButtonEnabled(true);
105 filterEdit->setPlaceholderText(tr("Filter data", "data view"));
106 connect(filterEdit, SIGNAL(valueErased()), this, SLOT(resetFilter()));
107 connect(filterEdit, SIGNAL(returnPressed()), this, SLOT(applyFilter()));
108 }
109
createContents()110 void DataView::createContents()
111 {
112 gridWidget = new QWidget();
113 formWidget = new QWidget();
114 addTab(gridWidget, tr("Grid view"));
115 addTab(formWidget, tr("Form view"));
116
117 QVBoxLayout* vbox = new QVBoxLayout();
118 gridWidget->setLayout(vbox);
119
120 vbox = new QVBoxLayout();
121 formWidget->setLayout(vbox);
122
123 gridToolBar = new QToolBar();
124 formToolBar = new QToolBar();
125 gridWidget->layout()->addWidget(gridToolBar);
126 formWidget->layout()->addWidget(formToolBar);
127
128 createFilterPanel();
129 gridWidget->layout()->addWidget(perColumnAreaParent);
130
131 THEME_TUNER->manageCompactLayout({
132 gridWidget,
133 formWidget
134 });
135
136 #ifdef Q_OS_MACX
137 QStyle *fusion = QStyleFactory::create("Fusion");
138 gridToolBar->setStyle(fusion);
139 formToolBar->setStyle(fusion);
140 #endif
141
142 gridView = new SqlQueryView();
143 gridView->setCornerButtonEnabled(true);
144 gridView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
145 gridWidget->layout()->addWidget(gridView);
146 }
147
createFilterPanel()148 void DataView::createFilterPanel()
149 {
150 perColumnAreaParent = new QWidget();
151 perColumnAreaParent->setVisible(false);
152 perColumnAreaParent->setLayout(new QHBoxLayout());
153 perColumnAreaParent->layout()->setSpacing(0);
154 perColumnAreaParent->layout()->setMargin(0);
155 perColumnAreaParent->setFixedHeight(0);
156
157 filterLeftSpacer = new QWidget();
158 perColumnAreaParent->layout()->addWidget(filterLeftSpacer);
159
160 perColumnFilterArea = new QScrollArea();
161 perColumnFilterArea->setFixedHeight(0);
162 perColumnFilterArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
163 perColumnFilterArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
164 perColumnFilterArea->setFrameShape(QFrame::NoFrame);
165 perColumnWidget = new QWidget();
166 perColumnWidget->setLayout(new QHBoxLayout());
167 perColumnWidget->layout()->setSizeConstraint(QLayout::SetFixedSize);
168 perColumnWidget->layout()->setSpacing(0);
169 perColumnWidget->layout()->setMargin(0);
170 perColumnWidget->setAutoFillBackground(true);
171 perColumnWidget->setBackgroundRole(QPalette::Window);
172 perColumnFilterArea->setWidget(perColumnWidget);
173 perColumnAreaParent->layout()->addWidget(perColumnFilterArea);
174
175 filterRightSpacer = new QWidget();
176 perColumnAreaParent->layout()->addWidget(filterRightSpacer);
177 }
178
initPageEdit()179 void DataView::initPageEdit()
180 {
181 pageEdit = new ExtLineEdit();
182 pageValidator = new IntValidator(1, 1, pageEdit);
183 pageValidator->setDefaultValue(1);
184 pageEdit->setAlignment(Qt::AlignCenter);
185 pageEdit->setValidator(pageValidator);
186 pageEdit->setExpanding(true);
187 pageEdit->setExpandingMinWidth(20);
188 connect(pageEdit, SIGNAL(editingFinished()), this, SLOT(pageEntered()));
189 }
190
initWidgetCover()191 void DataView::initWidgetCover()
192 {
193 widgetCover = new WidgetCover(this);
194 widgetCover->initWithProgressBarOnly("%v / %m");
195 connect(model, SIGNAL(aboutToCommit(int)), this, SLOT(coverForGridCommit(int)));
196 connect(model, SIGNAL(committingStepFinished(int)), this, SLOT(updateGridCommitCover(int)));
197 connect(model, SIGNAL(commitFinished()), this, SLOT(hideGridCommitCover()));
198 }
199
createActions()200 void DataView::createActions()
201 {
202 bool rowInserting = model->features().testFlag(SqlQueryModel::INSERT_ROW);
203 bool rowDeleting = model->features().testFlag(SqlQueryModel::DELETE_ROW);
204
205 // Grid actions
206 createAction(REFRESH_DATA, ICONS.RELOAD, tr("Refresh table data", "data view"), this, SLOT(refreshData()), gridToolBar, gridView);
207 gridToolBar->addSeparator();
208 if (rowInserting)
209 {
210 gridToolBar->addAction(gridView->getAction(SqlQueryView::INSERT_ROW));
211 attachActionInMenu(gridView->getAction(SqlQueryView::INSERT_ROW), gridView->getAction(SqlQueryView::INSERT_MULTIPLE_ROWS), gridToolBar);
212 addSeparatorInMenu(gridView->getAction(SqlQueryView::INSERT_ROW), gridToolBar);
213 for (Action act : {INSERT_ROW_BEFORE, INSERT_ROW_AFTER, INSERT_ROW_AT_END})
214 attachActionInMenu(gridView->getAction(SqlQueryView::INSERT_ROW), staticActions[act], gridToolBar);
215 }
216
217 if (rowDeleting)
218 gridToolBar->addAction(gridView->getAction(SqlQueryView::DELETE_ROW));
219
220 gridToolBar->addAction(gridView->getAction(SqlQueryView::COMMIT));
221 gridToolBar->addAction(gridView->getAction(SqlQueryView::ROLLBACK));
222 gridToolBar->addSeparator();
223 createAction(FIRST_PAGE, ICONS.PAGE_FIRST, tr("First page", "data view"), this, SLOT(firstPage()), gridToolBar);
224 createAction(PREV_PAGE, ICONS.PAGE_PREV, tr("Previous page", "data view"), this, SLOT(prevPage()), gridToolBar);
225 actionMap[PAGE_EDIT] = gridToolBar->addWidget(pageEdit);
226 createAction(NEXT_PAGE, ICONS.PAGE_NEXT, tr("Next page", "data view"), this, SLOT(nextPage()), gridToolBar);
227 createAction(LAST_PAGE, ICONS.PAGE_LAST, tr("Last page", "data view"), this, SLOT(lastPage()), gridToolBar);
228 gridToolBar->addSeparator();
229 if (model->features().testFlag(SqlQueryModel::FILTERING))
230 createFilteringActions();
231
232 actionMap[GRID_TOTAL_ROWS] = gridToolBar->addWidget(rowCountLabel);
233
234 noConfigShortcutActions << GRID_TOTAL_ROWS << FILTER_VALUE;
235
236 createAction(SELECTIVE_COMMIT, ICONS.COMMIT, tr("Commit changes for selected cells", "data view"), this, SLOT(selectiveCommitGrid()), this);
237 createAction(SELECTIVE_ROLLBACK, ICONS.ROLLBACK, tr("Rollback changes for selected cells", "data view"), this, SLOT(selectiveRollbackGrid()), this);
238 createAction(SHOW_GRID_VIEW, tr("Show grid view of results", "sql editor"), this, SLOT(showGridView()), this);
239 createAction(SHOW_FORM_VIEW, tr("Show form view of results", "sql editor"), this, SLOT(showFormView()), this);
240
241 connect(gridView, SIGNAL(requestForRowInsert()), this, SLOT(insertRow()));
242 connect(gridView, SIGNAL(requestForMultipleRowInsert()), this, SLOT(insertMultipleRows()));
243 connect(gridView, SIGNAL(requestForRowDelete()), this, SLOT(deleteRow()));
244
245
246 // Form view actions
247 if (rowInserting)
248 formToolBar->addAction(formView->getAction(FormView::INSERT_ROW));
249
250 if (rowDeleting)
251 formToolBar->addAction(formView->getAction(FormView::DELETE_ROW));
252
253 if (rowInserting || rowDeleting)
254 formToolBar->addSeparator();
255
256 formToolBar->addAction(formView->getAction(FormView::COMMIT));
257 formToolBar->addAction(formView->getAction(FormView::ROLLBACK));
258 formToolBar->addSeparator();
259 formToolBar->addAction(formView->getAction(FormView::FIRST_ROW));
260 formToolBar->addAction(formView->getAction(FormView::PREV_ROW));
261 formToolBar->addAction(formView->getAction(FormView::NEXT_ROW));
262 formToolBar->addAction(formView->getAction(FormView::LAST_ROW));
263 formToolBar->addSeparator();
264 actionMap[FORM_TOTAL_ROWS] = formToolBar->addWidget(formViewRowCountLabel);
265 formToolBar->addSeparator();
266 actionMap[FORM_CURRENT_ROW] = formToolBar->addWidget(formViewCurrentRowLabel);
267
268 noConfigShortcutActions << FORM_TOTAL_ROWS;
269
270 connect(formView, SIGNAL(requestForCommit()), this, SLOT(commitForm()));
271 connect(formView, SIGNAL(requestForRollback()), this, SLOT(rollbackForm()));
272 connect(formView, SIGNAL(requestForFirstRow()), this, SLOT(firstRow()));
273 connect(formView, SIGNAL(requestForPrevRow()), this, SLOT(prevRow()));
274 connect(formView, SIGNAL(requestForNextRow()), this, SLOT(nextRow()));
275 connect(formView, SIGNAL(requestForLastRow()), this, SLOT(lastRow()));
276 connect(formView, SIGNAL(requestForRowInsert()), this, SLOT(insertRow()));
277 connect(formView, SIGNAL(requestForRowDelete()), this, SLOT(deleteRow()));
278
279 // Actions for grid menu only
280 gridView->addAdditionalAction(staticActions[TABS_ON_TOP]);
281 gridView->addAdditionalAction(staticActions[TABS_AT_BOTTOM]);
282 connect(staticActions[TABS_ON_TOP], SIGNAL(triggered()), this, SLOT(updateTabsMode()));
283 connect(staticActions[TABS_AT_BOTTOM], SIGNAL(triggered()), this, SLOT(updateTabsMode()));
284 }
285
setupDefShortcuts()286 void DataView::setupDefShortcuts()
287 {
288 // Widget context
289 setShortcutContext({REFRESH_DATA, SHOW_GRID_VIEW, SHOW_FORM_VIEW}, Qt::WidgetWithChildrenShortcut);
290
291 BIND_SHORTCUTS(DataView, Action);
292 }
293
resizeColumnsInitiallyToContents()294 void DataView::resizeColumnsInitiallyToContents()
295 {
296 SqlQueryModel *model = gridView->getModel();
297 int cols = model->columnCount();
298 gridView->setIgnoreColumnWidthChanges(true);
299 gridView->resizeColumnsToContents();
300 int wd;
301 int desiredWidth = -1;
302 for (int i = 0; i < cols ; i++)
303 {
304 desiredWidth = model->getDesiredColumnWidth(i);
305 wd = gridView->columnWidth(i);
306
307 if (desiredWidth > -1 && wd != desiredWidth)
308 {
309 gridView->setColumnWidth(i, desiredWidth);
310 continue;
311 }
312
313 if (wd > CFG_UI.General.MaxInitialColumnWith.get())
314 {
315 gridView->setColumnWidth(i, CFG_UI.General.MaxInitialColumnWith.get());
316 }
317 else if (wd < 60)
318 {
319 gridView->setColumnWidth(i, 60);
320 }
321 }
322 gridView->setIgnoreColumnWidthChanges(false);
323 }
324
createStaticActions()325 void DataView::createStaticActions()
326 {
327 // Tabs position actions
328 staticActions[TABS_ON_TOP] = new ExtAction(ICONS.TABS_ON_TOP, tr("Tabs on top", "data view"), MainWindow::getInstance());
329 staticActions[TABS_AT_BOTTOM] = new ExtAction(ICONS.TABS_AT_BOTTOM, tr("Tabs at bottom", "data view"), MainWindow::getInstance());
330
331 staticActionGroups[ActionGroup::TABS_POSITION] = new QActionGroup(MainWindow::getInstance());
332 staticActionGroups[ActionGroup::TABS_POSITION]->addAction(staticActions[TABS_ON_TOP]);
333 staticActionGroups[ActionGroup::TABS_POSITION]->addAction(staticActions[TABS_AT_BOTTOM]);
334
335 connect(staticActions[TABS_ON_TOP], &QAction::triggered, [=]()
336 {
337 tabsPosition = TabsPosition::TOP;
338 CFG_UI.General.DataViewTabs.set("TOP");
339 });
340 connect(staticActions[TABS_AT_BOTTOM], &QAction::triggered, [=]()
341 {
342 tabsPosition = TabsPosition::BOTTOM;
343 CFG_UI.General.DataViewTabs.set("BOTTOM");
344 });
345
346 staticActions[TABS_ON_TOP]->setCheckable(true);
347 staticActions[TABS_AT_BOTTOM]->setCheckable(true);
348 if (tabsPosition == TabsPosition::TOP)
349 staticActions[TABS_ON_TOP]->setChecked(true);
350 else
351 staticActions[TABS_AT_BOTTOM]->setChecked(true);
352
353 // Insert row positioning
354 staticActions[INSERT_ROW_BEFORE] = new ExtAction(tr("Place new rows above selected row", "data view"), MainWindow::getInstance());
355 staticActions[INSERT_ROW_AFTER] = new ExtAction(tr("Place new rows below selected row", "data view"), MainWindow::getInstance());
356 staticActions[INSERT_ROW_AT_END] = new ExtAction(tr("Place new rows at the end of the data view", "data view"), MainWindow::getInstance());
357
358 staticActionGroups[ActionGroup::INSERT_ROW_POSITIONING] = new QActionGroup(MainWindow::getInstance());
359 staticActionGroups[ActionGroup::INSERT_ROW_POSITIONING]->addAction(staticActions[INSERT_ROW_BEFORE]);
360 staticActionGroups[ActionGroup::INSERT_ROW_POSITIONING]->addAction(staticActions[INSERT_ROW_AFTER]);
361 staticActionGroups[ActionGroup::INSERT_ROW_POSITIONING]->addAction(staticActions[INSERT_ROW_AT_END]);
362
363 connect(staticActions[INSERT_ROW_BEFORE], &QAction::triggered, [=]()
364 {
365 CFG_UI.General.InsertRowPlacement.set(Cfg::BEFORE_CURRENT);
366 });
367 connect(staticActions[INSERT_ROW_AFTER], &QAction::triggered, [=]()
368 {
369 CFG_UI.General.InsertRowPlacement.set(Cfg::AFTER_CURRENT);
370 });
371 connect(staticActions[INSERT_ROW_AT_END], &QAction::triggered, [=]()
372 {
373 CFG_UI.General.InsertRowPlacement.set(Cfg::AT_THE_END);
374 });
375
376 staticActions[INSERT_ROW_BEFORE]->setCheckable(true);
377 staticActions[INSERT_ROW_AFTER]->setCheckable(true);
378 staticActions[INSERT_ROW_AT_END]->setCheckable(true);
379 switch (static_cast<Cfg::InsertRowPlacement>(CFG_UI.General.InsertRowPlacement.get()))
380 {
381 case Cfg::BEFORE_CURRENT:
382 staticActions[INSERT_ROW_BEFORE]->setChecked(true);
383 break;
384 case Cfg::AFTER_CURRENT:
385 staticActions[INSERT_ROW_AFTER]->setChecked(true);
386 break;
387 case Cfg::AT_THE_END:
388 staticActions[INSERT_ROW_AT_END]->setChecked(true);
389 break;
390 }
391 }
392
loadTabsMode()393 void DataView::loadTabsMode()
394 {
395 QString valString = CFG_UI.General.DataViewTabs.get();
396 if (valString == "TOP")
397 tabsPosition = TabsPosition::TOP;
398 else if (valString == "BOTTOM")
399 tabsPosition = TabsPosition::BOTTOM;
400 }
401
goToFormRow(IndexModifier idxMod)402 void DataView::goToFormRow(IndexModifier idxMod)
403 {
404 if (formView->isModified())
405 formView->copyDataToGrid();
406
407 int row = gridView->getCurrentIndex().row();
408
409 switch (idxMod)
410 {
411 case IndexModifier::FIRST:
412 row = 0;
413 break;
414 case IndexModifier::PREV:
415 row--;
416 break;
417 case IndexModifier::NEXT:
418 row++;
419 break;
420 case IndexModifier::LAST:
421 row = model->rowCount() - 1;
422 break;
423 }
424
425 QModelIndex newRowIdx = model->index(row, 0);
426 if (!newRowIdx.isValid())
427 return;
428
429 gridView->setCurrentIndex(newRowIdx);
430 model->loadFullDataForEntireRow(row);
431 formView->updateFromGrid();
432 updateCurrentFormViewRow();
433 }
434
setNavigationState(bool enabled)435 void DataView::setNavigationState(bool enabled)
436 {
437 navigationState = enabled;
438 updateNavigationState();
439 setFormViewEnabled(enabled);
440 }
441
updateNavigationState()442 void DataView::updateNavigationState()
443 {
444 updateGridNavigationState();
445 updateFormNavigationState();
446 }
447
updateGridNavigationState()448 void DataView::updateGridNavigationState()
449 {
450 int page = model->getCurrentPage();
451 bool prevResultsAvailable = page > 0;
452 bool nextResultsAvailable = (page + 1) < model->getTotalPages();
453 bool reloadResultsAvailable = model->canReload();
454 bool pageNumEditAvailable = (prevResultsAvailable || nextResultsAvailable);
455
456 actionMap[PAGE_EDIT]->setEnabled(navigationState && totalPagesAvailable && pageNumEditAvailable);
457 actionMap[REFRESH_DATA]->setEnabled(navigationState && reloadResultsAvailable);
458 actionMap[NEXT_PAGE]->setEnabled(navigationState && totalPagesAvailable && nextResultsAvailable);
459 actionMap[LAST_PAGE]->setEnabled(navigationState && totalPagesAvailable && nextResultsAvailable);
460 actionMap[PREV_PAGE]->setEnabled(navigationState && totalPagesAvailable && prevResultsAvailable);
461 actionMap[FIRST_PAGE]->setEnabled(navigationState && totalPagesAvailable && prevResultsAvailable);
462 }
463
updateFormNavigationState()464 void DataView::updateFormNavigationState()
465 {
466 int row = gridView->getCurrentIndex().row();
467 int lastRow = model->rowCount() - 1;
468 bool nextRowAvailable = row < lastRow;
469 bool prevRowAvailable = row > 0;
470
471 formView->getAction(FormView::NEXT_ROW)->setEnabled(navigationState && nextRowAvailable);
472 formView->getAction(FormView::PREV_ROW)->setEnabled(navigationState && prevRowAvailable);
473
474 // We changed row in form view, this one might be already modified and be capable for commit/rollback
475 updateFormCommitRollbackActions();
476 }
477
updateFormCommitRollbackActions()478 void DataView::updateFormCommitRollbackActions()
479 {
480 bool enabled = formView->isModified();
481 formView->getAction(FormView::COMMIT)->setEnabled(enabled);
482 formView->getAction(FormView::ROLLBACK)->setEnabled(enabled);
483 uncommittedForm = enabled;
484 }
485
showGridView()486 void DataView::showGridView()
487 {
488 setCurrentIndex(0);
489 }
490
showFormView()491 void DataView::showFormView()
492 {
493 setCurrentIndex(1);
494 updateCurrentFormViewRow();
495 }
496
updateTabsMode()497 void DataView::updateTabsMode()
498 {
499 switch (tabsPosition)
500 {
501 case DataView::TabsPosition::TOP:
502 setTabPosition(TabPosition::North);
503 break;
504 case DataView::TabsPosition::BOTTOM:
505 setTabPosition(TabPosition::South);
506 break;
507 }
508 }
509
filterModeSelected()510 void DataView::filterModeSelected()
511 {
512 QAction* modeAction = dynamic_cast<QAction*>(sender());
513 filterMode = static_cast<FilterMode>(modeAction->property(DATA_VIEW_FILTER_PROP).toInt());
514 actionMap[FILTER]->setIcon(modeAction->icon());
515 }
516
coverForGridCommit(int total)517 void DataView::coverForGridCommit(int total)
518 {
519 if (total <= 3)
520 return;
521
522 widgetCover->displayProgress(total);
523 widgetCover->show();
524 QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
525 }
526
updateGridCommitCover(int value)527 void DataView::updateGridCommitCover(int value)
528 {
529 if (!widgetCover->isVisible() || (value % 10) != 0)
530 return;
531
532 widgetCover->setProgress(value);
533 QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
534 }
535
hideGridCommitCover()536 void DataView::hideGridCommitCover()
537 {
538 if (!widgetCover->isVisible())
539 return;
540
541 widgetCover->hide();
542 QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
543 }
544
adjustColumnWidth(SqlQueryItem * item)545 void DataView::adjustColumnWidth(SqlQueryItem* item)
546 {
547 if (!item)
548 return;
549
550 int col = item->column();
551 if (model->getDesiredColumnWidth(col) > -1)
552 return;
553
554 gridView->resizeColumnToContents(col);
555 if (gridView->columnWidth(col) > CFG_UI.General.MaxInitialColumnWith.get())
556 gridView->setColumnWidth(col, CFG_UI.General.MaxInitialColumnWith.get());
557 }
558
syncFilterScrollPosition()559 void DataView::syncFilterScrollPosition()
560 {
561 perColumnFilterArea->horizontalScrollBar()->setValue(gridView->horizontalScrollBar()->value());
562 }
563
resizeFilter(int section,int oldSize,int newSize)564 void DataView::resizeFilter(int section, int oldSize, int newSize)
565 {
566 UNUSED(oldSize);
567 if (filterInputs.isEmpty())
568 return;
569
570 if (filterInputs.size() <= section)
571 {
572 qCritical() << "Tried to adjust per-column filter input edit according to resized value, but section index is out of bounds:"
573 << section << ", while edit widgets count is:" << filterInputs.size();
574 return;
575 }
576
577 filterInputs[section]->setFixedWidth(newSize);
578 }
579
togglePerColumnFiltering()580 void DataView::togglePerColumnFiltering()
581 {
582 bool enable = actionMap[FILTER_PER_COLUMN]->isChecked();
583
584 filterEdit->setEnabled(!enable);
585 if (actionMap[FILTER_SQL]->isChecked())
586 actionMap[FILTER_STRING]->setChecked(true);
587
588 actionMap[FILTER_SQL]->setEnabled(!enable);
589 perColumnAreaParent->setVisible(enable);
590
591 recreateFilterInputs();
592 }
593
updateCommitRollbackActions(bool enabled)594 void DataView::updateCommitRollbackActions(bool enabled)
595 {
596 gridView->getAction(SqlQueryView::COMMIT)->setEnabled(enabled);
597 gridView->getAction(SqlQueryView::ROLLBACK)->setEnabled(enabled);
598 uncommittedGrid = enabled;
599 }
600
updateSelectiveCommitRollbackActions(bool enabled)601 void DataView::updateSelectiveCommitRollbackActions(bool enabled)
602 {
603 actionMap[SELECTIVE_COMMIT]->setEnabled(enabled);
604 actionMap[SELECTIVE_ROLLBACK]->setEnabled(enabled);
605 }
606
goToPage(const QString & pageStr)607 void DataView::goToPage(const QString& pageStr)
608 {
609 bool ok;
610 int page = pageStr.toInt(&ok);
611 if (!ok)
612 return;
613
614 page--; // Converting from visual page representation to logical
615
616 if (page == model->getCurrentPage(true))
617 return;
618
619 // We need to get this synchronized against event loop, cause changeing action status (probably) calls event loop update,
620 // so this method was sometimes called twice at the time (until setResultsNavigationState() call below),
621 // but the page in results model wasn't updated yet. We cannot simply move setResultsNavigationState() below gotoPage(),
622 // because we need to disable actions, before model returns from execution, so it can re-enable those actions.
623 // This method was called twice, because QLineEdit::editionFinished (the filter) signal is emitted twice:
624 // - because enter was pressed
625 // - because edit lost its focus (which happens after enter was hit), at least it looks like it
626 if (!manualPageChangeMutex.tryLock())
627 return;
628
629 setNavigationState(false);
630 model->gotoPage(page);
631 manualPageChangeMutex.unlock();
632 }
633
updatePageEdit()634 void DataView::updatePageEdit()
635 {
636 int page = model->getCurrentPage()+1;
637 QString text = QString::number(page);
638 int totalPages = model->getTotalPages();
639 pageEdit->setText(text);
640 pageEdit->setToolTip(QObject::tr("Total pages available: %1").arg(totalPages));
641 pageValidator->setTop(totalPages);
642 pageValidator->setDefaultValue(page);
643 updateCurrentFormViewRow();
644 }
645
updateResultsCount(int resultsCount)646 void DataView::updateResultsCount(int resultsCount)
647 {
648 if (resultsCount >= 0)
649 {
650 QString msg = QObject::tr("Total rows loaded: %1").arg(resultsCount);
651 rowCountLabel->setText(msg);
652 formViewRowCountLabel->setText(msg);
653 rowCountLabel->setToolTip(QString());
654 formViewRowCountLabel->setToolTip(QString());
655 }
656 else
657 {
658 rowCountLabel->setText(" "); // this might seem weird, but if it's not a wide, whitespace string, then icon is truncated from right side
659 formViewRowCountLabel->setText(" ");
660 rowCountLabel->setMovie(ICONS.LOADING);
661 formViewRowCountLabel->setMovie(ICONS.LOADING);
662
663 static QString loadingMsg = tr("Total number of rows is being counted.\nBrowsing other pages will be possible after the row counting is done.");
664 rowCountLabel->setToolTip(loadingMsg);
665 formViewRowCountLabel->setToolTip(loadingMsg);
666 }
667 }
668
updateCurrentFormViewRow()669 void DataView::updateCurrentFormViewRow()
670 {
671 int rowsPerPage = CFG_UI.General.NumberOfRowsPerPage.get();
672 int page = gridView->getModel()->getCurrentPage();
673 int row = rowsPerPage * page + 1 + gridView->getCurrentIndex().row();
674 formViewCurrentRowLabel->setText(tr("Row: %1").arg(row));
675 }
676
setFormViewEnabled(bool enabled)677 void DataView::setFormViewEnabled(bool enabled)
678 {
679 if (!enabled)
680 setCurrentIndex(0);
681
682 setTabEnabled(1, enabled);
683 }
684
readData()685 void DataView::readData()
686 {
687 setNavigationState(false);
688 model->executeQuery();
689 }
690
isUncommitted() const691 bool DataView::isUncommitted() const
692 {
693 return uncommittedGrid || uncommittedForm;
694 }
695
dataLoadingEnded(bool successful)696 void DataView::dataLoadingEnded(bool successful)
697 {
698 if (successful)
699 {
700 updatePageEdit();
701 resizeColumnsInitiallyToContents();
702 recreateFilterInputs();
703 }
704
705 setNavigationState(true);
706 }
707
executionSuccessful()708 void DataView::executionSuccessful()
709 {
710 updateResultsCount(-1);
711 }
712
totalRowsAndPagesAvailable()713 void DataView::totalRowsAndPagesAvailable()
714 {
715 updateResultsCount(model->getTotalRowsReturned());
716 totalPagesAvailable = true;
717 updatePageEdit();
718 updateNavigationState();
719 }
720
refreshData()721 void DataView::refreshData()
722 {
723 totalPagesAvailable = false;
724 readData();
725 }
726
insertRow()727 void DataView::insertRow()
728 {
729 if (!model->features().testFlag(SqlQueryModel::INSERT_ROW))
730 return;
731
732 model->addNewRow();
733 initFormViewForNewRow();
734 formView->updateFromGrid();
735 updateCurrentFormViewRow();
736 formViewFocusFirstEditor();
737 }
738
insertMultipleRows()739 void DataView::insertMultipleRows()
740 {
741 if (!model->features().testFlag(SqlQueryModel::INSERT_ROW))
742 return;
743
744 model->addMultipleRows();
745 formView->updateFromGrid();
746 updateCurrentFormViewRow();
747 formViewFocusFirstEditor();
748 }
749
deleteRow()750 void DataView::deleteRow()
751 {
752 if (!model->features().testFlag(SqlQueryModel::DELETE_ROW))
753 return;
754
755 model->deleteSelectedRows();
756 formView->updateFromGrid();
757 updateCurrentFormViewRow();
758 formViewFocusFirstEditor();
759 }
760
commitGrid()761 void DataView::commitGrid()
762 {
763 model->commit();
764 }
765
rollbackGrid()766 void DataView::rollbackGrid()
767 {
768 model->rollback();
769 }
770
selectiveCommitGrid()771 void DataView::selectiveCommitGrid()
772 {
773 QList<SqlQueryItem*> selectedItems = gridView->getSelectedItems();
774 model->commit(selectedItems);
775 }
776
selectiveRollbackGrid()777 void DataView::selectiveRollbackGrid()
778 {
779 QList<SqlQueryItem*> selectedItems = gridView->getSelectedItems();
780 model->rollback(selectedItems);
781 }
782
firstPage()783 void DataView::firstPage()
784 {
785 setNavigationState(false);
786 model->firstPage();
787 }
788
prevPage()789 void DataView::prevPage()
790 {
791 setNavigationState(false);
792 model->prevPage();
793 }
794
nextPage()795 void DataView::nextPage()
796 {
797 setNavigationState(false);
798 model->nextPage();
799 }
800
lastPage()801 void DataView::lastPage()
802 {
803 setNavigationState(false);
804 model->lastPage();
805 }
806
pageEntered()807 void DataView::pageEntered()
808 {
809 goToPage(pageEdit->text());
810 }
811
applyFilter()812 void DataView::applyFilter()
813 {
814 if (!model->features().testFlag(SqlQueryModel::Feature::FILTERING))
815 {
816 qWarning() << "Tried to apply filter on model that doesn't support it.";
817 return;
818 }
819
820 if (actionMap[FILTER_PER_COLUMN]->isChecked())
821 {
822 filterValues.clear();
823 for (QLineEdit* edit : filterInputs)
824 filterValues << edit->text();
825
826 if (filterValues.join("").isEmpty())
827 {
828 model->resetFilter();
829 return;
830 }
831
832 switch (filterMode)
833 {
834 case DataView::FilterMode::STRING:
835 model->applyStringFilter(filterValues);
836 break;
837 case DataView::FilterMode::SQL:
838 // Should never happen.
839 qWarning() << "Requested to filter by SQL for filtering per-column. This should not be possible.";
840 break;
841 case DataView::FilterMode::REGEXP:
842 model->applyRegExpFilter(filterValues);
843 break;
844 }
845 }
846 else
847 {
848 QString value = filterEdit->text();
849 switch (filterMode)
850 {
851 case DataView::FilterMode::STRING:
852 model->applyStringFilter(value);
853 break;
854 case DataView::FilterMode::SQL:
855 model->applySqlFilter(value);
856 break;
857 case DataView::FilterMode::REGEXP:
858 model->applyRegExpFilter(value);
859 break;
860 }
861 }
862 }
863
resetFilter()864 void DataView::resetFilter()
865 {
866 if (!model->features().testFlag(SqlQueryModel::Feature::FILTERING))
867 {
868 qWarning() << "Tried to reset filter on model that doesn't support it.";
869 return;
870 }
871
872 model->resetFilter();
873 }
874
commitForm()875 void DataView::commitForm()
876 {
877 formView->copyDataToGrid();
878 gridView->selectRow(formView->getCurrentRow());
879 selectiveCommitGrid();
880 formView->updateFromGrid();
881 formViewFocusFirstEditor();
882 }
883
rollbackForm()884 void DataView::rollbackForm()
885 {
886 formView->copyDataToGrid();
887 gridView->selectRow(formView->getCurrentRow());
888 selectiveRollbackGrid();
889 formView->updateFromGrid();
890 updateCurrentFormViewRow();
891 formViewFocusFirstEditor();
892 }
893
firstRow()894 void DataView::firstRow()
895 {
896 goToFormRow(IndexModifier::FIRST);
897 formViewFocusFirstEditor();
898 }
899
prevRow()900 void DataView::prevRow()
901 {
902 goToFormRow(IndexModifier::PREV);
903 formViewFocusFirstEditor();
904 }
905
nextRow()906 void DataView::nextRow()
907 {
908 goToFormRow(IndexModifier::NEXT);
909 formViewFocusFirstEditor();
910 }
911
lastRow()912 void DataView::lastRow()
913 {
914 goToFormRow(IndexModifier::LAST);
915 formViewFocusFirstEditor();
916 }
917
initFormViewForNewRow()918 void DataView::initFormViewForNewRow()
919 {
920 if (currentWidget() != formWidget)
921 return;
922
923 int row = gridView->getCurrentIndex().row();
924 for (SqlQueryItem* item : getModel()->getRow(row))
925 {
926 if (item->getColumn()->isAutoIncr())
927 continue;
928
929 item->setValue("");
930 }
931 }
932
formViewFocusFirstEditor()933 void DataView::formViewFocusFirstEditor()
934 {
935 if (currentWidget() == formWidget)
936 formView->focusFirstEditor();
937 }
938
recreateFilterInputs()939 void DataView::recreateFilterInputs()
940 {
941 qApp->processEvents();
942
943 for (QLineEdit* edit : filterInputs)
944 delete edit;
945
946 filterInputs.clear();
947
948 filterLeftSpacer->setFixedSize(gridView->verticalHeader()->width() + 1, 1);
949
950 QLineEdit* edit = nullptr;
951 for (int i = 0, total = gridView->horizontalHeader()->count(); i < total; ++i)
952 {
953 edit = new QLineEdit(perColumnWidget);
954 edit->setPlaceholderText(tr("Filter"));
955 edit->setClearButtonEnabled(true);
956 edit->setFixedWidth(gridView->columnWidth(i));
957 edit->setToolTip(tr("Hit Enter key or press \"Apply filter\" button on toolbar to apply new value."));
958 if (filterValues.size() > i)
959 edit->setText(filterValues[i]);
960
961 connect(edit, SIGNAL(returnPressed()), this, SLOT(applyFilter()));
962 perColumnWidget->layout()->addWidget(edit);
963 filterInputs << edit;
964 }
965
966 int rightSpacerWd = gridView->verticalScrollBar()->isVisible() ? gridView->verticalScrollBar()->width() : 0;
967 filterRightSpacer->setFixedSize(rightSpacerWd + 1, 1);
968
969 perColumnAreaParent->setFixedWidth(gridView->width());
970
971 if (edit)
972 {
973 int hg = edit->sizeHint().height();
974 perColumnFilterArea->setFixedHeight(hg);
975 perColumnAreaParent->setFixedHeight(hg);
976 }
977
978 qApp->processEvents();
979
980 syncFilterScrollPosition();
981 }
982
createFilteringActions()983 void DataView::createFilteringActions()
984 {
985 createAction(FILTER_STRING, ICONS.APPLY_FILTER_TXT, tr("Filter by text", "data view"), this, SLOT(filterModeSelected()), this);
986 createAction(FILTER_REGEXP, ICONS.APPLY_FILTER_RE, tr("Filter by the Regular Expression", "data view"), this, SLOT(filterModeSelected()), this);
987 createAction(FILTER_SQL, ICONS.APPLY_FILTER_SQL, tr("Filter by SQL expression", "data view"), this, SLOT(filterModeSelected()), this);
988
989 actionMap[FILTER_STRING]->setProperty(DATA_VIEW_FILTER_PROP, static_cast<int>(FilterMode::STRING));
990 actionMap[FILTER_REGEXP]->setProperty(DATA_VIEW_FILTER_PROP, static_cast<int>(FilterMode::REGEXP));
991 actionMap[FILTER_SQL]->setProperty(DATA_VIEW_FILTER_PROP, static_cast<int>(FilterMode::SQL));
992
993 QActionGroup* filterGroup = new QActionGroup(gridToolBar);
994 filterGroup->addAction(actionMap[FILTER_STRING]);
995 filterGroup->addAction(actionMap[FILTER_SQL]);
996 filterGroup->addAction(actionMap[FILTER_REGEXP]);
997
998 actionMap[FILTER_STRING]->setCheckable(true);
999 actionMap[FILTER_REGEXP]->setCheckable(true);
1000 actionMap[FILTER_SQL]->setCheckable(true);
1001 actionMap[FILTER_STRING]->setChecked(true);
1002
1003 createAction(FILTER_PER_COLUMN, tr("Show filter inputs per column", "data view"), this, SLOT(togglePerColumnFiltering()), this);
1004 actionMap[FILTER_PER_COLUMN]->setCheckable(true);
1005
1006 actionMap[FILTER_VALUE] = gridToolBar->addWidget(filterEdit);
1007 createAction(FILTER, tr("Apply filter", "data view"), this, SLOT(applyFilter()), gridToolBar);
1008 attachActionInMenu(FILTER, actionMap[FILTER_STRING], gridToolBar);
1009 attachActionInMenu(FILTER, actionMap[FILTER_REGEXP], gridToolBar);
1010 attachActionInMenu(FILTER, actionMap[FILTER_SQL], gridToolBar);
1011 addSeparatorInMenu(FILTER, gridToolBar);
1012 attachActionInMenu(FILTER, actionMap[FILTER_PER_COLUMN], gridToolBar);
1013 gridToolBar->addSeparator();
1014
1015 actionMap[FILTER]->setIcon(actionMap[FILTER_STRING]->icon());
1016
1017 gridView->getHeaderContextMenu()->addSeparator();
1018 gridView->getHeaderContextMenu()->addAction(actionMap[FILTER_PER_COLUMN]);
1019 }
1020
columnsHeaderClicked(int columnIdx)1021 void DataView::columnsHeaderClicked(int columnIdx)
1022 {
1023 model->changeSorting(columnIdx);
1024 }
1025
tabChanged(int newIndex)1026 void DataView::tabChanged(int newIndex)
1027 {
1028 switch (newIndex)
1029 {
1030 case 0:
1031 {
1032 formView->copyDataToGrid();
1033 gridView->setFocus();
1034 break;
1035 }
1036 case 1:
1037 {
1038 if (!gridView->getCurrentIndex().isValid() && model->rowCount() > 0)
1039 gridView->setCurrentRow(0);
1040
1041 int row = gridView->getCurrentIndex().row();
1042 model->loadFullDataForEntireRow(row);
1043 formView->updateFromGrid();
1044 updateCurrentFormViewRow();
1045 break;
1046 }
1047 }
1048 }
1049
getFormView() const1050 FormView* DataView::getFormView() const
1051 {
1052 return formView;
1053 }
1054
getModel() const1055 SqlQueryModel* DataView::getModel() const
1056 {
1057 return model;
1058 }
1059
getToolBar(int toolbar) const1060 QToolBar* DataView::getToolBar(int toolbar) const
1061 {
1062 switch (static_cast<ToolBar>(toolbar))
1063 {
1064 case TOOLBAR_GRID:
1065 return gridToolBar;
1066 case TOOLBAR_FORM:
1067 return formToolBar;
1068 }
1069 return nullptr;
1070 }
1071
staticInit()1072 void DataView::staticInit()
1073 {
1074 tabsPosition = TabsPosition::TOP;
1075 loadTabsMode();
1076 createStaticActions();
1077 }
1078
insertAction(ExtActionPrototype * action,DataView::ToolBar toolbar)1079 void DataView::insertAction(ExtActionPrototype* action, DataView::ToolBar toolbar)
1080 {
1081 return ExtActionContainer::insertAction<DataView>(action, toolbar);
1082 }
1083
insertActionBefore(ExtActionPrototype * action,DataView::Action beforeAction,DataView::ToolBar toolbar)1084 void DataView::insertActionBefore(ExtActionPrototype* action, DataView::Action beforeAction, DataView::ToolBar toolbar)
1085 {
1086 return ExtActionContainer::insertActionBefore<DataView>(action, beforeAction, toolbar);
1087 }
1088
insertActionAfter(ExtActionPrototype * action,DataView::Action afterAction,DataView::ToolBar toolbar)1089 void DataView::insertActionAfter(ExtActionPrototype* action, DataView::Action afterAction, DataView::ToolBar toolbar)
1090 {
1091 return ExtActionContainer::insertActionAfter<DataView>(action, afterAction, toolbar);
1092 }
1093
removeAction(ExtActionPrototype * action,DataView::ToolBar toolbar)1094 void DataView::removeAction(ExtActionPrototype* action, DataView::ToolBar toolbar)
1095 {
1096 ExtActionContainer::removeAction<DataView>(action, toolbar);
1097 }
1098
getGridView() const1099 SqlQueryView* DataView::getGridView() const
1100 {
1101 return gridView;
1102 }
1103
qHash(DataView::ActionGroup action)1104 int qHash(DataView::ActionGroup action)
1105 {
1106 return static_cast<int>(action);
1107 }
1108