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