Improvements for drag and drop of items from the DB Schema dock to editor

Two new options editable from a new context menu of the dock:
- Drag & Drop Qualified Names: add table name to fields and schema name
to other objects (except for "main" schema)
- Drag & Drop Enquoted Names: whether to surround the identifiers by the
configured quoting characters for identifiers.

Support for dragging & dropping of attached databases names.

Add "." as separator for multiple dropped objects other than fields (since
they are not usually used in SQL as a list). This allows to compose
qualified names by dropping the parent and the child items together. This
is only generally useful when the "Qualified Names" option is disabled.

See related issue #1433
This commit is contained in:
mgrojo
2018-09-09 19:52:36 +02:00
parent 04bc7da6e7
commit f33943f4cf
6 changed files with 127 additions and 12 deletions

View File

@@ -10,7 +10,9 @@
DbStructureModel::DbStructureModel(DBBrowserDB& db, QObject* parent)
: QAbstractItemModel(parent),
m_db(db)
m_db(db),
m_dropQualifiedNames(false),
m_dropEnquotedNames(false)
{
// Create root item and use its columns to store the header strings
QStringList header;
@@ -41,7 +43,7 @@ QVariant DbStructureModel::data(const QModelIndex& index, int role) const
switch(role)
{
case Qt::DisplayRole:
// For the display role and the browsabled branch of the tree we want to show the column name including the schema name if necessary (i.e.
// For the display role and the browsable branch of the tree we want to show the column name including the schema name if necessary (i.e.
// for schemata != "main"). For the normal structure branch of the tree we don't want to add the schema name because it's already obvious from
// the position of the item in the tree.
if(index.column() == ColumnName && item->parent() == browsablesRootItem)
@@ -66,9 +68,10 @@ Qt::ItemFlags DbStructureModel::flags(const QModelIndex &index) const
// All items are enabled and selectable
Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled;
// Only enable dragging for entire table objects and for fields (composition in SQL text editor)
// Only enable dragging for SQLite objects and for fields (composition in SQL text editor).
// Grouping nodes have no type.
QString type = data(index.sibling(index.row(), ColumnObjectType), Qt::DisplayRole).toString();
if(type == "table" || type == "field" || type == "view" || type == "index" || type == "trigger")
if(!type.isEmpty())
flags |= Qt::ItemIsDragEnabled;
return flags;
@@ -143,8 +146,8 @@ void DbStructureModel::reloadData()
}
// Create the nodes for browsables and for tables, indices, views and triggers. The idea here is to basically have two trees in one model:
// In the root node there are two nodes: 'browsables' and 'all'. The first node contains a list of a all browsable objects, i.e. views and tables.
// The seconds node contains four sub-nodes (tables, indices, views and triggers), each containing a list of objects of that type.
// In the root node there are two nodes: 'browsables' and 'all'. The first node contains a list of all browsable objects, i.e. views and tables.
// The second node contains four sub-nodes (tables, indices, views and triggers), each containing a list of objects of that type.
// This way we only have to have and only have to update one model and can use it in all sorts of places, just by setting a different root node.
browsablesRootItem = new QTreeWidgetItem(rootItem);
browsablesRootItem->setIcon(ColumnName, QIcon(QString(":/icons/view")));
@@ -154,6 +157,7 @@ void DbStructureModel::reloadData()
QTreeWidgetItem* itemAll = new QTreeWidgetItem(rootItem);
itemAll->setIcon(ColumnName, QIcon(QString(":/icons/database")));
itemAll->setText(ColumnName, tr("All"));
itemAll->setText(ColumnObjectType, "database");
buildTree(itemAll, "main");
// Add the temporary database as a node if it isn't empty. Make sure it's always second if it exists.
@@ -162,6 +166,7 @@ void DbStructureModel::reloadData()
QTreeWidgetItem* itemTemp = new QTreeWidgetItem(itemAll);
itemTemp->setIcon(ColumnName, QIcon(QString(":/icons/database")));
itemTemp->setText(ColumnName, tr("Temporary"));
itemTemp->setText(ColumnObjectType, "database");
buildTree(itemTemp, "temp");
}
@@ -174,6 +179,7 @@ void DbStructureModel::reloadData()
QTreeWidgetItem* itemSchema = new QTreeWidgetItem(itemAll);
itemSchema->setIcon(ColumnName, QIcon(QString(":/icons/database")));
itemSchema->setText(ColumnName, it.key());
itemSchema->setText(ColumnObjectType, "database");
buildTree(itemSchema, it.key());
}
}
@@ -198,14 +204,26 @@ QMimeData* DbStructureModel::mimeData(const QModelIndexList& indices) const
// Loop through selected indices
for(const QModelIndex& index : indices)
{
// Get the item the index points at
QTreeWidgetItem* item = static_cast<QTreeWidgetItem*>(index.internalPointer());
// Only export data for valid indices and only once per row (SQL column or Name column).
// For names, export an escaped identifier of the item for statement composition in SQL editor.
// Commas are included for a list of identifiers.
if(index.isValid()) {
QString objectType = data(index.sibling(index.row(), ColumnObjectType), Qt::DisplayRole).toString();
// For names, export an escaped identifier of the item for statement composition in SQL editor.
// Commas are included for a list of fields. A dot is added after other items, in order to allow composing
// a fully qualified name by selecting together and dropping a parent item and a child (e.g. select with
// control an attached database and a table, and drag and drop them to get "schema1"."table1".)
// Note that this only makes sense when the "Drop Qualified Names" option is not set.
if(index.column() == ColumnName)
namesData.append(sqlb::escapeIdentifier(data(index, Qt::DisplayRole).toString()) + ", ");
if(objectType == "field")
namesData.append(getNameForDropping(item->parent()->text(ColumnName), item->text(ColumnName)) + ", ");
else if(objectType != "")
if(item->text(ColumnSchema) == "main")
namesData.append(getNameForDropping("", item->text(ColumnName)) + ".");
else
namesData.append(getNameForDropping(item->text(ColumnSchema), item->text(ColumnName)) + ".");
if(objectType != "field" && index.column() == ColumnSQL)
{
@@ -243,8 +261,12 @@ QMimeData* DbStructureModel::mimeData(const QModelIndexList& indices) const
mime->setProperty("db_file", m_db.currentFile()); // Also save the file name to avoid dropping an object on the same database as it comes from
// When we have both SQL and Names data (probable row selection mode) we give precedence to the SQL data
if (sqlData.length() == 0 && namesData.length() > 0) {
// Remove last ", "
namesData.chop(2);
// Remove last ", " or "."
if (namesData.endsWith(", "))
namesData.chop(2);
else if (namesData.endsWith("."))
namesData.chop(1);
mime->setData("text/plain", namesData);
} else
mime->setData("text/plain", sqlData);
@@ -362,3 +384,14 @@ QTreeWidgetItem* DbStructureModel::addNode(QTreeWidgetItem* parent, const sqlb::
return item;
}
QString DbStructureModel::getNameForDropping(const QString& parentName, const QString& itemName) const
{
QString name;
if (m_dropQualifiedNames && parentName != "")
name = m_dropEnquotedNames ? sqlb::escapeIdentifier(parentName) + "." : parentName + ".";
name += m_dropEnquotedNames ? sqlb::escapeIdentifier(itemName) : itemName;
return name;
}

View File

@@ -38,6 +38,8 @@ public:
public slots:
void reloadData();
void setDropQualifiedNames(bool value) { m_dropQualifiedNames = value; };
void setDropEnquotedNames(bool value) { m_dropEnquotedNames = value; };
signals:
void structureUpdated();
@@ -46,9 +48,12 @@ private:
DBBrowserDB& m_db;
QTreeWidgetItem* rootItem;
QTreeWidgetItem* browsablesRootItem;
bool m_dropQualifiedNames;
bool m_dropEnquotedNames;
void buildTree(QTreeWidgetItem* parent, const QString& schema);
QTreeWidgetItem* addNode(QTreeWidgetItem* parent, const sqlb::ObjectPtr& object, const QString& schema);
QString getNameForDropping(const QString& parentName, const QString& itemName) const;
};
#endif

View File

@@ -176,6 +176,10 @@ void MainWindow::init()
popupTableMenu->addAction(ui->actionEditCopyCreateStatement);
popupTableMenu->addAction(ui->actionExportCsvPopup);
popupSchemaDockMenu = new QMenu(this);
popupSchemaDockMenu->addAction(ui->actionDropQualifiedCheck);
popupSchemaDockMenu->addAction(ui->actionEnquoteNamesCheck);
popupOpenDbMenu = new QMenu(this);
popupOpenDbMenu->addAction(ui->fileOpenAction);
popupOpenDbMenu->addAction(ui->fileOpenReadOnlyAction);
@@ -303,6 +307,10 @@ void MainWindow::init()
connect(m_remoteDb, &RemoteDatabase::gotCurrentVersion, this, &MainWindow::checkNewVersion);
connect(m_browseTableModel, &SqliteTableModel::finishedFetch, this, &MainWindow::setRecordsetLabel);
connect(ui->dataTable, &ExtendedTableWidget::selectedRowsToBeDeleted, this, &MainWindow::deleteRecord);
connect(ui->actionDropQualifiedCheck, &QAction::toggled, dbStructureModel, &DbStructureModel::setDropQualifiedNames);
connect(ui->actionEnquoteNamesCheck, &QAction::toggled, dbStructureModel, &DbStructureModel::setDropEnquotedNames);
ui->actionDropQualifiedCheck->setChecked(Settings::getValue("SchemaDock", "dropQualifiedNames").toBool());
ui->actionEnquoteNamesCheck->setChecked(Settings::getValue("SchemaDock", "dropEnquotedNames").toBool());
connect(m_browseTableModel, &SqliteTableModel::finishedFetch, [this](){
auto & settings = browseTableSettings[currentlyBrowsedTableName()];
@@ -715,6 +723,9 @@ void MainWindow::closeEvent( QCloseEvent* event )
Settings::setValue("MainWindow", "geometry", saveGeometry());
Settings::setValue("MainWindow", "windowState", saveState());
Settings::setValue("SQLLogDock", "Log", ui->comboLogSubmittedBy->currentText());
Settings::setValue("SchemaDock", "dropQualifiedNames", ui->actionDropQualifiedCheck->isChecked());
Settings::setValue("SchemaDock", "dropEnquotedNames", ui->actionEnquoteNamesCheck->isChecked());
QMainWindow::closeEvent(event);
} else {
event->ignore();
@@ -1652,6 +1663,12 @@ void MainWindow::createTreeContextMenu(const QPoint &qPoint)
popupTableMenu->exec(ui->dbTreeWidget->mapToGlobal(qPoint));
}
//** DB Schema Dock Context Menu
void MainWindow::createSchemaDockContextMenu(const QPoint &qPoint)
{
popupSchemaDockMenu->exec(ui->treeSchemaDock->mapToGlobal(qPoint));
}
void MainWindow::changeTreeSelection()
{
// Just assume first that something's selected that can not be edited at all

View File

@@ -145,6 +145,7 @@ private:
SqliteTableModel* m_currentTabTableModel;
QMenu* popupTableMenu;
QMenu* popupSchemaDockMenu;
QMenu* recentFilesMenu;
QMenu* popupOpenDbMenu;
QMenu* popupNewRecordMenu;
@@ -214,6 +215,7 @@ public slots:
private slots:
void createTreeContextMenu(const QPoint & qPoint);
void createSchemaDockContextMenu(const QPoint & qPoint);
void changeTreeSelection();
void fileNew();
void fileNewInMemoryDatabase();

View File

@@ -1207,9 +1207,12 @@ You can drag SQL statements from an object row and drop them into other applicat
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<widget class="QTreeView" name="treeSchemaDock">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="whatsThis">
<string>This is the structure of the opened database.
You can drag multiple object names from the Name column and drop them into the SQL editor.
You can drag multiple object names from the Name column and drop them into the SQL editor and you can adjust the properties of the dropped names using the context menu. This would help you in composing SQL statements.
You can drag SQL statements from the Schema column and drop them into the SQL editor or into other applications.
</string>
</property>
@@ -2203,6 +2206,34 @@ You can drag SQL statements from the Schema column and drop them into the SQL ed
<string>New In-&amp;Memory Database</string>
</property>
</action>
<action name="actionDropQualifiedCheck">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Drag &amp;&amp; Drop Qualified Names</string>
</property>
<property name="statusTip">
<string>Use qualified names (e.g. &quot;Table&quot;.&quot;Field&quot;) when dragging the objects and dropping them into the editor </string>
</property>
<property name="whatsThis">
<string>Use qualified names (e.g. &quot;Table&quot;.&quot;Field&quot;) when dragging the objects and dropping them into the editor </string>
</property>
</action>
<action name="actionEnquoteNamesCheck">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Drag &amp;&amp; Drop Enquoted Names</string>
</property>
<property name="statusTip">
<string>Use escaped identifiers (e.g. &quot;Table1&quot;) when dragging the objects and dropping them into the editor </string>
</property>
<property name="whatsThis">
<string>Use escaped identifiers (e.g. &quot;Table1&quot;) when dragging the objects and dropping them into the editor </string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
@@ -2696,6 +2727,22 @@ You can drag SQL statements from the Schema column and drop them into the SQL ed
</hint>
</hints>
</connection>
<connection>
<sender>treeSchemaDock</sender>
<signal>customContextMenuRequested(QPoint)</signal>
<receiver>MainWindow</receiver>
<slot>createSchemaDockContextMenu(QPoint)</slot>
<hints>
<hint type="sourcelabel">
<x>111</x>
<y>261</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
<y>299</y>
</hint>
</hints>
</connection>
<connection>
<sender>viewDBToolbarAction</sender>
<signal>toggled(bool)</signal>

View File

@@ -334,6 +334,17 @@ QVariant Settings::getDefaultValue(const QString& group, const QString& name)
return 4;
}
// SchemaDock Drag & drop settings
if(group == "SchemaDock")
{
if(name == "dropQualifiedNames")
return false;
if(name == "dropEnquotedNames")
return true;
}
// Remote settings?
if(group == "remote")
{