1 #include "sqlqueryview.h"
2 #include "sqlqueryitemdelegate.h"
3 #include "sqlquerymodel.h"
4 #include "sqlqueryitem.h"
5 #include "common/widgetcover.h"
6 #include "tsvserializer.h"
7 #include "iconmanager.h"
8 #include "common/unused.h"
9 #include "common/extaction.h"
10 #include "multieditor/multieditor.h"
11 #include "multieditor/multieditordialog.h"
12 #include "uiconfig.h"
13 #include "dialogs/sortdialog.h"
14 #include "services/notifymanager.h"
15 #include "windows/editorwindow.h"
16 #include "mainwindow.h"
17 #include "common/utils_sql.h"
18 #include "querygenerator.h"
19 #include "services/codeformatter.h"
20 #include <QPushButton>
21 #include <QProgressBar>
22 #include <QGridLayout>
23 #include <QDebug>
24 #include <QFocusEvent>
25 #include <QApplication>
26 #include <QClipboard>
27 #include <QAction>
28 #include <QMenu>
29 #include <QMimeData>
30 #include <QCryptographicHash>
31 #include <QMessageBox>
32 #include <QScrollBar>
33 
CFG_KEYS_DEFINE(SqlQueryView)34 CFG_KEYS_DEFINE(SqlQueryView)
35 
36 SqlQueryView::SqlQueryView(QWidget *parent) :
37     QTableView(parent)
38 {
39     init();
40 }
41 
~SqlQueryView()42 SqlQueryView::~SqlQueryView()
43 {
44     delete itemDelegate;
45 }
46 
init()47 void SqlQueryView::init()
48 {
49     itemDelegate = new SqlQueryItemDelegate();
50     setItemDelegate(itemDelegate);
51     setMouseTracking(true);
52 //    setEditTriggers(QAbstractItemView::AnyKeyPressed);
53     setEditTriggers(QAbstractItemView::AnyKeyPressed|QAbstractItemView::EditKeyPressed);
54 
55     setContextMenuPolicy(Qt::CustomContextMenu);
56     contextMenu = new QMenu(this);
57     referencedTablesMenu = new QMenu(tr("Go to referenced row in..."), contextMenu);
58 
59     setHorizontalHeader(new Header(this));
60 
61     connect(this, &QWidget::customContextMenuRequested, this, &SqlQueryView::customContextMenuRequested);
62     connect(CFG_UI.Fonts.DataView, SIGNAL(changed(QVariant)), this, SLOT(updateFont()));
63     connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(itemActivated(QModelIndex)));
64     connect(horizontalHeader(), &QHeaderView::sectionResized, [this](int section, int, int newSize)
65     {
66         if (ignoreColumnWidthChanges)
67             return;
68 
69         getModel()->setDesiredColumnWidth(section, newSize);
70     });
71 
72     horizontalHeader()->setSortIndicatorShown(false);
73     horizontalHeader()->setSectionsClickable(true);
74     updateFont();
75 
76     setupWidgetCover();
77     initActions();
78     setupHeaderMenu();
79 }
80 
setupWidgetCover()81 void SqlQueryView::setupWidgetCover()
82 {
83     widgetCover = new WidgetCover(this);
84     widgetCover->initWithInterruptContainer();
85 }
86 
createActions()87 void SqlQueryView::createActions()
88 {
89     createAction(COPY, ICONS.ACT_COPY, tr("Copy"), this, SLOT(copy()), this);
90     createAction(COPY_WITH_HEADER, ICONS.ACT_COPY, tr("Copy with headers"), this, SLOT(copyWithHeader()), this);
91     createAction(COPY_AS, ICONS.ACT_COPY, tr("Copy as..."), this, SLOT(copyAs()), this);
92     createAction(PASTE, ICONS.ACT_PASTE, tr("Paste"), this, SLOT(paste()), this);
93     createAction(PASTE_AS, ICONS.ACT_PASTE, tr("Paste as..."), this, SLOT(pasteAs()), this);
94     createAction(SET_NULL, ICONS.SET_NULL, tr("Set NULL values"), this, SLOT(setNull()), this);
95     createAction(ERASE, ICONS.ERASE, tr("Erase values"), this, SLOT(erase()), this);
96     createAction(OPEN_VALUE_EDITOR, ICONS.OPEN_VALUE_EDITOR, "", this, SLOT(openValueEditor()), this); // actual label is set dynamically in setupActionsForMenu()
97     createAction(COMMIT, ICONS.COMMIT, tr("Commit"), this, SLOT(commit()), this);
98     createAction(ROLLBACK, ICONS.ROLLBACK, tr("Rollback"), this, SLOT(rollback()), this);
99     createAction(SELECTIVE_COMMIT, ICONS.COMMIT, tr("Commit selected cells"), this, SLOT(selectiveCommit()), this);
100     createAction(SELECTIVE_ROLLBACK, ICONS.ROLLBACK, tr("Rollback selected cells"), this, SLOT(selectiveRollback()), this);
101     createAction(GENERATE_SELECT, "SELECT", this, SLOT(generateSelect()), this);
102     createAction(GENERATE_INSERT, "INSERT", this, SLOT(generateInsert()), this);
103     createAction(GENERATE_UPDATE, "UPDATE", this, SLOT(generateUpdate()), this);
104     createAction(GENERATE_DELETE, "DELETE", this, SLOT(generateDelete()), this);
105     createAction(SORT_DIALOG, ICONS.SORT_COLUMNS, tr("Define columns to sort by"), this, SLOT(openSortDialog()), this);
106     createAction(RESET_SORTING, ICONS.SORT_RESET, tr("Remove custom sorting"), this, SLOT(resetSorting()), this);
107     createAction(INSERT_ROW, ICONS.INSERT_ROW, tr("Insert row"), this, SIGNAL(requestForRowInsert()), this);
108     createAction(INSERT_MULTIPLE_ROWS, ICONS.INSERT_ROWS, tr("Insert multiple rows"), this, SIGNAL(requestForMultipleRowInsert()), this);
109     createAction(DELETE_ROW, ICONS.DELETE_ROW, tr("Delete selected row"), this, SIGNAL(requestForRowDelete()), this);
110     createAction(LOAD_FULL_VALUES, ICONS.LOAD_FULL_VALUES, tr("Load full values"), this, SLOT(loadFullValuesForColumn()), this);
111 
112     actionMap[RESET_SORTING]->setEnabled(false);
113 }
114 
setupDefShortcuts()115 void SqlQueryView::setupDefShortcuts()
116 {
117     setShortcutContext({ROLLBACK, SET_NULL, ERASE, OPEN_VALUE_EDITOR, COMMIT, COPY, COPY_AS,
118                        PASTE, PASTE_AS}, Qt::WidgetWithChildrenShortcut);
119 
120     BIND_SHORTCUTS(SqlQueryView, Action);
121 }
122 
setupActionsForMenu(SqlQueryItem * currentItem,const QList<SqlQueryItem * > & selectedItems)123 void SqlQueryView::setupActionsForMenu(SqlQueryItem* currentItem, const QList<SqlQueryItem*>& selectedItems)
124 {
125     // Selected items count
126     int selCount = selectedItems.size();
127 
128     // Uncommitted items count
129     QList<SqlQueryItem*> uncommittedItems = getModel()->getUncommittedItems();
130     int uncommittedCount = uncommittedItems.size();
131 
132     // How many of selected items is editable
133     int editableSelCount = selCount;
134     for (SqlQueryItem* selItem : getSelectedItems())
135         if (selItem->getColumn()->editionForbiddenReason.size() > 0)
136             editableSelCount--;
137 
138     bool currentItemEditable = (currentItem && currentItem->getColumn()->editionForbiddenReason.size() == 0);
139 
140     // Uncommitted & selected items count
141     int uncommittedSelCount = 0;
142     for (SqlQueryItem* item : uncommittedItems)
143         if (selectedItems.contains(item))
144             uncommittedSelCount++;
145 
146     if (uncommittedCount > 0)
147         contextMenu->addAction(actionMap[COMMIT]);
148 
149     if (uncommittedSelCount > 0)
150         contextMenu->addAction(actionMap[SELECTIVE_COMMIT]);
151 
152     if (uncommittedCount > 0)
153         contextMenu->addAction(actionMap[ROLLBACK]);
154 
155     if (uncommittedSelCount > 0)
156         contextMenu->addAction(actionMap[SELECTIVE_ROLLBACK]);
157 
158     if (uncommittedCount > 0 && selCount > 0)
159         contextMenu->addSeparator();
160 
161     // Edit/show label for "open in editor" action
162     actionMap[OPEN_VALUE_EDITOR]->setText(currentItemEditable ? tr("Edit value in editor") : tr("Show value in a viewer"));
163 
164     if (selCount > 0)
165     {
166         if (editableSelCount > 0)
167         {
168             contextMenu->addAction(actionMap[ERASE]);
169             contextMenu->addAction(actionMap[SET_NULL]);
170         }
171         contextMenu->addAction(actionMap[OPEN_VALUE_EDITOR]);
172         contextMenu->addSeparator();
173     }
174 
175     if (selCount == 1 && currentItem && selectedItems.first() == currentItem)
176         addFkActionsToContextMenu(currentItem);
177 
178     if (selCount > 0)
179     {
180         QMenu* generateQueryMenu = contextMenu->addMenu(ICONS.GENERATE_QUERY, tr("Generate query for selected cells"));
181         generateQueryMenu->addAction(actionMap[GENERATE_SELECT]);
182         if (getModel()->supportsModifyingQueriesInMenu())
183         {
184             generateQueryMenu->addAction(actionMap[GENERATE_INSERT]);
185             generateQueryMenu->addAction(actionMap[GENERATE_UPDATE]);
186             generateQueryMenu->addAction(actionMap[GENERATE_DELETE]);
187         }
188 
189 
190         contextMenu->addSeparator();
191         contextMenu->addAction(actionMap[COPY]);
192         contextMenu->addAction(actionMap[COPY_WITH_HEADER]);
193         //contextMenu->addAction(actionMap[COPY_AS]); // TODO uncomment when implemented
194         contextMenu->addAction(actionMap[PASTE]);
195         //contextMenu->addAction(actionMap[PASTE_AS]); // TODO uncomment when implemented
196     }
197     if (additionalActions.size() > 0)
198     {
199         contextMenu->addSeparator();
200         for (QAction* action : additionalActions)
201             contextMenu->addAction(action);
202     }
203 }
204 
setupHeaderMenu()205 void SqlQueryView::setupHeaderMenu()
206 {
207     horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
208     connect(horizontalHeader(), &QWidget::customContextMenuRequested, this, &SqlQueryView::headerContextMenuRequested);
209     headerContextMenu = new QMenu(horizontalHeader());
210     headerContextMenu->addAction(actionMap[SORT_DIALOG]);
211     headerContextMenu->addAction(actionMap[RESET_SORTING]);
212     headerContextMenu->addSeparator();
213     headerContextMenu->addAction(actionMap[LOAD_FULL_VALUES]);
214 }
215 
getSelectedItems()216 QList<SqlQueryItem*> SqlQueryView::getSelectedItems()
217 {
218     QList<SqlQueryItem*> items;
219     QModelIndexList idxList = selectionModel()->selectedIndexes();
220     QModelIndex currIdx = getCurrentIndex();
221     if (!idxList.contains(currIdx) && currIdx.isValid())
222         idxList << currIdx;
223 
224     if (idxList.size() == 0)
225         return items;
226 
227     std::sort(idxList.begin(), idxList.end());
228     const SqlQueryModel* model = dynamic_cast<const SqlQueryModel*>(idxList.first().model());
229     for (const QModelIndex& idx : idxList)
230         items << model->itemFromIndex(idx);
231 
232     return items;
233 }
234 
getCurrentItem()235 SqlQueryItem* SqlQueryView::getCurrentItem()
236 {
237     QModelIndex idx = getCurrentIndex();
238     if (!idx.isValid())
239         return nullptr;
240 
241     return getModel()->itemFromIndex(idx);
242 }
243 
getModel()244 SqlQueryModel* SqlQueryView::getModel()
245 {
246     return dynamic_cast<SqlQueryModel*>(model());
247 }
248 
setModel(QAbstractItemModel * model)249 void SqlQueryView::setModel(QAbstractItemModel* model)
250 {
251     QTableView::setModel(model);
252     SqlQueryModel* m = getModel();
253     connect(widgetCover, SIGNAL(cancelClicked()), m, SLOT(interrupt()));
254     connect(getModel(), &SqlQueryModel::commitStatusChanged, this, &SqlQueryView::updateCommitRollbackActions);
255     connect(getModel(), &SqlQueryModel::sortingUpdated, this, &SqlQueryView::sortingUpdated);
256 }
257 
itemAt(const QPoint & pos)258 SqlQueryItem* SqlQueryView::itemAt(const QPoint& pos)
259 {
260     return dynamic_cast<SqlQueryItem*>(getModel()->itemFromIndex(indexAt(pos)));
261 }
262 
getToolBar(int toolbar) const263 QToolBar* SqlQueryView::getToolBar(int toolbar) const
264 {
265     UNUSED(toolbar);
266     return nullptr;
267 }
268 
addAdditionalAction(QAction * action)269 void SqlQueryView::addAdditionalAction(QAction* action)
270 {
271     additionalActions << action;
272 }
273 
getCurrentIndex() const274 QModelIndex SqlQueryView::getCurrentIndex() const
275 {
276     return currentIndex();
277 }
278 
itemActivated(const QModelIndex & index)279 void SqlQueryView::itemActivated(const QModelIndex& index)
280 {
281     if (simpleBrowserMode)
282         return;
283 
284     if (!index.isValid())
285         return;
286 
287     SqlQueryItem* item = getModel()->itemFromIndex(index);
288     if (!item)
289         return;
290 
291     if (!editInEditorIfNecessary(item))
292         return;
293 
294     edit(getCurrentIndex());
295 }
296 
generateSelect()297 void SqlQueryView::generateSelect()
298 {
299     QString sql = getModel()->generateSelectQueryForItems(getSelectedItems());
300     MAINWINDOW->openSqlEditor(getModel()->getDb(), sql);
301 
302 }
303 
generateInsert()304 void SqlQueryView::generateInsert()
305 {
306     QString sql = getModel()->generateInsertQueryForItems(getSelectedItems());
307     MAINWINDOW->openSqlEditor(getModel()->getDb(), sql);
308 }
309 
generateUpdate()310 void SqlQueryView::generateUpdate()
311 {
312     QString sql = getModel()->generateUpdateQueryForItems(getSelectedItems());
313     MAINWINDOW->openSqlEditor(getModel()->getDb(), sql);
314 }
315 
generateDelete()316 void SqlQueryView::generateDelete()
317 {
318     QString sql = getModel()->generateDeleteQueryForItems(getSelectedItems());
319     MAINWINDOW->openSqlEditor(getModel()->getDb(), sql);
320 }
321 
loadFullValuesForColumn()322 void SqlQueryView::loadFullValuesForColumn()
323 {
324     getModel()->loadFullDataForEntireColumn(headerContextMenuSection);
325 }
326 
editInEditorIfNecessary(SqlQueryItem * item)327 bool SqlQueryView::editInEditorIfNecessary(SqlQueryItem* item)
328 {
329     if (item->getColumn()->dataType.getType() == DataType::BLOB)
330     {
331         openValueEditor(item);
332         return false;
333     }
334     return true;
335 }
336 
paste(const QList<QList<QVariant>> & data)337 void SqlQueryView::paste(const QList<QList<QVariant> >& data)
338 {
339     if (simpleBrowserMode)
340         return;
341 
342     QList<SqlQueryItem*> selectedItems = getSelectedItems();
343     if (selectedItems.isEmpty())
344     {
345         notifyWarn(tr("No items selected to paste clipboard contents to."));
346         return;
347     }
348 
349     if (getModel()->isStructureOutOfDate())
350     {
351         notifyWarn(tr("Cannot paste data. Details: %1").arg(tr("Structure of at least one table used has changed since last data was loaded. Reload the data to proceed.")));
352         return;
353     }
354 
355     QSet<QString> warnedColumns;
356     bool warnedRowDeletion = false;
357     if (data.size() == 1 && data[0].size() == 1)
358     {
359         QVariant theValue = data[0][0];
360         for (SqlQueryItem* item : selectedItems)
361         {
362             if (!validatePasting(warnedColumns, warnedRowDeletion, item))
363                 continue;
364 
365             item->setValue(theValue, false, false);
366         }
367 
368         return;
369     }
370 
371     SqlQueryItem* topLeft = selectedItems.first();
372 
373     int columnCount = getModel()->columnCount();
374     int rowCount = getModel()->rowCount();
375     int rowIdx = topLeft->row();
376     int colIdx = topLeft->column();
377 
378     SqlQueryItem* item = nullptr;
379 
380     for (const QList<QVariant>& cells : data)
381     {
382         // Check if we're out of rows range
383         if (rowIdx >= rowCount)
384         {
385             // No more rows available.
386             qDebug() << "Tried to paste more rows than available in the grid.";
387             break;
388         }
389 
390         for (const QVariant& cell : cells)
391         {
392             // Get current cell
393             if (colIdx >= columnCount)
394             {
395                 // No more columns available.
396                 qDebug() << "Tried to paste more columns than available in the grid.";
397                 break;
398             }
399             item = getModel()->itemFromIndex(rowIdx, colIdx++);
400 
401             // Set value to the cell, if possible
402             if (!validatePasting(warnedColumns, warnedRowDeletion, item))
403                 continue;
404 
405             item->setValue(cell, false, false);
406         }
407 
408         // Go to next row, first column
409         rowIdx++;
410         colIdx = topLeft->column();
411     }
412 }
413 
validatePasting(QSet<QString> & warnedColumns,bool & warnedRowDeletion,SqlQueryItem * item)414 bool SqlQueryView::validatePasting(QSet<QString>& warnedColumns, bool& warnedRowDeletion, SqlQueryItem* item)
415 {
416     if (item->isDeletedRow())
417     {
418         if (!warnedRowDeletion)
419         {
420             warnedRowDeletion = true;
421             notifyWarn(tr("Cannot paste to a cell. Details: %1").arg(tr("The row is marked for deletion.")));
422         }
423         return false;
424     }
425 
426     if (!item->getColumn()->canEdit())
427     {
428         QString colName = item->getColumn()->displayName;
429         if (!warnedColumns.contains(colName))
430         {
431             warnedColumns << colName;
432             notifyWarn(tr("Cannot paste to column %1. Details: %2").arg(colName).arg(item->getColumn()->getEditionForbiddenReason()));
433         }
434         return false;
435     }
436 
437     return true;
438 }
439 
addFkActionsToContextMenu(SqlQueryItem * currentItem)440 void SqlQueryView::addFkActionsToContextMenu(SqlQueryItem* currentItem)
441 {
442     QList<SqlQueryModelColumn::ConstraintFk*> fkList = currentItem->getColumn()->getFkConstraints();
443     if (fkList.isEmpty())
444         return;
445 
446     QAction* act;
447     if (fkList.size() == 1)
448     {
449         SqlQueryModelColumn::ConstraintFk* fk = fkList.first();
450         act = contextMenu->addAction(tr("Go to referenced row in table '%1'").arg(fk->foreignTable));
451         connect(act, &QAction::triggered, [this, fk, currentItem](bool) {
452             goToReferencedRow(fk->foreignTable, fk->foreignColumn, currentItem->getValue());
453         });
454         contextMenu->addSeparator();
455         return;
456     }
457 
458     referencedTablesMenu->clear();
459     contextMenu->addMenu(referencedTablesMenu);
460     for (SqlQueryModelColumn::ConstraintFk* fk : fkList)
461     {
462         act = referencedTablesMenu->addAction(tr("table '%1'").arg(fk->foreignTable));
463         connect(act, &QAction::triggered, [this, fk, currentItem](bool) {
464             goToReferencedRow(fk->foreignTable, fk->foreignColumn, currentItem->getValue());
465         });
466     }
467     contextMenu->addSeparator();
468 }
469 
goToReferencedRow(const QString & table,const QString & column,const QVariant & value)470 void SqlQueryView::goToReferencedRow(const QString& table, const QString& column, const QVariant& value)
471 {
472     Db* db = getModel()->getDb();
473     if (!db || !db->isValid())
474         return;
475 
476     static_qstring(sqlTpl, "SELECT * FROM %1 WHERE %2 = %3");
477 
478     QString wrappedTable = wrapObjIfNeeded(table);
479     QString wrappedColumn = wrapObjIfNeeded(column);
480     QString valueStr = wrapValueIfNeeded(value.toString());
481     EditorWindow* win = MAINWINDOW->openSqlEditor(db, sqlTpl.arg(wrappedTable, wrappedColumn, valueStr));
482     if (!win)
483         return;
484 
485     win->getMdiWindow()->rename(tr("Referenced row (%1)").arg(table));
486     win->execute();
487 }
488 
copy(bool withHeader)489 void SqlQueryView::copy(bool withHeader)
490 {
491     if (simpleBrowserMode)
492         return;
493 
494     QList<SqlQueryItem*> selectedItems = getSelectedItems();
495     QList<QList<SqlQueryItem*> > groupedItems = SqlQueryModel::groupItemsByRows(selectedItems);
496 
497     if (selectedItems.isEmpty())
498         return;
499 
500     QVariant itemValue;
501     QStringList cells;
502     QList<QStringList> rows;
503 
504     QPair<QString,QList<QList<QVariant>>> theDataPair;
505     QList<QList<QVariant>> theData;
506     QList<QVariant> theDataRow;
507 
508     // Header
509     if (withHeader)
510     {
511         int leftMostColumn = groupedItems.first().first()->column();
512         for (SqlQueryModelColumnPtr col : getModel()->getColumns().mid(leftMostColumn, groupedItems.first().size()))
513         {
514             theDataRow << col->displayName;
515             cells << col->displayName;
516         }
517 
518         rows << cells;
519         cells.clear();
520 
521         theData << theDataRow;
522         theDataRow.clear();
523     }
524 
525     // Data
526     for (const QList<SqlQueryItem*>& itemsInRows : groupedItems)
527     {
528         for (SqlQueryItem* item : itemsInRows)
529         {
530             itemValue = item->getFullValue();
531             if (itemValue.userType() == QVariant::Double)
532                 cells << doubleToString(itemValue);
533             else
534                 cells << itemValue.toString();
535 
536             theDataRow << itemValue;
537         }
538 
539         rows << cells;
540         cells.clear();
541 
542         theData << theDataRow;
543         theDataRow.clear();
544     }
545 
546     QMimeData* mimeData = new QMimeData();
547     QString tsv = TsvSerializer::serialize(rows);
548     mimeData->setText(tsv);
549 
550     QString md5 = QCryptographicHash::hash(tsv.toUtf8(), QCryptographicHash::Md5);
551     theDataPair.first = md5;
552     theDataPair.second = theData;
553 
554     QByteArray serializedData;
555     QDataStream stream(&serializedData, QIODevice::WriteOnly);
556     stream << theDataPair;
557     mimeData->setData(mimeDataId, serializedData);
558 
559     qApp->clipboard()->setMimeData(mimeData);
560 }
561 
getSimpleBrowserMode() const562 bool SqlQueryView::getSimpleBrowserMode() const
563 {
564     return simpleBrowserMode;
565 }
566 
setSimpleBrowserMode(bool value)567 void SqlQueryView::setSimpleBrowserMode(bool value)
568 {
569     simpleBrowserMode = value;
570 }
571 
setIgnoreColumnWidthChanges(bool ignore)572 void SqlQueryView::setIgnoreColumnWidthChanges(bool ignore)
573 {
574     ignoreColumnWidthChanges = ignore;
575 }
576 
getHeaderContextMenu() const577 QMenu* SqlQueryView::getHeaderContextMenu() const
578 {
579     return headerContextMenu;
580 }
581 
scrollContentsBy(int dx,int dy)582 void SqlQueryView::scrollContentsBy(int dx, int dy)
583 {
584     QTableView::scrollContentsBy(dx, dy);
585     emit scrolledBy(dx, dy);
586 }
587 
mouseMoveEvent(QMouseEvent * event)588 void SqlQueryView::mouseMoveEvent(QMouseEvent* event)
589 {
590     QAbstractItemView::mouseMoveEvent(event);
591 
592     QModelIndex idx = indexAt(QPoint(event->x(), event->y()));
593     if (idx != indexUnderCursor)
594     {
595         if (indexUnderCursor.isValid())
596             itemDelegate->mouseLeftIndex(indexUnderCursor);
597 
598         indexUnderCursor = idx;
599     }
600 }
601 
updateCommitRollbackActions(bool enabled)602 void SqlQueryView::updateCommitRollbackActions(bool enabled)
603 {
604     actionMap[COMMIT]->setEnabled(enabled);
605     actionMap[ROLLBACK]->setEnabled(enabled);
606 }
607 
customContextMenuRequested(const QPoint & pos)608 void SqlQueryView::customContextMenuRequested(const QPoint& pos)
609 {
610     if (simpleBrowserMode)
611         return;
612 
613     SqlQueryItem* currentItem = getCurrentItem();
614     QList<SqlQueryItem*> selectedItems = getSelectedItems();
615 
616     contextMenu->clear();
617 
618     setupActionsForMenu(currentItem, selectedItems);
619     emit contextMenuRequested(currentItem, selectedItems);
620 
621     if (contextMenu->actions().size() == 0)
622         return;
623 
624     contextMenu->popup(viewport()->mapToGlobal(pos));
625 }
626 
headerContextMenuRequested(const QPoint & pos)627 void SqlQueryView::headerContextMenuRequested(const QPoint& pos)
628 {
629     if (simpleBrowserMode)
630         return;
631 
632     headerContextMenuSection = horizontalHeader()->visualIndexAt(pos.x());
633 
634     bool hasLimitedValues = getModel()->doesColumnHaveLimitedValues(headerContextMenuSection);
635     actionMap[LOAD_FULL_VALUES]->setEnabled(hasLimitedValues);
636 
637     headerContextMenu->popup(horizontalHeader()->mapToGlobal(pos));
638 }
639 
openSortDialog()640 void SqlQueryView::openSortDialog()
641 {
642     QStringList columns;
643     for (SqlQueryModelColumnPtr col : getModel()->getColumns())
644         columns << col->displayName;
645 
646     SortDialog dialog(this);
647     dialog.setColumns(columns);
648     dialog.setSortOrder(getModel()->getSortOrder());
649     if (dialog.exec() != QDialog::Accepted)
650         return;
651 
652     getModel()->setSortOrder(dialog.getSortOrder());
653 }
654 
resetSorting()655 void SqlQueryView::resetSorting()
656 {
657     getModel()->setSortOrder(QueryExecutor::SortList());
658 }
659 
sortingUpdated(const QueryExecutor::SortList & sortOrder)660 void SqlQueryView::sortingUpdated(const QueryExecutor::SortList& sortOrder)
661 {
662     actionMap[RESET_SORTING]->setEnabled(sortOrder.size() > 0);
663 }
664 
updateFont()665 void SqlQueryView::updateFont()
666 {
667     QFont f = CFG_UI.Fonts.DataView.get();
668     QFontMetrics fm(f);
669     verticalHeader()->setDefaultSectionSize(fm.height() + 4);
670 }
671 
executionStarted()672 void SqlQueryView::executionStarted()
673 {
674     beforeExecutionHorizontalPosition = horizontalScrollBar()->sliderPosition();
675     widgetCover->show();
676 }
677 
executionEnded()678 void SqlQueryView::executionEnded()
679 {
680     if (beforeExecutionHorizontalPosition > -1)
681     {
682         horizontalScrollBar()->setSliderPosition(beforeExecutionHorizontalPosition);
683         emit scrolledBy(beforeExecutionHorizontalPosition, 0);
684     }
685 
686     widgetCover->hide();
687 }
688 
setCurrentRow(int row)689 void SqlQueryView::setCurrentRow(int row)
690 {
691     setCurrentIndex(model()->index(row, 0));
692 }
693 
copy()694 void SqlQueryView::copy()
695 {
696     copy(false);
697 }
698 
copyWithHeader()699 void SqlQueryView::copyWithHeader()
700 {
701     copy(true);
702 }
703 
paste()704 void SqlQueryView::paste()
705 {
706     if (simpleBrowserMode)
707         return;
708 
709     const QMimeData* mimeData = qApp->clipboard()->mimeData();
710     if (mimeData->hasFormat(mimeDataId))
711     {
712         QString tsv = mimeData->text();
713         QString md5 = QCryptographicHash::hash(tsv.toUtf8(), QCryptographicHash::Md5);
714 
715         QPair<QString,QList<QList<QVariant>>> theDataPair;
716         QByteArray serializedData = mimeData->data(mimeDataId);
717         QDataStream stream(&serializedData, QIODevice::ReadOnly);
718         stream >> theDataPair;
719 
720         if (md5 == theDataPair.first)
721         {
722             paste(theDataPair.second);
723             return;
724         }
725     }
726 
727     QList<QStringList> deserializedRows = TsvSerializer::deserialize(mimeData->text());
728     bool trimOnPaste = false;
729     bool trimOnPasteAsked = false;
730 
731     QList<QVariant> dataRow;
732     QList<QList<QVariant>> dataToPaste;
733     for (const QStringList& cells : deserializedRows)
734     {
735         for (const QString& cell : cells)
736         {
737 #if QT_VERSION >= 0x050A00
738             if ((cell.front().isSpace() || cell.back().isSpace()) && !trimOnPasteAsked)
739 #else
740             if ((cell.at(0).isSpace() || cell.at(cell.size() - 1).isSpace()) && !trimOnPasteAsked)
741 #endif
742             {
743                 QMessageBox::StandardButton choice;
744                 choice = QMessageBox::question(this, tr("Trim pasted text?"),
745                                                tr("The pasted text contains leading or trailing white space. Trim it automatically?"));
746                 trimOnPasteAsked = true;
747                 trimOnPaste = (choice == QMessageBox::Yes);
748             }
749 
750             dataRow << (trimOnPaste ? cell.trimmed() : cell);
751         }
752 
753         dataToPaste << dataRow;
754         dataRow.clear();
755     }
756 
757     paste(dataToPaste);
758 }
759 
copyAs()760 void SqlQueryView::copyAs()
761 {
762     // TODO copyAs()
763 }
764 
pasteAs()765 void SqlQueryView::pasteAs()
766 {
767     // TODO pasteAs()
768 }
769 
setNull()770 void SqlQueryView::setNull()
771 {
772     if (simpleBrowserMode)
773         return;
774 
775     for (SqlQueryItem* selItem : getSelectedItems()) {
776         if (selItem->getColumn()->editionForbiddenReason.size() > 0)
777             continue;
778 
779         selItem->setValue(QVariant(QString()), false, false);
780     }
781 }
782 
erase()783 void SqlQueryView::erase()
784 {
785     if (simpleBrowserMode)
786         return;
787 
788     for (SqlQueryItem* selItem : getSelectedItems()) {
789         if (selItem->getColumn()->editionForbiddenReason.size() > 0)
790             continue;
791 
792         selItem->setValue("", false, false);
793     }
794 }
795 
commit()796 void SqlQueryView::commit()
797 {
798     if (simpleBrowserMode)
799         return;
800 
801     getModel()->commit();
802 }
803 
rollback()804 void SqlQueryView::rollback()
805 {
806     if (simpleBrowserMode)
807         return;
808 
809     getModel()->rollback();
810 }
811 
selectiveCommit()812 void SqlQueryView::selectiveCommit()
813 {
814     if (simpleBrowserMode)
815         return;
816 
817     getModel()->commit(getSelectedItems());
818 }
819 
selectiveRollback()820 void SqlQueryView::selectiveRollback()
821 {
822     if (simpleBrowserMode)
823         return;
824 
825     getModel()->rollback(getSelectedItems());
826 }
827 
openValueEditor(SqlQueryItem * item)828 void SqlQueryView::openValueEditor(SqlQueryItem* item)
829 {
830     if (simpleBrowserMode)
831         return;
832 
833     if (!item)
834     {
835         qWarning() << "Tried to open value editor while there's no current item. It should not be called in that case.";
836         return;
837     }
838 
839     MultiEditorDialog editor(this);
840     editor.setWindowTitle(tr("Edit value"));
841     editor.setDataType(item->getColumn()->dataType);
842     editor.setValue(item->getFullValue());
843     editor.setReadOnly(!item->getColumn()->canEdit());
844     if (editor.exec() == QDialog::Rejected)
845         return;
846 
847     item->setValue(editor.getValue());
848 }
849 
openValueEditor()850 void SqlQueryView::openValueEditor()
851 {
852     SqlQueryItem* currentItem = getCurrentItem();
853     openValueEditor(currentItem);
854 }
855 
qHash(SqlQueryView::Action action)856 int qHash(SqlQueryView::Action action)
857 {
858     return static_cast<int>(action);
859 }
860 
Header(SqlQueryView * parent)861 SqlQueryView::Header::Header(SqlQueryView* parent) :
862     QHeaderView(Qt::Horizontal, parent)
863 {
864 }
865 
sectionSizeFromContents(int section) const866 QSize SqlQueryView::Header::sectionSizeFromContents(int section) const
867 {
868     QSize originalSize = QHeaderView::sectionSizeFromContents(section);
869     int colCount = dynamic_cast<SqlQueryView*>(parent())->getModel()->columnCount();
870     if (colCount <= 5)
871         return originalSize;
872 
873     int wd = minHeaderWidth;
874     wd = qMin((wd + wd * 20 / colCount), originalSize.width());
875     return QSize(wd, originalSize.height());
876 }
877