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