summaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/guiSQLiteStudio/dbtree
diff options
context:
space:
mode:
Diffstat (limited to 'SQLiteStudio3/guiSQLiteStudio/dbtree')
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp1557
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h205
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui90
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp332
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h112
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp164
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.h28
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp74
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.h26
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp1222
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h135
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.cpp255
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h57
13 files changed, 4257 insertions, 0 deletions
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp
new file mode 100644
index 0000000..98baaa9
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp
@@ -0,0 +1,1557 @@
+#include "dbtree.h"
+#include "dbtreeitem.h"
+#include "ui_dbtree.h"
+#include "actionentry.h"
+#include "common/utils_sql.h"
+#include "dbtreemodel.h"
+#include "dialogs/dbdialog.h"
+#include "services/dbmanager.h"
+#include "iconmanager.h"
+#include "common/global.h"
+#include "services/notifymanager.h"
+#include "mainwindow.h"
+#include "mdiarea.h"
+#include "common/unused.h"
+#include "dbobjectdialogs.h"
+#include "common/userinputfilter.h"
+#include "common/widgetcover.h"
+#include "windows/tablewindow.h"
+#include "dialogs/indexdialog.h"
+#include "dialogs/triggerdialog.h"
+#include "dialogs/exportdialog.h"
+#include "dialogs/importdialog.h"
+#include "dialogs/populatedialog.h"
+#include "services/importmanager.h"
+#include "windows/editorwindow.h"
+#include "uiconfig.h"
+#include <QApplication>
+#include <QClipboard>
+#include <QAction>
+#include <QMenu>
+#include <QInputDialog>
+#include <QMessageBox>
+#include <QTimer>
+#include <QDebug>
+#include <QKeyEvent>
+#include <QMimeData>
+#include <dialogs/dbconverterdialog.h>
+
+CFG_KEYS_DEFINE(DbTree)
+QHash<DbTreeItem::Type,QList<DbTreeItem::Type>> DbTree::allowedTypesInside;
+QSet<DbTreeItem::Type> DbTree::draggableTypes;
+
+DbTree::DbTree(QWidget *parent) :
+ QDockWidget(parent),
+ ui(new Ui::DbTree)
+{
+ init();
+}
+
+DbTree::~DbTree()
+{
+ delete ui;
+ delete treeModel;
+}
+
+void DbTree::staticInit()
+{
+ initDndTypes();
+}
+
+void DbTree::init()
+{
+ ui->setupUi(this);
+ initDndTypes();
+
+ ui->nameFilter->setClearButtonEnabled(true);
+
+ widgetCover = new WidgetCover(this);
+ widgetCover->initWithInterruptContainer();
+ widgetCover->hide();
+ connect(widgetCover, SIGNAL(cancelClicked()), this, SLOT(interrupt()));
+
+ treeModel = new DbTreeModel();
+ treeModel->setTreeView(ui->treeView);
+
+ new UserInputFilter(ui->nameFilter, treeModel, SLOT(applyFilter(QString)));
+
+ ui->treeView->setDbTree(this);
+ ui->treeView->setModel(treeModel);
+
+ initActions();
+
+ if (DBLIST->getDbList().size() > 0)
+ treeModel->loadDbList();
+
+ connect(DBLIST, SIGNAL(dbListLoaded()), treeModel, SLOT(loadDbList()));
+ connect(ui->treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &DbTree::currentChanged);
+ connect(DBLIST, SIGNAL(dbConnected(Db*)), this, SLOT(dbConnected(Db*)));
+ connect(DBLIST, SIGNAL(dbDisconnected(Db*)), this, SLOT(dbDisconnected(Db*)));
+ connect(IMPORT_MANAGER, SIGNAL(schemaModified(Db*)), this, SLOT(refreshSchema(Db*)));
+
+ connect(CFG_UI.Fonts.DbTree, SIGNAL(changed(QVariant)), this, SLOT(refreshFont()));
+
+ updateActionsForCurrent();
+}
+
+void DbTree::createActions()
+{
+ createAction(COPY, ICONS.ACT_COPY, tr("Copy"), this, SLOT(copy()), this);
+ createAction(PASTE, ICONS.ACT_PASTE, tr("Paste"), this, SLOT(paste()), this);
+ createAction(SELECT_ALL, ICONS.ACT_SELECT_ALL, tr("Select all"), this, SLOT(selectAll()), this);
+ createAction(CREATE_GROUP, ICONS.DIRECTORY_ADD, tr("Create a group"), this, SLOT(createGroup()), this);
+ createAction(DELETE_GROUP, ICONS.DIRECTORY_DEL, tr("Delete the group"), this, SLOT(deleteGroup()), this);
+ createAction(RENAME_GROUP, ICONS.DIRECTORY_EDIT, tr("Rename the group"), this, SLOT(renameGroup()), this);
+ createAction(ADD_DB, ICONS.DATABASE_ADD, tr("Add a database"), this, SLOT(addDb()), this);
+ createAction(EDIT_DB, ICONS.DATABASE_EDIT, tr("Edit the database"), this, SLOT(editDb()), this);
+ createAction(DELETE_DB, ICONS.DATABASE_DEL, tr("Remove the database"), this, SLOT(removeDb()), this);
+ createAction(CONNECT_TO_DB, ICONS.DATABASE_CONNECT, tr("Connect to the database"), this, SLOT(connectToDb()), this);
+ createAction(DISCONNECT_FROM_DB, ICONS.DATABASE_DISCONNECT, tr("Disconnect from the database"), this, SLOT(disconnectFromDb()), this);
+ createAction(IMPORT_INTO_DB, ICONS.IMPORT, tr("Import"), this, SLOT(import()), this);
+ createAction(EXPORT_DB, ICONS.DATABASE_EXPORT, tr("Export the database"), this, SLOT(exportDb()), this);
+ createAction(CONVERT_DB, ICONS.CONVERT_DB, tr("Convert database type"), this, SLOT(convertDb()), this);
+ createAction(VACUUM_DB, ICONS.VACUUM_DB, tr("Vacuum"), this, SLOT(vacuumDb()), this);
+ createAction(INTEGRITY_CHECK, ICONS.INTEGRITY_CHECK, tr("Integrity check"), this, SLOT(integrityCheck()), this);
+ createAction(ADD_TABLE, ICONS.TABLE_ADD, tr("Create a table"), this, SLOT(addTable()), this);
+ createAction(EDIT_TABLE, ICONS.TABLE_EDIT, tr("Edit the table"), this, SLOT(editTable()), this);
+ createAction(DEL_TABLE, ICONS.TABLE_DEL, tr("Drop the table"), this, SLOT(delTable()), this);
+ createAction(EXPORT_TABLE, ICONS.TABLE_EXPORT, tr("Export the table"), this, SLOT(exportTable()), this);
+ createAction(IMPORT_TABLE, ICONS.TABLE_IMPORT, tr("Import into the table"), this, SLOT(importTable()), this);
+ createAction(POPULATE_TABLE, ICONS.TABLE_POPULATE, tr("Populate table"), this, SLOT(populateTable()), this);
+ createAction(CREATE_SIMILAR_TABLE, ICONS.TABLE_CREATE_SIMILAR, tr("Create similar table"), this, SLOT(createSimilarTable()), this);
+ createAction(ADD_INDEX, ICONS.INDEX_ADD, tr("Create an index"), this, SLOT(addIndex()), this);
+ createAction(EDIT_INDEX, ICONS.INDEX_EDIT, tr("Edit the index"), this, SLOT(editIndex()), this);
+ createAction(DEL_INDEX, ICONS.INDEX_DEL, tr("Drop the index"), this, SLOT(delIndex()), this);
+ createAction(ADD_TRIGGER, ICONS.TRIGGER_ADD, tr("Create a trigger"), this, SLOT(addTrigger()), this);
+ createAction(EDIT_TRIGGER, ICONS.TRIGGER_EDIT, tr("Edit the trigger"), this, SLOT(editTrigger()), this);
+ createAction(DEL_TRIGGER, ICONS.TRIGGER_DEL, tr("Drop the trigger"), this, SLOT(delTrigger()), this);
+ createAction(ADD_VIEW, ICONS.VIEW_ADD, tr("Create a view"), this, SLOT(addView()), this);
+ createAction(EDIT_VIEW, ICONS.VIEW_EDIT, tr("Edit the view"), this, SLOT(editView()), this);
+ createAction(DEL_VIEW, ICONS.VIEW_DEL, tr("Drop the view"), this, SLOT(delView()), this);
+ createAction(ADD_COLUMN, ICONS.TABLE_COLUMN_ADD, tr("Add a column"), this, SLOT(addColumn()), this);
+ createAction(EDIT_COLUMN, ICONS.TABLE_COLUMN_EDIT, tr("Edit the column"), this, SLOT(editColumn()), this);
+ createAction(DEL_COLUMN, ICONS.TABLE_COLUMN_DELETE, tr("Delete the column"), this, SLOT(delColumn()), this);
+ createAction(DEL_SELECTED, ICONS.DELETE_SELECTED, tr("Delete selected items"), this, SLOT(deleteSelected()), this);
+ createAction(CLEAR_FILTER, tr("Clear filter"), ui->nameFilter, SLOT(clear()), this);
+ createAction(REFRESH_SCHEMAS, ICONS.DATABASE_RELOAD, tr("Refresh all database schemas"), this, SLOT(refreshSchemas()), this);
+ createAction(REFRESH_SCHEMA, ICONS.DATABASE_RELOAD, tr("Refresh selected database schema"), this, SLOT(refreshSchema()), this);
+}
+
+void DbTree::updateActionStates(const QStandardItem *item)
+{
+ QList<int> enabled;
+ const DbTreeItem* dbTreeItem = dynamic_cast<const DbTreeItem*>(item);
+ if (item != nullptr)
+ {
+ bool isDbOpen = false;
+ DbTreeItem* parentItem = dbTreeItem->parentDbTreeItem();
+ DbTreeItem* grandParentItem = parentItem ? parentItem->parentDbTreeItem() : nullptr;
+
+ // Add database should always be available, as well as a copy of an item
+ enabled << ADD_DB << COPY;
+
+ if (isMimeDataValidForItem(QApplication::clipboard()->mimeData(), dbTreeItem))
+ enabled << PASTE;
+
+ enabled << CLEAR_FILTER;
+
+ // Group actions
+ if (dbTreeItem->getType() == DbTreeItem::Type::DIR)
+ enabled << CREATE_GROUP << RENAME_GROUP << DELETE_GROUP << ADD_DB;
+
+ if (dbTreeItem->getDb())
+ {
+ enabled << DELETE_DB << EDIT_DB;
+ if (dbTreeItem->getDb()->isOpen())
+ {
+ enabled << DISCONNECT_FROM_DB << ADD_TABLE << ADD_VIEW << IMPORT_INTO_DB << EXPORT_DB << REFRESH_SCHEMA << CONVERT_DB
+ << VACUUM_DB << INTEGRITY_CHECK;
+ isDbOpen = true;
+ }
+ else
+ enabled << CONNECT_TO_DB;
+ }
+
+ if (isDbOpen)
+ {
+ switch (dbTreeItem->getType())
+ {
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ break;
+ case DbTreeItem::Type::DIR:
+ // It's handled outside of "item with db", above
+ break;
+ case DbTreeItem::Type::DB:
+ enabled << CREATE_GROUP << DELETE_DB << EDIT_DB;
+ break;
+ case DbTreeItem::Type::TABLES:
+ break;
+ case DbTreeItem::Type::TABLE:
+ enabled << EDIT_TABLE << DEL_TABLE << EXPORT_TABLE << IMPORT_TABLE << POPULATE_TABLE << ADD_COLUMN << CREATE_SIMILAR_TABLE;
+ enabled << ADD_INDEX << ADD_TRIGGER;
+ break;
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ // TODO change below when virtual tables can be edited
+// enabled << EDIT_TABLE << DEL_TABLE;
+ enabled << DEL_TABLE;
+ break;
+ case DbTreeItem::Type::INDEXES:
+ enabled << EDIT_TABLE << DEL_TABLE;
+ enabled << ADD_INDEX << ADD_TRIGGER;
+ break;
+ case DbTreeItem::Type::INDEX:
+ enabled << EDIT_TABLE << DEL_TABLE;
+ enabled << EDIT_INDEX << DEL_INDEX;
+ enabled << ADD_INDEX << ADD_TRIGGER;
+ break;
+ case DbTreeItem::Type::TRIGGERS:
+ {
+ if (parentItem->getType() == DbTreeItem::Type::TABLE)
+ {
+ enabled << EDIT_TABLE << DEL_TABLE;
+ enabled << ADD_INDEX << ADD_TRIGGER;
+ }
+ else
+ {
+ enabled << EDIT_VIEW << DEL_VIEW;
+ enabled << ADD_TRIGGER;
+ }
+
+ enabled << ADD_TRIGGER;
+ break;
+ }
+ case DbTreeItem::Type::TRIGGER:
+ {
+ if (grandParentItem->getType() == DbTreeItem::Type::TABLE)
+ {
+ enabled << EDIT_TABLE << DEL_TABLE;
+ enabled << ADD_INDEX << ADD_TRIGGER;
+ }
+ else
+ {
+ enabled << EDIT_VIEW << DEL_VIEW;
+ enabled << ADD_TRIGGER;
+ }
+
+ enabled << EDIT_TRIGGER << DEL_TRIGGER;
+ break;
+ }
+ case DbTreeItem::Type::VIEWS:
+ break;
+ case DbTreeItem::Type::VIEW:
+ enabled << EDIT_VIEW << DEL_VIEW;
+ enabled << ADD_TRIGGER;
+ break;
+ case DbTreeItem::Type::COLUMNS:
+ enabled << EDIT_TABLE << DEL_TABLE << EXPORT_TABLE << IMPORT_TABLE << POPULATE_TABLE << ADD_COLUMN;
+ enabled << ADD_INDEX << ADD_TRIGGER;
+ break;
+ case DbTreeItem::Type::COLUMN:
+ enabled << EDIT_TABLE << DEL_TABLE << EXPORT_TABLE << IMPORT_TABLE << POPULATE_TABLE << ADD_COLUMN << DEL_COLUMN;
+ enabled << EDIT_COLUMN;
+ enabled << ADD_INDEX << ADD_TRIGGER;
+ break;
+ }
+ }
+
+ // Do we have any deletable object selected? If yes, enable "Del" action.
+ bool enableDel = false;
+ for (DbTreeItem* selItem : getModel()->getItemsForIndexes(getView()->getSelectedIndexes()))
+ {
+ switch (selItem->getType())
+ {
+ case DbTreeItem::Type::COLUMN:
+ case DbTreeItem::Type::DB:
+ case DbTreeItem::Type::DIR:
+ case DbTreeItem::Type::INDEX:
+ case DbTreeItem::Type::TABLE:
+ case DbTreeItem::Type::TRIGGER:
+ case DbTreeItem::Type::VIEW:
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ enableDel = true;
+ break;
+ case DbTreeItem::Type::COLUMNS:
+ case DbTreeItem::Type::INDEXES:
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ case DbTreeItem::Type::TABLES:
+ case DbTreeItem::Type::TRIGGERS:
+ case DbTreeItem::Type::VIEWS:
+ break;
+ }
+
+ if (enableDel)
+ {
+ enabled << DEL_SELECTED;
+ break;
+ }
+ }
+ }
+ else
+ {
+ enabled << CREATE_GROUP << ADD_DB;
+ }
+
+ if (treeModel->rowCount() > 0)
+ enabled << SELECT_ALL; // if there's at least 1 item, enable this
+
+ enabled << REFRESH_SCHEMAS;
+
+ foreach (int action, actionMap.keys())
+ setActionEnabled(action, enabled.contains(action));
+}
+
+void DbTree::setupActionsForMenu(DbTreeItem* currItem, QMenu* contextMenu)
+{
+ QList<ActionEntry> actions;
+
+ ActionEntry dbEntry(ICONS.DATABASE, tr("Datatabase"));
+ dbEntry += ADD_DB;
+ dbEntry += EDIT_DB;
+ dbEntry += DELETE_DB;
+
+ ActionEntry dbEntryExt(ICONS.DATABASE, tr("Datatabase"));
+ dbEntryExt += CONNECT_TO_DB;
+ dbEntryExt += DISCONNECT_FROM_DB;
+ dbEntryExt += _separator;
+ dbEntryExt += REFRESH_SCHEMA;
+ dbEntryExt += _separator;
+ dbEntryExt += ADD_DB;
+ dbEntryExt += EDIT_DB;
+ dbEntryExt += DELETE_DB;
+
+ ActionEntry groupEntry(ICONS.DIRECTORY, tr("Grouping"));
+ groupEntry += CREATE_GROUP;
+ groupEntry += RENAME_GROUP;
+ groupEntry += DELETE_GROUP;
+
+ if (currItem)
+ {
+ DbTreeItem* parentItem = currItem->parentDbTreeItem();
+ DbTreeItem* grandParentItem = parentItem ? parentItem->parentDbTreeItem() : nullptr;
+ DbTreeItem::Type itemType = currItem->getType();
+ switch (itemType)
+ {
+ case DbTreeItem::Type::DIR:
+ {
+ actions += ActionEntry(CREATE_GROUP);
+ actions += ActionEntry(RENAME_GROUP);
+ actions += ActionEntry(DELETE_GROUP);
+ actions += ActionEntry(_separator);
+ actions += dbEntry;
+ break;
+ }
+ case DbTreeItem::Type::DB:
+ {
+ if (currItem->getDb()->isValid())
+ {
+ actions += ActionEntry(CONNECT_TO_DB);
+ actions += ActionEntry(DISCONNECT_FROM_DB);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_DB);
+ actions += ActionEntry(EDIT_DB);
+ actions += ActionEntry(DELETE_DB);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(ADD_INDEX);
+ actions += ActionEntry(ADD_TRIGGER);
+ actions += ActionEntry(ADD_VIEW);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(REFRESH_SCHEMA);
+ actions += ActionEntry(IMPORT_INTO_DB);
+ actions += ActionEntry(EXPORT_DB);
+ actions += ActionEntry(CONVERT_DB);
+ actions += ActionEntry(VACUUM_DB);
+ actions += ActionEntry(INTEGRITY_CHECK);
+ actions += ActionEntry(_separator);
+ }
+ else
+ {
+ actions += ActionEntry(ADD_DB);
+ actions += ActionEntry(EDIT_DB);
+ actions += ActionEntry(DELETE_DB);
+ actions += ActionEntry(_separator);
+ }
+ break;
+ }
+ case DbTreeItem::Type::TABLES:
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::TABLE:
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(EDIT_TABLE);
+ actions += ActionEntry(DEL_TABLE);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_COLUMN);
+ actions += ActionEntry(ADD_INDEX);
+ actions += ActionEntry(ADD_TRIGGER);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(IMPORT_TABLE);
+ actions += ActionEntry(EXPORT_TABLE);
+ actions += ActionEntry(POPULATE_TABLE);
+ actions += ActionEntry(CREATE_SIMILAR_TABLE);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ actions += ActionEntry(ADD_TABLE);
+ //actions += ActionEntry(EDIT_TABLE); // TODO uncomment when virtual tables have their own edition window
+ actions += ActionEntry(DEL_TABLE);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::INDEXES:
+ actions += ActionEntry(ADD_INDEX);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(EDIT_TABLE);
+ actions += ActionEntry(DEL_TABLE);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::INDEX:
+ actions += ActionEntry(ADD_INDEX);
+ actions += ActionEntry(EDIT_INDEX);
+ actions += ActionEntry(DEL_INDEX);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(EDIT_TABLE);
+ actions += ActionEntry(DEL_TABLE);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::TRIGGERS:
+ {
+ actions += ActionEntry(ADD_TRIGGER);
+ actions += ActionEntry(_separator);
+ if (parentItem->getType() == DbTreeItem::Type::TABLE)
+ {
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(EDIT_TABLE);
+ actions += ActionEntry(DEL_TABLE);
+ }
+ else
+ {
+ actions += ActionEntry(ADD_VIEW);
+ actions += ActionEntry(EDIT_VIEW);
+ actions += ActionEntry(DEL_VIEW);
+ }
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ }
+ case DbTreeItem::Type::TRIGGER:
+ {
+ actions += ActionEntry(ADD_TRIGGER);
+ actions += ActionEntry(EDIT_TRIGGER);
+ actions += ActionEntry(DEL_TRIGGER);
+ actions += ActionEntry(_separator);
+ if (grandParentItem->getType() == DbTreeItem::Type::TABLE)
+ {
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(EDIT_TABLE);
+ actions += ActionEntry(DEL_TABLE);
+ }
+ else
+ {
+ actions += ActionEntry(ADD_VIEW);
+ actions += ActionEntry(EDIT_VIEW);
+ actions += ActionEntry(DEL_VIEW);
+ }
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ }
+ case DbTreeItem::Type::VIEWS:
+ actions += ActionEntry(ADD_VIEW);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::VIEW:
+ actions += ActionEntry(ADD_VIEW);
+ actions += ActionEntry(EDIT_VIEW);
+ actions += ActionEntry(DEL_VIEW);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_TRIGGER);
+ actions += ActionEntry(EDIT_TRIGGER);
+ actions += ActionEntry(DEL_TRIGGER);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::COLUMNS:
+ actions += ActionEntry(ADD_COLUMN);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(EDIT_TABLE);
+ actions += ActionEntry(DEL_TABLE);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_INDEX);
+ actions += ActionEntry(ADD_TRIGGER);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(IMPORT_TABLE);
+ actions += ActionEntry(EXPORT_TABLE);
+ actions += ActionEntry(POPULATE_TABLE);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::COLUMN:
+ actions += ActionEntry(ADD_COLUMN);
+ actions += ActionEntry(EDIT_COLUMN);
+ actions += ActionEntry(DEL_COLUMN);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_TABLE);
+ actions += ActionEntry(EDIT_TABLE);
+ actions += ActionEntry(DEL_TABLE);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(ADD_INDEX);
+ actions += ActionEntry(ADD_TRIGGER);
+ actions += ActionEntry(_separator);
+ actions += ActionEntry(IMPORT_TABLE);
+ actions += ActionEntry(EXPORT_TABLE);
+ actions += ActionEntry(POPULATE_TABLE);
+ actions += ActionEntry(_separator);
+ actions += dbEntryExt;
+ break;
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ break;
+ }
+
+ actions += ActionEntry(_separator);
+
+ if (itemType == DbTreeItem::Type::DB)
+ actions += groupEntry;
+ }
+ else
+ {
+ actions += dbEntry;
+ actions += ActionEntry(_separator);
+ actions += groupEntry;
+ }
+
+ actions += COPY;
+ actions += PASTE;
+ actions += _separator;
+ actions += DEL_SELECTED;
+ actions += _separator;
+ actions += SELECT_ALL;
+ actions += ActionEntry(REFRESH_SCHEMAS);
+
+ QMenu* subMenu = nullptr;
+ foreach (ActionEntry actionEntry, actions)
+ {
+ switch (actionEntry.type)
+ {
+ case ActionEntry::Type::SINGLE:
+ {
+ if (actionEntry.action == DbTree::_separator)
+ {
+ contextMenu->addSeparator();
+ break;
+ }
+ contextMenu->addAction(actionMap[actionEntry.action]);
+ break;
+ }
+ case ActionEntry::Type::SUB_MENU:
+ {
+ subMenu = contextMenu->addMenu(actionEntry.subMenuIcon, actionEntry.subMenuLabel);
+ foreach (Action action, actionEntry.actions)
+ {
+ if (action == DbTree::_separator)
+ {
+ subMenu->addSeparator();
+ continue;
+ }
+ subMenu->addAction(actionMap[action]);
+ }
+ break;
+ }
+ }
+ }
+}
+
+void DbTree::initDndTypes()
+{
+ draggableTypes << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW << DbTreeItem::Type::DIR << DbTreeItem::Type::DB;
+
+ allowedTypesInside[DbTreeItem::Type::DIR] << DbTreeItem::Type::DB << DbTreeItem::Type::DIR;
+ allowedTypesInside[DbTreeItem::Type::DB] << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW;
+ allowedTypesInside[DbTreeItem::Type::TABLES] << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW;
+ allowedTypesInside[DbTreeItem::Type::TABLE] << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW;
+ allowedTypesInside[DbTreeItem::Type::VIEWS] << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW;
+ allowedTypesInside[DbTreeItem::Type::VIEW] << DbTreeItem::Type::TABLE << DbTreeItem::Type::VIEW;
+}
+
+QVariant DbTree::saveSession()
+{
+ treeModel->storeGroups();
+ return QVariant();
+}
+
+void DbTree::restoreSession(const QVariant& sessionValue)
+{
+ UNUSED(sessionValue);
+}
+
+DbTreeModel* DbTree::getModel() const
+{
+ return treeModel;
+}
+
+DbTreeView*DbTree::getView() const
+{
+ return ui->treeView;
+}
+
+bool DbTree::isMimeDataValidForItem(const QMimeData* mimeData, const DbTreeItem* item)
+{
+ if (mimeData->formats().contains(DbTreeModel::MIMETYPE))
+ return areDbTreeItemsValidForItem(getModel()->getDragItems(mimeData), item);
+ else if (mimeData->hasUrls())
+ return areUrlsValidForItem(mimeData->urls(), item);
+
+ return false;
+}
+
+bool DbTree::isItemDraggable(const DbTreeItem* item)
+{
+ return item && draggableTypes.contains(item->getType());
+}
+
+bool DbTree::areDbTreeItemsValidForItem(QList<DbTreeItem*> srcItems, const DbTreeItem* dstItem)
+{
+ QSet<Db*> srcDbs;
+ QList<DbTreeItem::Type> srcTypes;
+ DbTreeItem::Type dstType = DbTreeItem::Type::DIR; // the empty space is treated as group
+ if (dstItem)
+ dstType = dstItem->getType();
+
+ for (DbTreeItem* srcItem : srcItems)
+ {
+ if (srcItem)
+ srcTypes << srcItem->getType();
+ else
+ srcTypes << DbTreeItem::Type::ITEM_PROTOTYPE;
+
+ if (srcItem->getDb())
+ srcDbs << srcItem->getDb();
+ }
+
+ for (DbTreeItem::Type srcType : srcTypes)
+ {
+ if (!allowedTypesInside[dstType].contains(srcType))
+ return false;
+
+ if (dstType == DbTreeItem::Type::DB && !dstItem->getDb()->isOpen())
+ return false;
+ }
+
+ if (dstItem && dstItem->getDb() && srcDbs.contains(dstItem->getDb()))
+ return false;
+
+ return true;
+}
+
+bool DbTree::areUrlsValidForItem(const QList<QUrl>& srcUrls, const DbTreeItem* dstItem)
+{
+ UNUSED(dstItem);
+ for (const QUrl& srcUrl : srcUrls)
+ {
+ if (!srcUrl.isLocalFile())
+ return false;
+ }
+ return true;
+}
+
+void DbTree::showWidgetCover()
+{
+ widgetCover->show();
+}
+
+void DbTree::hideWidgetCover()
+{
+ widgetCover->hide();
+}
+
+void DbTree::setSelectedItem(DbTreeItem *item)
+{
+ ui->treeView->setCurrentIndex(item->index());
+ ui->treeView->selectionModel()->select(item->index(), QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent);
+}
+
+QToolBar* DbTree::getToolBar(int toolbar) const
+{
+ UNUSED(toolbar);
+ return nullptr;
+}
+
+void DbTree::setActionEnabled(int action, bool enabled)
+{
+ actionMap[action]->setEnabled(enabled);
+}
+
+Db* DbTree::getSelectedDb()
+{
+ DbTreeItem* item = ui->treeView->currentItem();
+ if (!item)
+ return nullptr;
+
+ return item->getDb();
+}
+
+Db* DbTree::getSelectedOpenDb()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isOpen())
+ return nullptr;
+
+ return db;
+}
+
+TableWindow* DbTree::openTable(DbTreeItem* item)
+{
+ QString database = QString::null; // TODO implement this when named databases (attached) are handled by dbtree.
+ Db* db = item->getDb();
+ return openTable(db, database, item->text());
+}
+
+TableWindow* DbTree::openTable(Db* db, const QString& database, const QString& table)
+{
+ DbObjectDialogs dialogs(db);
+ return dialogs.editTable(database, table);
+}
+
+void DbTree::editIndex(DbTreeItem* item)
+{
+ //QString database = QString::null; // TODO implement this when named databases (attached) are handled by dbtree.
+ Db* db = item->getDb();
+
+ DbObjectDialogs dialogs(db);
+ dialogs.editIndex(item->text());
+}
+
+ViewWindow* DbTree::openView(DbTreeItem* item)
+{
+ QString database = QString::null; // TODO implement this when named databases (attached) are handled by dbtree.
+ Db* db = item->getDb();
+ return openView(db, database, item->text());
+}
+
+ViewWindow* DbTree::openView(Db* db, const QString& database, const QString& view)
+{
+ DbObjectDialogs dialogs(db);
+ return dialogs.editView(database, view);
+}
+
+TableWindow* DbTree::newTable(DbTreeItem* item)
+{
+ Db* db = item->getDb();
+
+ DbObjectDialogs dialogs(db);
+ return dialogs.addTable();
+}
+
+ViewWindow* DbTree::newView(DbTreeItem* item)
+{
+ Db* db = item->getDb();
+
+ DbObjectDialogs dialogs(db);
+ return dialogs.addView();
+}
+
+void DbTree::editTrigger(DbTreeItem* item)
+{
+ //QString database = QString::null; // TODO implement this when named databases (attached) are handled by dbtree.
+ Db* db = item->getDb();
+
+ DbObjectDialogs dialogs(db);
+ dialogs.editTrigger(item->text());
+}
+
+void DbTree::delSelectedObject()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db)
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ if (!item)
+ return;
+
+ DbObjectDialogs dialogs(db);
+ dialogs.dropObject(item->text()); // TODO add database prefix when supported
+}
+
+void DbTree::filterUndeletableItems(QList<DbTreeItem*>& items)
+{
+ QMutableListIterator<DbTreeItem*> it(items);
+ DbTreeItem::Type type;
+ while (it.hasNext())
+ {
+ type = it.next()->getType();
+ switch (type)
+ {
+ case DbTreeItem::Type::TABLES:
+ case DbTreeItem::Type::INDEXES:
+ case DbTreeItem::Type::TRIGGERS:
+ case DbTreeItem::Type::VIEWS:
+ case DbTreeItem::Type::COLUMNS:
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ it.remove();
+ break;
+ case DbTreeItem::Type::DIR:
+ case DbTreeItem::Type::DB:
+ case DbTreeItem::Type::TABLE:
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ case DbTreeItem::Type::INDEX:
+ case DbTreeItem::Type::TRIGGER:
+ case DbTreeItem::Type::VIEW:
+ case DbTreeItem::Type::COLUMN:
+ break;
+ }
+ }
+}
+
+void DbTree::filterItemsWithParentInList(QList<DbTreeItem*>& items)
+{
+ QMutableListIterator<DbTreeItem*> it(items);
+ DbTreeItem* item = nullptr;
+ DbTreeItem* pathItem = nullptr;
+ while (it.hasNext())
+ {
+ item = it.next();
+ foreach (pathItem, item->getPathToRoot().mid(1))
+ {
+ if (items.contains(pathItem) && pathItem->getType() != DbTreeItem::Type::DIR)
+ {
+ it.remove();
+ break;
+ }
+ }
+ }
+}
+
+void DbTree::deleteItem(DbTreeItem* item)
+{
+ switch (item->getType())
+ {
+ case DbTreeItem::Type::DIR:
+ treeModel->deleteGroup(item);
+ break;
+ case DbTreeItem::Type::DB:
+ DBLIST->removeDb(item->getDb());
+ break;
+ case DbTreeItem::Type::TABLE:
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ case DbTreeItem::Type::INDEX:
+ case DbTreeItem::Type::TRIGGER:
+ case DbTreeItem::Type::VIEW:
+ {
+ Db* db = item->getDb();
+ DbObjectDialogs dialogs(db);
+ dialogs.setNoConfirmation(true); // confirmation is done in deleteSelected()
+ dialogs.setNoSchemaRefreshing(true); // we will refresh after all items are deleted
+ dialogs.dropObject(item->text()); // TODO database name when supported
+ break;
+ }
+ case DbTreeItem::Type::TABLES:
+ case DbTreeItem::Type::INDEXES:
+ case DbTreeItem::Type::TRIGGERS:
+ case DbTreeItem::Type::VIEWS:
+ case DbTreeItem::Type::COLUMNS:
+ case DbTreeItem::Type::COLUMN:
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ break;
+ }
+}
+
+
+void DbTree::refreshSchema(Db* db)
+{
+ if (!db)
+ return;
+
+ if (!db->isOpen())
+ return;
+
+ treeModel->refreshSchema(db);
+}
+
+void DbTree::copy()
+{
+ QMimeData* mimeData = treeModel->mimeData(ui->treeView->getSelectedIndexes());
+ QApplication::clipboard()->setMimeData(mimeData);
+}
+
+void DbTree::paste()
+{
+ DbTreeItem* currItem = ui->treeView->currentItem();
+ QModelIndex idx;
+ if (currItem)
+ idx = currItem->index();
+
+ treeModel->pasteData(QApplication::clipboard()->mimeData(), -1, -1, idx, Qt::CopyAction);
+}
+
+void DbTree::selectAll()
+{
+ ui->treeView->selectAll();
+}
+
+void DbTree::createGroup()
+{
+ QStringList existingItems;
+ QStandardItem* currItem = ui->treeView->getItemForAction(true);
+ DbTreeItem* itemToMove = nullptr;
+ if (currItem)
+ {
+ // Look for any directory in the path to the root, starting with the current item
+ do
+ {
+ if (dynamic_cast<DbTreeItem*>(currItem)->getType() == DbTreeItem::Type::DIR)
+ {
+ existingItems = dynamic_cast<DbTreeItem*>(currItem)->childNames();
+ break;
+ }
+ else
+ {
+ itemToMove = dynamic_cast<DbTreeItem*>(currItem);
+ }
+ }
+ while ((currItem = currItem->parent()) != nullptr);
+ }
+
+ // No luck? Use root.
+ if (!currItem)
+ currItem = treeModel->root();
+
+ QString name = "";
+ while (existingItems.contains(name = QInputDialog::getText(this, tr("Create group"), tr("Group name"))) ||
+ (name.isEmpty() && !name.isNull()))
+ {
+ QMessageBox::information(this, tr("Create directory"), tr("Entry with name %1 already exists in directory %2.")
+ .arg(name).arg(currItem->text()), QMessageBox::Ok);
+ }
+
+ if (name.isNull())
+ return;
+
+ DbTreeItem* newDir = treeModel->createGroup(name, currItem);
+ if (itemToMove)
+ treeModel->move(itemToMove, newDir);
+}
+
+void DbTree::deleteGroup()
+{
+ DbTreeItem* item = ui->treeView->getItemForAction();
+ if (!item)
+ return;
+
+ QMessageBox::StandardButton resp = QMessageBox::question(this, tr("Delete group"),
+ tr("Are you sure you want to delete group %1?\nAll objects from this group will be moved to parent group.").arg(item->text().left(ITEM_TEXT_LIMIT)));
+
+ if (resp != QMessageBox::Yes)
+ return;
+
+ treeModel->deleteGroup(item);
+}
+
+void DbTree::renameGroup()
+{
+ DbTreeItem* item = ui->treeView->getItemForAction();
+ if (!item)
+ return;
+
+ ui->treeView->edit(item->index());
+}
+
+void DbTree::addDb()
+{
+ DbTreeItem* currItem = ui->treeView->getItemForAction();
+
+ DbDialog dialog(DbDialog::ADD, this);
+ if (!dialog.exec())
+ return;
+
+ QString name = dialog.getName();
+
+ // If we created db in some group, move it there
+ if (currItem && currItem->getType() == DbTreeItem::Type::DIR)
+ {
+ DbTreeItem* dbItem = dynamic_cast<DbTreeItem*>(treeModel->findItem(DbTreeItem::Type::DB, name));
+ if (!dbItem)
+ {
+ qWarning() << "Created and added db to tree, but could not find it while trying to move it to target group" << currItem->text();
+ return;
+ }
+ treeModel->move(dbItem, currItem);
+ }
+}
+
+void DbTree::editDb()
+{
+ Db* db = getSelectedDb();
+ if (!db)
+ return;
+
+ bool perm = CFG->isDbInConfig(db->getName());
+
+ DbDialog dialog(DbDialog::EDIT, this);
+ dialog.setDb(db);
+ dialog.setPermanent(perm);
+ dialog.exec();
+}
+
+void DbTree::removeDb()
+{
+ Db* db = getSelectedDb();
+ if (!db)
+ return;
+
+ QMessageBox::StandardButton result = QMessageBox::question(this, tr("Delete database"), tr("Are you sure you want to delete database '%1'?").arg(db->getName().left(ITEM_TEXT_LIMIT)));
+ if (result != QMessageBox::Yes)
+ return;
+
+ DBLIST->removeDb(db);
+}
+
+void DbTree::connectToDb()
+{
+ Db* db = getSelectedDb();
+ if (!db)
+ return;
+
+ if (db->isOpen())
+ return;
+
+ db->open();
+}
+
+void DbTree::disconnectFromDb()
+{
+ Db* db = getSelectedDb();
+ if (!db)
+ return;
+
+ if (!db->isOpen())
+ return;
+
+ db->close();
+}
+
+
+void DbTree::import()
+{
+ if (!ImportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot import, because no import plugin is loaded."));
+ return;
+ }
+
+ ImportDialog dialog(this);
+ Db* db = getSelectedDb();
+ if (db)
+ dialog.setDb(db);
+
+ dialog.exec();
+}
+
+void DbTree::exportDb()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ if (!ExportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot export, because no export plugin is loaded."));
+ return;
+ }
+
+ ExportDialog dialog(this);
+ dialog.setDatabaseMode(db);
+ dialog.exec();
+}
+
+void DbTree::addTable()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ newTable(item);
+}
+
+void DbTree::editTable()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+ if (table.isNull())
+ {
+ qWarning() << "Tried to edit table, while table wasn't selected in DbTree.";
+ return;
+ }
+
+ openTable(db, QString::null, table); // TODO put database name when supported
+}
+
+void DbTree::delTable()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+ if (table.isNull())
+ {
+ qWarning() << "Tried to drop table, while table wasn't selected in DbTree.";
+ return;
+ }
+
+ DbObjectDialogs dialogs(db);
+ dialogs.dropObject(table); // TODO add database prefix when supported
+}
+
+void DbTree::addIndex()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+
+ DbObjectDialogs dialogs(db);
+ dialogs.addIndex(table);
+}
+
+void DbTree::editIndex()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString index = item->getIndex();
+
+ DbObjectDialogs dialogs(db);
+ dialogs.editIndex(index);
+}
+
+void DbTree::delIndex()
+{
+ delSelectedObject();
+}
+
+void DbTree::addTrigger()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db)
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+ QString view = item->getView();
+
+ DbObjectDialogs dialogs(db);
+ dialogs.addTrigger(table, view);
+}
+
+void DbTree::editTrigger()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString trigger = item->getTrigger();
+
+ DbObjectDialogs dialogs(db);
+ dialogs.editTrigger(trigger);
+}
+
+void DbTree::delTrigger()
+{
+ delSelectedObject();
+}
+
+void DbTree::addView()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ newView(item);
+}
+
+void DbTree::editView()
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString view = item->getView();
+ if (view.isNull())
+ {
+ qWarning() << "Tried to edit view, while view wasn't selected in DbTree.";
+ return;
+ }
+
+ openView(db, QString(), view); // TODO handle named database when supported
+}
+
+void DbTree::delView()
+{
+ delSelectedObject();
+}
+
+void DbTree::exportTable()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+ if (table.isNull())
+ {
+ qWarning() << "Tried to export table, while table wasn't selected in DbTree.";
+ return;
+ }
+
+ if (!ExportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot export, because no export plugin is loaded."));
+ return;
+ }
+
+ ExportDialog dialog(this);
+ dialog.setTableMode(db, table);
+ dialog.exec();
+}
+
+void DbTree::importTable()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+ if (table.isNull())
+ {
+ qWarning() << "Tried to import into table, while table wasn't selected in DbTree.";
+ return;
+ }
+
+ if (!ImportManager::isAnyPluginAvailable())
+ {
+ notifyError(tr("Cannot import, because no import plugin is loaded."));
+ return;
+ }
+
+ ImportDialog dialog(this);
+ dialog.setDbAndTable(db, table);
+ dialog.exec();
+}
+
+void DbTree::populateTable()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+ if (table.isNull())
+ {
+ qWarning() << "Tried to populate table, while table wasn't selected in DbTree.";
+ return;
+ }
+
+ PopulateDialog dialog(this);
+ dialog.setDbAndTable(db, table);
+ dialog.exec();
+}
+
+void DbTree::addColumn()
+{
+ DbTreeItem* item = ui->treeView->currentItem();
+ if (!item)
+ return;
+
+ addColumn(item);
+}
+
+void DbTree::editColumn()
+{
+ DbTreeItem* item = ui->treeView->currentItem();
+ if (!item)
+ return;
+
+ editColumn(item);
+}
+
+void DbTree::delColumn()
+{
+ DbTreeItem* item = ui->treeView->currentItem();
+ if (!item)
+ return;
+
+ delColumn(item);
+}
+
+void DbTree::convertDb()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbConverterDialog dialog(this);
+ dialog.setDb(db);
+ dialog.exec();
+}
+
+void DbTree::vacuumDb()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ SqlQueryPtr res = db->exec("VACUUM;");
+ if (res->isError())
+ notifyError(tr("Error while executing VACUUM on the database %1: %2").arg(db->getName(), res->getErrorText()));
+ else
+ notifyInfo(tr("VACUUM execution finished successfully."));
+}
+
+void DbTree::integrityCheck()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ EditorWindow* win = MAINWINDOW->openSqlEditor();
+ if (!win->setCurrentDb(db))
+ {
+ qCritical() << "Created EditorWindow had not got requested database:" << db->getName();
+ win->close();
+ return;
+ }
+
+ win->getMdiWindow()->rename(tr("Integrity check (%1)").arg(db->getName()));
+ win->setContents("PRAGMA integrity_check;");
+ win->execute();
+}
+
+void DbTree::createSimilarTable()
+{
+ Db* db = getSelectedDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* item = ui->treeView->currentItem();
+ QString table = item->getTable();
+ if (table.isNull())
+ {
+ qWarning() << "Tried to clone table, while table wasn't selected in DbTree.";
+ return;
+ }
+
+ DbObjectDialogs dialog(db);
+ dialog.addTableSimilarTo(QString(), table);
+}
+
+void DbTree::addColumn(DbTreeItem* item)
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ DbTreeItem* tableItem = nullptr;
+
+ if (item->getType() == DbTreeItem::Type::TABLE)
+ tableItem = item;
+ else
+ tableItem = item->findParentItem(DbTreeItem::Type::TABLE);
+
+ if (!tableItem)
+ return;
+
+ TableWindow* tableWin = openTable(tableItem);
+ tableWin->addColumn();
+}
+
+void DbTree::editColumn(DbTreeItem* item)
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ if (item->getType() != DbTreeItem::Type::COLUMN)
+ return;
+
+ DbTreeItem* tableItem = item->findParentItem(DbTreeItem::Type::TABLE);
+ if (!tableItem)
+ return;
+
+ TableWindow* tableWin = openTable(tableItem);
+ tableWin->editColumn(item->text());
+}
+
+void DbTree::delColumn(DbTreeItem* item)
+{
+ Db* db = getSelectedOpenDb();
+ if (!db || !db->isValid())
+ return;
+
+ if (item->getType() != DbTreeItem::Type::COLUMN)
+ return;
+
+ DbTreeItem* tableItem = item->findParentItem(DbTreeItem::Type::TABLE);
+ if (!tableItem)
+ return;
+
+ TableWindow* tableWin = openTable(tableItem);
+ tableWin->delColumn(item->text());
+}
+
+void DbTree::currentChanged(const QModelIndex &current, const QModelIndex &previous)
+{
+ UNUSED(previous);
+ updateActionStates(treeModel->itemFromIndex(current));
+}
+
+void DbTree::deleteSelected()
+{
+ QModelIndexList idxList = ui->treeView->getSelectedIndexes();
+ QList<DbTreeItem*> items;
+ foreach (const QModelIndex& idx, idxList)
+ items << dynamic_cast<DbTreeItem*>(treeModel->itemFromIndex(idx));
+
+ deleteItems(items);
+}
+
+void DbTree::deleteItems(const QList<DbTreeItem*>& itemsToDelete)
+{
+ QList<DbTreeItem*> items = itemsToDelete;
+
+ filterUndeletableItems(items);
+ filterItemsWithParentInList(items);
+
+ // Warning user about items to be deleted
+ static const QString itemTmp = "<img src=\"%1\"/> %2";
+
+ QStringList toDelete;
+ QStringList databasesToRemove;
+ QString itemStr;
+ int groupItems = 0;
+ foreach (DbTreeItem* item, items)
+ {
+ itemStr = itemTmp.arg(item->getIcon()->toUrl()).arg(item->text().left(ITEM_TEXT_LIMIT));
+
+ if (item->getType() == DbTreeItem::Type::DB)
+ databasesToRemove << itemStr;
+ else
+ toDelete << itemStr;
+
+ if (item->getType() == DbTreeItem::Type::DIR)
+ groupItems++;
+ }
+
+ QStringList actions;
+ if (toDelete.size() > 0)
+ actions << tr("Following objects will be deleted: %1.").arg(toDelete.join(", "));
+
+ if (databasesToRemove.size() > 0)
+ actions << tr("Following databases will be removed from list: %1.").arg(databasesToRemove.join(", "));
+
+ if (groupItems > 0)
+ actions << tr("Remainig objects from deleted group will be moved in place where the group used to be.");
+
+ QString msg = tr("%1<br><br>Are you sure you want to continue?").arg(actions.join("<br><br>"));
+
+ QMessageBox::StandardButton result = QMessageBox::question(this, tr("Delete objects"), msg);
+ if (result != QMessageBox::Yes)
+ return;
+
+ // Deleting items
+ QSet<Db*> databasesToRefresh;
+ for (DbTreeItem* item : items)
+ {
+ databasesToRefresh << item->getDb();
+ deleteItem(item);
+ }
+
+ for (Db* dbToRefresh : databasesToRefresh)
+ DBTREE->refreshSchema(dbToRefresh);
+}
+
+void DbTree::refreshSchemas()
+{
+ foreach (Db* db, DBLIST->getDbList())
+ treeModel->refreshSchema(db);
+}
+
+void DbTree::interrupt()
+{
+ treeModel->interrupt();
+}
+
+void DbTree::refreshSchema()
+{
+ Db* db = getSelectedDb();
+ refreshSchema(db);
+}
+
+void DbTree::updateActionsForCurrent()
+{
+ updateActionStates(ui->treeView->currentItem());
+}
+
+void DbTree::dbConnected(Db* db)
+{
+ updateActionsForCurrent();
+ updateDbIcon(db);
+}
+
+void DbTree::dbDisconnected(Db* db)
+{
+ updateActionsForCurrent();
+ updateDbIcon(db);
+}
+
+void DbTree::updateDbIcon(Db* db)
+{
+ DbTreeItem* item = treeModel->findItem(DbTreeItem::Type::DB, db);
+ if (item)
+ item->updateDbIcon();
+}
+
+void DbTree::refreshFont()
+{
+ ui->treeView->doItemsLayout();
+}
+
+void DbTree::setupDefShortcuts()
+{
+ setShortcutContext({
+ CLEAR_FILTER, DEL_SELECTED, REFRESH_SCHEMA, REFRESH_SCHEMAS,
+ ADD_DB, SELECT_ALL, COPY, PASTE
+ }, Qt::WidgetWithChildrenShortcut);
+
+ BIND_SHORTCUTS(DbTree, Action);
+}
+
+int qHash(DbTree::Action action)
+{
+ return static_cast<int>(action);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h
new file mode 100644
index 0000000..b368c08
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h
@@ -0,0 +1,205 @@
+#ifndef DBTREE_H
+#define DBTREE_H
+
+#include "db/db.h"
+#include "common/extactioncontainer.h"
+#include "mainwindow.h"
+#include "dbtree/dbtreeitem.h"
+#include "guiSQLiteStudio_global.h"
+#include <QDockWidget>
+
+class WidgetCover;
+class QAction;
+class QMenu;
+class DbTreeModel;
+class QStandardItem;
+class QTimer;
+class TableWindow;
+class ViewWindow;
+class UserInputFilter;
+class DbTreeView;
+
+namespace Ui {
+ class DbTree;
+}
+
+CFG_KEY_LIST(DbTree, QObject::tr("Database list"),
+ CFG_KEY_ENTRY(DEL_SELECTED, Qt::Key_Delete, QObject::tr("Delete selected item"))
+ CFG_KEY_ENTRY(CLEAR_FILTER, Qt::Key_Escape, QObject::tr("Clear filter contents"))
+ CFG_KEY_ENTRY(REFRESH_SCHEMA, Qt::Key_F5, QObject::tr("Refresh schema"))
+ CFG_KEY_ENTRY(REFRESH_SCHEMAS, Qt::SHIFT + Qt::Key_F5, QObject::tr("Refresh all schemas"))
+ CFG_KEY_ENTRY(ADD_DB, Qt::CTRL + Qt::Key_O, QObject::tr("Add database"))
+ CFG_KEY_ENTRY(SELECT_ALL, Qt::CTRL + Qt::Key_A, QObject::tr("Select all items"))
+ CFG_KEY_ENTRY(COPY, Qt::CTRL + Qt::Key_C, QObject::tr("Copy selected item(s)"))
+ CFG_KEY_ENTRY(PASTE, Qt::CTRL + Qt::Key_V, QObject::tr("Paste from clipboard"))
+)
+
+class GUI_API_EXPORT DbTree : public QDockWidget, public ExtActionContainer
+{
+ Q_OBJECT
+ Q_ENUMS(Action)
+
+ public:
+ friend class DbTreeView;
+
+ enum Action
+ {
+ COPY,
+ PASTE,
+ SELECT_ALL,
+ DEL_SELECTED,
+ CREATE_GROUP,
+ DELETE_GROUP,
+ RENAME_GROUP,
+ ADD_DB,
+ EDIT_DB,
+ DELETE_DB,
+ CONNECT_TO_DB,
+ DISCONNECT_FROM_DB,
+ IMPORT_INTO_DB,
+ EXPORT_DB,
+ CONVERT_DB,
+ VACUUM_DB,
+ INTEGRITY_CHECK,
+ ADD_TABLE,
+ EDIT_TABLE,
+ DEL_TABLE,
+ EXPORT_TABLE,
+ IMPORT_TABLE,
+ POPULATE_TABLE,
+ ADD_INDEX,
+ EDIT_INDEX,
+ DEL_INDEX,
+ ADD_TRIGGER,
+ EDIT_TRIGGER,
+ DEL_TRIGGER,
+ ADD_VIEW,
+ EDIT_VIEW,
+ DEL_VIEW,
+ ADD_COLUMN,
+ EDIT_COLUMN,
+ DEL_COLUMN,
+ CLEAR_FILTER,
+ REFRESH_SCHEMAS,
+ REFRESH_SCHEMA,
+ CREATE_SIMILAR_TABLE,
+ _separator // Never use it directly, it's just for menu setup
+ };
+
+ enum ToolBar
+ {
+ };
+
+ explicit DbTree(QWidget *parent = 0);
+ ~DbTree();
+
+ static void staticInit();
+
+ void init();
+ void updateActionStates(const QStandardItem* item);
+ void setupActionsForMenu(DbTreeItem* currItem, QMenu* contextMenu);
+ QVariant saveSession();
+ void restoreSession(const QVariant& sessionValue);
+ DbTreeModel* getModel() const;
+ DbTreeView* getView() const;
+ void showWidgetCover();
+ void hideWidgetCover();
+ void setSelectedItem(DbTreeItem* item);
+ bool isMimeDataValidForItem(const QMimeData* mimeData, const DbTreeItem* item);
+ QToolBar* getToolBar(int toolbar) const;
+
+ static bool isItemDraggable(const DbTreeItem* item);
+
+ protected:
+ void createActions();
+ void setupDefShortcuts();
+
+ private:
+ void setActionEnabled(int action, bool enabled);
+ Db* getSelectedDb();
+ Db* getSelectedOpenDb();
+ TableWindow* openTable(DbTreeItem* item);
+ TableWindow* openTable(Db* db, const QString& database, const QString& table);
+ TableWindow* newTable(DbTreeItem* item);
+ ViewWindow* openView(DbTreeItem* item);
+ ViewWindow* openView(Db* db, const QString& database, const QString& view);
+ ViewWindow* newView(DbTreeItem* item);
+ void editIndex(DbTreeItem* item);
+ void editTrigger(DbTreeItem* item);
+ void delSelectedObject();
+ void filterUndeletableItems(QList<DbTreeItem*>& items);
+ void filterItemsWithParentInList(QList<DbTreeItem*>& items);
+ void deleteItem(DbTreeItem* item);
+ static bool areDbTreeItemsValidForItem(QList<DbTreeItem*> srcItems, const DbTreeItem* dstItem);
+ static bool areUrlsValidForItem(const QList<QUrl>& srcUrls, const DbTreeItem* dstItem);
+
+ static void initDndTypes();
+
+ Ui::DbTree *ui = nullptr;
+ DbTreeModel* treeModel = nullptr;
+ WidgetCover* widgetCover = nullptr;
+
+ static QHash<DbTreeItem::Type,QList<DbTreeItem::Type>> allowedTypesInside;
+ static QSet<DbTreeItem::Type> draggableTypes;
+ static const constexpr int ITEM_TEXT_LIMIT = 300;
+
+ public slots:
+ void refreshSchema(Db* db);
+ void refreshSchemas();
+ void interrupt();
+
+ private slots:
+ void copy();
+ void paste();
+ void selectAll();
+ void createGroup();
+ void deleteGroup();
+ void renameGroup();
+ void addDb();
+ void editDb();
+ void removeDb();
+ void connectToDb();
+ void disconnectFromDb();
+ void import();
+ void exportDb();
+ void addTable();
+ void editTable();
+ void delTable();
+ void addIndex();
+ void editIndex();
+ void delIndex();
+ void addTrigger();
+ void editTrigger();
+ void delTrigger();
+ void addView();
+ void editView();
+ void delView();
+ void exportTable();
+ void importTable();
+ void populateTable();
+ void addColumn();
+ void editColumn();
+ void delColumn();
+ void convertDb();
+ void vacuumDb();
+ void integrityCheck();
+ void createSimilarTable();
+ void addColumn(DbTreeItem* item);
+ void editColumn(DbTreeItem* item);
+ void delColumn(DbTreeItem* item);
+ void currentChanged(const QModelIndex & current, const QModelIndex & previous);
+ void deleteSelected();
+ void deleteItems(const QList<DbTreeItem*>& itemsToDelete);
+ void refreshSchema();
+ void updateActionsForCurrent();
+ void dbConnected(Db* db);
+ void dbDisconnected(Db* db);
+ void updateDbIcon(Db* db);
+ void refreshFont();
+};
+
+int qHash(DbTree::Action action);
+
+#define DBTREE MainWindow::getInstance()->getDbTree()
+
+#endif // DBTREE_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui
new file mode 100644
index 0000000..52b0c7b
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DbTree</class>
+ <widget class="QDockWidget" name="DbTree">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>200</width>
+ <height>618</height>
+ </rect>
+ </property>
+ <property name="floating">
+ <bool>false</bool>
+ </property>
+ <property name="features">
+ <set>QDockWidget::AllDockWidgetFeatures</set>
+ </property>
+ <property name="allowedAreas">
+ <set>Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea</set>
+ </property>
+ <property name="windowTitle">
+ <string>Databases</string>
+ </property>
+ <widget class="QWidget" name="dockWidgetContents">
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="0">
+ <widget class="DbTreeView" name="treeView">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Ignored" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="acceptDrops">
+ <bool>true</bool>
+ </property>
+ <property name="dragEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::InternalMove</enum>
+ </property>
+ <property name="defaultDropAction">
+ <enum>Qt::CopyAction</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QWidget" name="top" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="nameFilter">
+ <property name="placeholderText">
+ <string>Filter by name</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>DbTreeView</class>
+ <extends>QTreeView</extends>
+ <header>dbtree/dbtreeview.h</header>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>nameFilter</tabstop>
+ <tabstop>treeView</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp
new file mode 100644
index 0000000..ead5e3d
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp
@@ -0,0 +1,332 @@
+#include "dbtreeitem.h"
+#include "iconmanager.h"
+#include "dbtreemodel.h"
+#include "services/dbmanager.h"
+#include "dbtree.h"
+#include <QDebug>
+#include <db/invaliddb.h>
+
+DbTreeItem::DbTreeItem(DbTreeItem::Type type, const Icon& icon, const QString& nodeName, QObject* parent)
+ : DbTreeItem(type, nodeName, parent)
+{
+ setIcon(icon);
+}
+
+DbTreeItem::DbTreeItem(DbTreeItem::Type type, const QString& nodeName, QObject *parent)
+ : QObject(parent)
+{
+ setText(nodeName);
+ setType(type);
+ init();
+}
+
+DbTreeItem::DbTreeItem(const DbTreeItem& item)
+ : QObject(item.QObject::parent()), QStandardItem(item)
+{
+ init();
+}
+
+DbTreeItem::DbTreeItem()
+{
+ setType(Type::ITEM_PROTOTYPE);
+ init();
+}
+
+void DbTreeItem::initMeta()
+{
+ qRegisterMetaType<DbTreeItem*>("DbTreeItem*");
+ qRegisterMetaTypeStreamOperators<DbTreeItem*>("DbTreeItem*");
+}
+
+DbTreeItem::Type DbTreeItem::getType() const
+{
+ return static_cast<Type>(type());
+}
+
+void DbTreeItem::setType(Type type)
+{
+ setData(static_cast<int>(type), DataRole::TYPE);
+}
+
+int DbTreeItem::type() const
+{
+ return data(DataRole::TYPE).toInt();
+}
+
+DbTreeItem* DbTreeItem::findItem(DbTreeItem::Type type, const QString& name)
+{
+ return DbTreeModel::findItem(this, type, name);
+}
+
+QStandardItem* DbTreeItem::clone() const
+{
+ return new DbTreeItem(*this);
+}
+
+QList<QStandardItem *> DbTreeItem::childs() const
+{
+ QList<QStandardItem *> results;
+ for (int i = 0; i < rowCount(); i++)
+ results += child(i);
+
+ return results;
+}
+
+QStringList DbTreeItem::childNames() const
+{
+ QStringList results;
+ for (int i = 0; i < rowCount(); i++)
+ results += child(i)->text();
+
+ return results;
+}
+
+QString DbTreeItem::getTable() const
+{
+ const DbTreeItem* item = getParentItem(Type::TABLE);
+ if (!item)
+ return QString::null;
+
+ return item->text();
+}
+
+QString DbTreeItem::getColumn() const
+{
+ if (getType() != Type::COLUMN)
+ return QString::null;
+
+ return text();
+}
+
+QString DbTreeItem::getIndex() const
+{
+ const DbTreeItem* item = getParentItem(Type::INDEX);
+ if (!item)
+ return QString::null;
+
+ return item->text();
+}
+
+QString DbTreeItem::getTrigger() const
+{
+ const DbTreeItem* item = getParentItem(Type::TRIGGER);
+ if (!item)
+ return QString::null;
+
+ return item->text();
+}
+
+QString DbTreeItem::getView() const
+{
+ const DbTreeItem* item = getParentItem(Type::VIEW);
+ if (!item)
+ return QString::null;
+
+ return item->text();
+}
+
+QStandardItem *DbTreeItem::parentItem() const
+{
+ if (!QStandardItem::parent())
+ return model()->invisibleRootItem();
+
+ return QStandardItem::parent();
+}
+
+DbTreeItem *DbTreeItem::parentDbTreeItem() const
+{
+ QStandardItem* parentItem = QStandardItem::parent();
+ if (!parentItem)
+ return nullptr;
+
+ return dynamic_cast<DbTreeItem*>(parentItem);
+}
+
+QList<DbTreeItem *> DbTreeItem::getPathToRoot()
+{
+ QList<DbTreeItem *> path;
+ getPathToRoot(path);
+ return path;
+}
+
+QList<DbTreeItem*> DbTreeItem::getPathToParentItem(DbTreeItem::Type type)
+{
+ QList<DbTreeItem*> path;
+ getPathToParentItem(path, type);
+ return path;
+}
+
+QList<DbTreeItem*> DbTreeItem::getPathToParentItem(DbTreeItem::Type type, const QString& name)
+{
+ QList<DbTreeItem*> path;
+ getPathToParentItem(path, type, name);
+ return path;
+}
+
+DbTreeItem* DbTreeItem::findParentItem(DbTreeItem::Type type)
+{
+ DbTreeItem* parent = parentDbTreeItem();
+ if (!parent)
+ return nullptr;
+
+ if (parent->getType() == type)
+ return parent;
+
+ return parent->findParentItem(type);
+}
+
+DbTreeItem* DbTreeItem::findParentItem(DbTreeItem::Type type, const QString& name)
+{
+ DbTreeItem* parent = parentDbTreeItem();
+ if (!parent)
+ return nullptr;
+
+ if (parent->getType() == type && name == parent->text())
+ return parent;
+
+ return parent->findParentItem(type);
+}
+
+void DbTreeItem::getPathToRoot(QList<DbTreeItem *> &path)
+{
+ path << this;
+ if (parentDbTreeItem())
+ parentDbTreeItem()->getPathToRoot(path);
+}
+
+QString DbTreeItem::signature() const
+{
+ QString sig;
+ if (parentDbTreeItem())
+ sig += parentDbTreeItem()->signature() + "_";
+
+ sig += QString::number(type()) + "." + QString::fromLatin1(text().toUtf8().toBase64());
+ return sig;
+}
+
+void DbTreeItem::getPathToParentItem(QList<DbTreeItem*>& path, DbTreeItem::Type type)
+{
+ path << this;
+ if (getType() == type)
+ return;
+
+ if (parentDbTreeItem())
+ parentDbTreeItem()->getPathToParentItem(path, type);
+}
+
+void DbTreeItem::getPathToParentItem(QList<DbTreeItem*>& path, DbTreeItem::Type type, const QString& name)
+{
+ path << this;
+ if (getType() == type && name == text())
+ return;
+
+ if (parentDbTreeItem())
+ parentDbTreeItem()->getPathToParentItem(path, type, name);
+}
+
+const DbTreeItem* DbTreeItem::getParentItem(DbTreeItem::Type type) const
+{
+ if (getType() == type)
+ return this;
+
+ DbTreeItem* parent = parentDbTreeItem();
+ if (parent)
+ return parent->getParentItem(type);
+
+ return nullptr;
+}
+
+Db* DbTreeItem::getDb() const
+{
+ QString dbName = data(DataRole::DB).toString();
+ return DBLIST->getByName(dbName);
+}
+
+void DbTreeItem::setDb(Db* value)
+{
+ setDb(value->getName());
+}
+
+void DbTreeItem::setDb(const QString& dbName)
+{
+ setData(dbName, DataRole::DB);
+ updateDbIcon();
+}
+
+void DbTreeItem::updateDbIcon()
+{
+ if (getType() != DbTreeItem::Type::DB)
+ return;
+
+ Db* db = getDb();
+ if (db->isValid())
+ {
+ if (db->isOpen())
+ setIcon(ICONS.DATABASE_ONLINE);
+ else
+ setIcon(ICONS.DATABASE_OFFLINE);
+ }
+ else
+ setIcon(ICONS.DATABASE_INVALID);
+}
+
+const Icon* DbTreeItem::getIcon() const
+{
+ return data(DataRole::ICON_PTR).value<const Icon*>();
+}
+
+void DbTreeItem::setHidden(bool hidden)
+{
+ setData(hidden, DataRole::HIDDEN);
+ dynamic_cast<DbTreeModel*>(model())->itemChangedVisibility(this);
+}
+
+bool DbTreeItem::isHidden() const
+{
+ return data(DataRole::HIDDEN).toBool();
+}
+
+void DbTreeItem::setIcon(const Icon& icon)
+{
+ setData(QVariant::fromValue(&icon), DataRole::ICON_PTR);
+ if (!icon.isNull())
+ QStandardItem::setIcon(icon);
+}
+
+void DbTreeItem::init()
+{
+ Type type = getType();
+ if (type == Type::DIR)
+ setEditable(true);
+ else
+ setEditable(false);
+
+ setData(false, DataRole::HIDDEN);
+
+ Qt::ItemFlags f = flags();
+ if (DbTree::isItemDraggable(this))
+ f |= Qt::ItemIsDragEnabled;
+ else
+ f ^= Qt::ItemIsDragEnabled;
+
+ setFlags(f);
+}
+
+QDataStream &operator <<(QDataStream &out, const DbTreeItem *item)
+{
+ out << item->signature();
+ return out;
+}
+
+QDataStream &operator >>(QDataStream &in, DbTreeItem *&item)
+{
+ QString signature;
+ in >> signature;
+ item = DBTREE->getModel()->findItemBySignature(signature);
+ return in;
+}
+
+int qHash(DbTreeItem::Type type)
+{
+ return static_cast<int>(type);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h
new file mode 100644
index 0000000..ba230f2
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h
@@ -0,0 +1,112 @@
+#ifndef DBTREEITEM_H
+#define DBTREEITEM_H
+
+#include "db/db.h"
+#include "iconmanager.h"
+#include "guiSQLiteStudio_global.h"
+#include <QStandardItem>
+#include <QObject>
+
+class GUI_API_EXPORT DbTreeItem : public QObject, public QStandardItem
+{
+ Q_OBJECT
+
+ public:
+ enum class Type
+ {
+ DIR = 1000,
+ DB = 1001,
+ TABLES = 1002,
+ TABLE = 1003,
+ INDEXES = 1004,
+ INDEX = 1005,
+ TRIGGERS = 1006,
+ TRIGGER = 1007,
+ VIEWS = 1008,
+ VIEW = 1009,
+ COLUMNS = 1010,
+ COLUMN = 1011,
+ VIRTUAL_TABLE = 1012,
+ ITEM_PROTOTYPE = 9999
+ };
+
+ DbTreeItem(Type type, const Icon& icon, const QString& nodeName, QObject* parent = 0);
+ DbTreeItem(const DbTreeItem& item);
+ DbTreeItem();
+
+ static void initMeta();
+
+ int type() const;
+ DbTreeItem* findItem(Type type, const QString& name);
+ QStandardItem* clone() const;
+ QList<QStandardItem*> childs() const;
+ QStringList childNames() const;
+ QString getTable() const;
+ QString getColumn() const;
+ QString getIndex() const;
+ QString getTrigger() const;
+ QString getView() const;
+
+ /**
+ * @brief parentItem
+ * @return Parent item for this item. Might be the "invisible root item" if this is the top level item. It will never be null.
+ */
+ QStandardItem* parentItem() const;
+
+ /**
+ * @brief parentDbTreeItem
+ * @return Parent item that is always DbTreeItem. If there is no parent item (i.e. this is the top item), then null is returned.
+ */
+ DbTreeItem* parentDbTreeItem() const;
+ QList<DbTreeItem*> getPathToRoot();
+ QList<DbTreeItem*> getPathToParentItem(Type type);
+ QList<DbTreeItem*> getPathToParentItem(Type type, const QString& name);
+ DbTreeItem* findParentItem(Type type);
+ DbTreeItem* findParentItem(Type type, const QString& name);
+ QString signature() const;
+
+ Type getType() const;
+ void setType(Type type);
+ Db* getDb() const;
+ void setDb(Db* value);
+ void setDb(const QString& dbName);
+ void updateDbIcon();
+ const Icon* getIcon() const;
+ void setHidden(bool hidden);
+ bool isHidden() const;
+ void setIcon(const Icon& icon);
+
+ private:
+ struct DataRole // not 'enum class' because we need autocasting to int for this one
+ {
+ enum Enum
+ {
+ TYPE = 1001,
+ DB = 1002,
+ ICON_PTR = 1003,
+ HIDDEN = 1004
+ };
+ };
+
+ DbTreeItem(Type type, const QString& nodeName, QObject* parent = 0);
+
+ void init();
+ void getPathToRoot(QList<DbTreeItem*>& path);
+ void getPathToParentItem(QList<DbTreeItem*>& path, Type type);
+ void getPathToParentItem(QList<DbTreeItem*>& path, Type type, const QString& name);
+ const DbTreeItem* getParentItem(Type type) const;
+
+ signals:
+
+ public slots:
+
+};
+
+GUI_API_EXPORT QDataStream &operator<<(QDataStream &out, const DbTreeItem* item);
+GUI_API_EXPORT QDataStream &operator>>(QDataStream &in, DbTreeItem*& item);
+
+GUI_API_EXPORT int qHash(DbTreeItem::Type type);
+
+Q_DECLARE_METATYPE(DbTreeItem*)
+
+#endif // DBTREEITEM_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp
new file mode 100644
index 0000000..ef691d2
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp
@@ -0,0 +1,164 @@
+#include "dbtreeitemdelegate.h"
+#include "dbtreeitem.h"
+#include "dbtreemodel.h"
+#include "common/utils_sql.h"
+#include "uiconfig.h"
+#include "dbtree.h"
+#include "dbtreeview.h"
+#include <QPainter>
+#include <QDebug>
+
+DbTreeItemDelegate::DbTreeItemDelegate(QObject *parent) :
+ QStyledItemDelegate(parent)
+{
+}
+
+QSize DbTreeItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ QSize size = QStyledItemDelegate::sizeHint(option, index);
+
+ QFont f = CFG_UI.Fonts.DbTree.get();
+ QFontMetrics fm(f);
+ size.setHeight(qMax(18, fm.height()));
+ return size;
+}
+
+void DbTreeItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ QStyleOptionViewItem opt = option;
+ initStyleOption(&opt, index);
+
+ const DbTreeModel* model = dynamic_cast<const DbTreeModel*>(index.model());
+ DbTreeItem* item = dynamic_cast<DbTreeItem*>(model->itemFromIndex(index));
+
+ opt.font = CFG_UI.Fonts.DbTree.get();
+ opt.fontMetrics = QFontMetrics(opt.font);
+
+ QModelIndex currIndex = DBTREE->getView()->selectionModel()->currentIndex();
+ if (currIndex.isValid() && item->index() == currIndex)
+ opt.state |= QStyle::State_HasFocus;
+
+ QStyledItemDelegate::paint(painter, opt, index);
+
+ if (!CFG_UI.General.ShowDbTreeLabels.get())
+ return;
+
+ switch (item->getType())
+ {
+ case DbTreeItem::Type::DIR:
+ break;
+ case DbTreeItem::Type::DB:
+ paintDb(painter, opt, index, item);
+ break;
+ case DbTreeItem::Type::TABLES:
+ case DbTreeItem::Type::INDEXES:
+ case DbTreeItem::Type::TRIGGERS:
+ case DbTreeItem::Type::VIEWS:
+ case DbTreeItem::Type::COLUMNS:
+ paintChildCount(painter, opt, index, item);
+ break;
+ case DbTreeItem::Type::TABLE:
+ paintTableLabel(painter, opt, index, item);
+ break;
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ paintVirtualTableLabel(painter, opt, index, item);
+ break;
+ case DbTreeItem::Type::INDEX:
+ paintSystemIndexLabel(painter, opt, index, item);
+ break;
+ case DbTreeItem::Type::TRIGGER:
+ case DbTreeItem::Type::VIEW:
+ case DbTreeItem::Type::COLUMN:
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ break;
+ }
+}
+
+void DbTreeItemDelegate::paintDb(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem *item) const
+{
+ static const QString versionStringTemplate = QStringLiteral("(%1)");
+ QString versionString = versionStringTemplate.arg("?");
+ Db* db = item->getDb();
+ if (!db)
+ return;
+
+ if (db->isValid())
+ {
+ QString t = db->getTypeLabel();
+ versionString = versionStringTemplate.arg(t);
+ }
+ else
+ {
+ versionString = versionStringTemplate.arg(tr("error", "dbtree labels"));
+ }
+
+ paintLabel(painter, option, index, item, versionString);
+}
+
+void DbTreeItemDelegate::paintChildCount(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem *item) const
+{
+ int cnt = item->rowCount();
+ if (cnt > 0)
+ paintLabel(painter, option, index, item, QString("(%1)").arg(cnt));
+}
+
+void DbTreeItemDelegate::paintTableLabel(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, DbTreeItem* item) const
+{
+ if (isSystemTable(item->text()))
+ {
+ paintLabel(painter, option, index, item, tr("(system table)", "database tree label"));
+ return;
+ }
+
+ if (!CFG_UI.General.ShowRegularTableLabels.get())
+ return;
+
+ int columnsCount = item->child(0)->rowCount();
+ int indexesCount = item->child(1)->rowCount();
+ int triggersCount = item->child(2)->rowCount();
+ paintLabel(painter, option, index, item, QString("(%1, %2, %3)").arg(columnsCount).arg(indexesCount).arg(triggersCount));
+}
+
+void DbTreeItemDelegate::paintVirtualTableLabel(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, DbTreeItem* item) const
+{
+ if (!CFG_UI.General.ShowVirtualTableLabels.get())
+ return;
+
+ paintLabel(painter, option, index, item, tr("(virtual)", "virtual table label"));
+}
+
+void DbTreeItemDelegate::paintSystemIndexLabel(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, DbTreeItem* item) const
+{
+ Db* db = item->getDb();
+ if (!db || !db->isValid())
+ return;
+
+ if (!isSystemIndex(item->text(), db->getDialect()))
+ return;
+
+ paintLabel(painter, option, index, item, tr("(system index)", "database tree label"));
+}
+
+void DbTreeItemDelegate::paintLabel(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem *item, const QString &label) const
+{
+ QStyleOptionViewItem opt = option;
+ initStyleOption(&opt, index);
+
+ painter->save();
+
+ // Colors
+ painter->setPen(CFG_UI.Colors.DbTreeLabelsFg.get());
+
+ // Font
+ opt.font = CFG_UI.Fonts.DbTreeLabel.get();
+ opt.fontMetrics = QFontMetrics(opt.font);
+ painter->setFont(opt.font);
+
+ // Coords
+ int x = option.rect.x() + option.fontMetrics.width(item->text()) + 15 + option.decorationSize.width();
+ int y = opt.rect.y() + (opt.rect.height() - opt.fontMetrics.descent() - opt.fontMetrics.ascent()) / 2 + opt.fontMetrics.ascent();
+
+ // Paint
+ painter->drawText(QPoint(x, y), label);
+ painter->restore();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.h
new file mode 100644
index 0000000..43eeac2
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.h
@@ -0,0 +1,28 @@
+#ifndef DBTREEITEMDELEGATE_H
+#define DBTREEITEMDELEGATE_H
+
+#include "guiSQLiteStudio_global.h"
+#include <QStyledItemDelegate>
+
+class DbTreeItem;
+
+class GUI_API_EXPORT DbTreeItemDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+
+ public:
+ explicit DbTreeItemDelegate(QObject *parent = 0);
+
+ QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
+ void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
+
+ private:
+ void paintDb(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem* item) const;
+ void paintChildCount(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem* item) const;
+ void paintTableLabel(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem* item) const;
+ void paintVirtualTableLabel(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, DbTreeItem* item) const;
+ void paintSystemIndexLabel(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem* item) const;
+ void paintLabel(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, DbTreeItem* item, const QString& label) const;
+};
+
+#endif // DBTREEITEMDELEGATE_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp
new file mode 100644
index 0000000..b112713
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp
@@ -0,0 +1,74 @@
+#include "dbtreeitemfactory.h"
+#include "iconmanager.h"
+#include "common/unused.h"
+
+DbTreeItem *DbTreeItemFactory::createDir(const QString &name, QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::DIR, ICONS.DIRECTORY, name, parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createDb(const QString &name, QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::DB, ICONS.DATABASE_OFFLINE, name, parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createTable(const QString &name, QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::TABLE, ICONS.TABLE, name, parent);
+}
+
+DbTreeItem* DbTreeItemFactory::createVirtualTable(const QString& name, QObject* parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::VIRTUAL_TABLE, ICONS.VIRTUAL_TABLE, name, parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createIndex(const QString &name, QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::INDEX, ICONS.INDEX, name, parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createTrigger(const QString &name, QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::TRIGGER, ICONS.TRIGGER, name, parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createView(const QString &name, QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::VIEW, ICONS.VIEW, name, parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createColumn(const QString &name, QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::COLUMN, ICONS.COLUMN, name, parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createTables(QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::TABLES, ICONS.TABLES, QObject::tr("Tables"), parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createIndexes(QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::INDEXES, ICONS.INDEXES, QObject::tr("Indexes"), parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createTriggers(QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::TRIGGERS, ICONS.TRIGGERS, QObject::tr("Triggers"), parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createViews(QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::VIEWS, ICONS.VIEWS, QObject::tr("Views"), parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createColumns(QObject *parent)
+{
+ return new DbTreeItem(DbTreeItem::Type::COLUMNS, ICONS.COLUMNS, QObject::tr("Columns"), parent);
+}
+
+DbTreeItem *DbTreeItemFactory::createPrototype(QObject *parent)
+{
+ UNUSED(parent);
+ return new DbTreeItem();
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.h
new file mode 100644
index 0000000..acb3aeb
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.h
@@ -0,0 +1,26 @@
+#ifndef DBTREEITEMFACTORY_H
+#define DBTREEITEMFACTORY_H
+
+#include "guiSQLiteStudio_global.h"
+#include "dbtree/dbtreeitem.h"
+
+class GUI_API_EXPORT DbTreeItemFactory
+{
+ public:
+ static DbTreeItem* createDir(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createDb(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createTable(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createVirtualTable(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createIndex(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createTrigger(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createView(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createColumn(const QString& name, QObject *parent = nullptr);
+ static DbTreeItem* createTables(QObject *parent = nullptr);
+ static DbTreeItem* createIndexes(QObject *parent = nullptr);
+ static DbTreeItem* createTriggers(QObject *parent = nullptr);
+ static DbTreeItem* createViews(QObject *parent = nullptr);
+ static DbTreeItem* createColumns(QObject *parent = nullptr);
+ static DbTreeItem* createPrototype(QObject *parent = nullptr);
+};
+
+#endif // DBTREEITEMFACTORY_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp
new file mode 100644
index 0000000..8a71a10
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp
@@ -0,0 +1,1222 @@
+#include "dbtreemodel.h"
+#include "services/dbmanager.h"
+#include "dbtreeview.h"
+#include "iconmanager.h"
+#include "uiconfig.h"
+#include "schemaresolver.h"
+#include "dbtreeitemfactory.h"
+#include "common/unused.h"
+#include "services/pluginmanager.h"
+#include "plugins/dbplugin.h"
+#include "dbobjectorganizer.h"
+#include "dialogs/dbdialog.h"
+#include "dialogs/errorsconfirmdialog.h"
+#include "dialogs/versionconvertsummarydialog.h"
+#include "db/invaliddb.h"
+#include <QMimeData>
+#include <QDebug>
+#include <QFile>
+#include <QFileInfo>
+#include <QMessageBox>
+#include <QInputDialog>
+#include <QCheckBox>
+#include <QWidgetAction>
+#include <QClipboard>
+
+const QString DbTreeModel::toolTipTableTmp = "<table>%1</table>";
+const QString DbTreeModel::toolTipHdrRowTmp = "<tr><th><img src=\"%1\"/></th><th colspan=2>%2</th></tr>";
+const QString DbTreeModel::toolTipRowTmp = "<tr><td></td><td>%1</td><td align=\"right\">%2</td></tr>";
+const QString DbTreeModel::toolTipIconRowTmp = "<tr><td><img src=\"%1\"/></td><td>%2</td><td align=\"right\">%3</td></tr>";
+
+DbTreeModel::DbTreeModel()
+{
+ setItemPrototype(DbTreeItemFactory::createPrototype());
+ connectDbManagerSignals();
+
+ connect(CFG, SIGNAL(massSaveBegins()), this, SLOT(massSaveBegins()));
+ connect(CFG, SIGNAL(massSaveCommited()), this, SLOT(massSaveCommited()));
+ connect(CFG_UI.General.ShowSystemObjects, SIGNAL(changed(QVariant)), this, SLOT(markSchemaReloadingRequired()));
+
+ dbOrganizer = new DbObjectOrganizer(confirmReferencedTables, resolveNameConflict, confirmConversion, confirmConversionErrors);
+ dbOrganizer->setAutoDelete(false);
+ connect(dbOrganizer, SIGNAL(finishedDbObjectsCopy(bool,Db*,Db*)), this, SLOT(dbObjectsCopyFinished(bool,Db*,Db*)));
+ connect(dbOrganizer, SIGNAL(finishedDbObjectsMove(bool,Db*,Db*)), this, SLOT(dbObjectsMoveFinished(bool,Db*,Db*)));
+}
+
+DbTreeModel::~DbTreeModel()
+{
+}
+
+void DbTreeModel::connectDbManagerSignals()
+{
+ connect(DBLIST, SIGNAL(dbAdded(Db*)), this, SLOT(dbAdded(Db*)));
+ connect(DBLIST, SIGNAL(dbUpdated(QString,Db*)), this, SLOT(dbUpdated(QString,Db*)));
+ connect(DBLIST, SIGNAL(dbRemoved(Db*)), this, SLOT(dbRemoved(Db*)));
+ connect(DBLIST, SIGNAL(dbConnected(Db*)), this, SLOT(dbConnected(Db*)));
+ connect(DBLIST, SIGNAL(dbDisconnected(Db*)), this, SLOT(dbDisconnected(Db*)));
+ connect(DBLIST, SIGNAL(dbLoaded(Db*)), this, SLOT(dbLoaded(Db*)));
+ connect(DBLIST, SIGNAL(dbUnloaded(Db*)), this, SLOT(dbUnloaded(Db*)));
+}
+
+void DbTreeModel::move(QStandardItem *itemToMove, QStandardItem *newParentItem, int newRow)
+{
+ QStandardItem* currParent = dynamic_cast<DbTreeItem*>(itemToMove)->parentItem();
+ if (!newParentItem)
+ newParentItem = root();
+
+ if (newParentItem == currParent)
+ {
+ move(itemToMove, newRow);
+ return;
+ }
+
+ int oldRow = itemToMove->index().row();
+ currParent->takeRow(oldRow);
+
+ if (newRow > currParent->rowCount() || newRow < 0)
+ newParentItem->appendRow(itemToMove);
+ else
+ newParentItem->insertRow(newRow, itemToMove);
+}
+
+void DbTreeModel::move(QStandardItem *itemToMove, int newRow)
+{
+ QStandardItem* currParent = dynamic_cast<DbTreeItem*>(itemToMove)->parentItem();
+ int oldRow = itemToMove->index().row();
+ currParent->takeRow(oldRow);
+ if (newRow > currParent->rowCount() || newRow < 0)
+ currParent->appendRow(itemToMove);
+ else if (oldRow < newRow)
+ currParent->insertRow(newRow - 1, itemToMove);
+ else
+ currParent->insertRow(newRow, itemToMove);
+}
+
+void DbTreeModel::deleteGroup(QStandardItem *groupItem)
+{
+ QStandardItem* parentItem = dynamic_cast<DbTreeItem*>(groupItem)->parentItem();
+ if (!parentItem)
+ parentItem = root();
+
+ foreach (QStandardItem* child, dynamic_cast<DbTreeItem*>(groupItem)->childs())
+ move(child, parentItem);
+
+ parentItem->removeRow(groupItem->row());
+}
+
+DbTreeItem* DbTreeModel::createGroup(const QString& name, QStandardItem* parent)
+{
+ if (!parent)
+ parent = root();
+
+ DbTreeItem* item = DbTreeItemFactory::createDir(name, this);
+ parent->appendRow(item);
+ return item;
+}
+
+QStringList DbTreeModel::getGroupFor(QStandardItem *item)
+{
+ QStringList group;
+ while ((item = item->parent()) != nullptr)
+ {
+ if (dynamic_cast<DbTreeItem*>(item)->getType() == DbTreeItem::Type::DIR)
+ group.prepend(item->text());
+ }
+ return group;
+}
+
+void DbTreeModel::applyFilter(const QString &filter)
+{
+ applyFilter(root(), filter);
+ currentFilter = filter;
+}
+
+bool DbTreeModel::applyFilter(QStandardItem *parentItem, const QString &filter)
+{
+ bool empty = filter.isEmpty();
+ bool visibilityForParent = false;
+ DbTreeItem* item = nullptr;
+ QModelIndex index;
+ bool subFilterResult;
+ bool matched;
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ {
+ item = dynamic_cast<DbTreeItem*>(parentItem->child(i));
+ index = item->index();
+ subFilterResult = applyFilter(item, filter);
+ matched = empty || subFilterResult || item->text().contains(filter, Qt::CaseInsensitive);
+ treeView->setRowHidden(index.row(), index.parent(), !matched);
+
+ if (matched)
+ visibilityForParent = true;
+ }
+ return visibilityForParent;
+}
+
+void DbTreeModel::storeGroups()
+{
+ QList<Config::DbGroupPtr> groups = childsToConfig(invisibleRootItem());
+ CFG->storeGroups(groups);
+}
+
+void DbTreeModel::readGroups(QList<Db*> dbList)
+{
+ QList<Config::DbGroupPtr> groups = CFG->getGroups();
+ foreach (const Config::DbGroupPtr& group, groups)
+ restoreGroup(group, &dbList);
+
+ // Add rest of databases, not mentioned in groups
+ Config::DbGroupPtr group;
+ foreach (Db* db, dbList)
+ {
+ group = Config::DbGroupPtr::create();
+ group->referencedDbName = db->getName();
+ restoreGroup(group);
+ }
+}
+
+QList<Config::DbGroupPtr> DbTreeModel::childsToConfig(QStandardItem *item)
+{
+ QList<Config::DbGroupPtr> groups;
+ Config::DbGroupPtr group;
+ DbTreeItem* dbTreeItem = nullptr;
+ for (int i = 0; i < item->rowCount(); i++)
+ {
+ dbTreeItem = dynamic_cast<DbTreeItem*>(item->child(i));
+ switch (dbTreeItem->getType())
+ {
+ case DbTreeItem::Type::DIR:
+ {
+ group = Config::DbGroupPtr::create();
+ group->name = dbTreeItem->text();
+ group->order = i;
+ group->open = treeView->isExpanded(dbTreeItem->index());
+ group->childs = childsToConfig(dbTreeItem);
+ groups += group;
+ break;
+ }
+ case DbTreeItem::Type::DB:
+ {
+ group = Config::DbGroupPtr::create();
+ group->referencedDbName = dbTreeItem->text();
+ group->order = i;
+ group->open = dbTreeItem->getDb()->isOpen();
+ groups += group;
+ break;
+ }
+ default:
+ // no-op
+ break;
+ }
+ }
+ return groups;
+}
+
+void DbTreeModel::restoreGroup(const Config::DbGroupPtr& group, QList<Db*>* dbList, QStandardItem* parent)
+{
+ Db* db = nullptr;
+ DbTreeItem* item = nullptr;
+ if (group->referencedDbName.isNull())
+ {
+ item = DbTreeItemFactory::createDir(group->name, this);
+ }
+ else
+ {
+ // If db is managed by manager, it means it was successfully loaded.
+ // Otherwise there was a problem with the file, or with plugin for that database
+ // and we still want to have dbtree item for that database, we will just hide it.
+ // Later, when plugin is loaded, item might become visible.
+ item = DbTreeItemFactory::createDb(group->referencedDbName, this);
+ item->setDb(group->referencedDbName);
+
+ db = DBLIST->getByName(group->referencedDbName);
+ if (db && dbList)
+ dbList->removeOne(db);
+ }
+
+ if (!parent)
+ parent = invisibleRootItem();
+
+ parent->appendRow(item);
+
+ if (item->getType() == DbTreeItem::Type::DIR)
+ {
+ foreach (const Config::DbGroupPtr& childGroup, group->childs)
+ restoreGroup(childGroup, dbList, item);
+ }
+
+ if (group->open)
+ {
+ if (db)
+ {
+ if (db->open())
+ treeView->expand(item->index());
+ }
+ else
+ {
+ treeView->expand(item->index());
+ }
+ }
+}
+
+void DbTreeModel::expanded(const QModelIndex &index)
+{
+ QStandardItem* item = itemFromIndex(index);
+ if (!item->hasChildren())
+ {
+ treeView->collapse(index);
+ return;
+ }
+
+ if (dynamic_cast<DbTreeItem*>(item)->getType() == DbTreeItem::Type::DIR)
+ itemFromIndex(index)->setIcon(ICONS.DIRECTORY_OPEN);
+}
+
+void DbTreeModel::collapsed(const QModelIndex &index)
+{
+ QStandardItem* item = itemFromIndex(index);
+ if (dynamic_cast<DbTreeItem*>(item)->getType() == DbTreeItem::Type::DIR)
+ item->setIcon(ICONS.DIRECTORY_OPEN);
+}
+
+void DbTreeModel::dbAdded(Db* db)
+{
+ DbTreeItem* item = DbTreeItemFactory::createDb(db->getName(), this);
+ item->setDb(db);
+ root()->appendRow(item);
+}
+
+void DbTreeModel::dbUpdated(const QString& oldName, Db* db)
+{
+ DbTreeItem* item = dynamic_cast<DbTreeItem*>(findItem(DbTreeItem::Type::DB, oldName));
+ if (!item)
+ {
+ qWarning() << "Updated database in db model that couldn't be found in the model:" << oldName;
+ return;
+ }
+
+ item->setText(db->getName());
+ item->setDb(db->getName());
+}
+
+void DbTreeModel::dbRemoved(Db* db)
+{
+ dbRemoved(db->getName());
+}
+
+void DbTreeModel::dbRemoved(const QString& name)
+{
+ QStandardItem* item = findItem(DbTreeItem::Type::DB, name);
+ if (!item)
+ {
+ qWarning() << "Removed database from db model that couldn't be found in the model:" << name;
+ return;
+ }
+ dbRemoved(item);
+}
+
+void DbTreeModel::dbRemoved(QStandardItem* item)
+{
+ QStandardItem* parent = item->parent();
+ if (!parent)
+ parent = root();
+
+ parent->removeRow(item->index().row());
+ if (!parent->hasChildren())
+ treeView->collapse(parent->index());
+}
+
+void DbTreeModel::interrupt()
+{
+ dbOrganizer->interrupt();
+}
+
+void DbTreeModel::refreshSchema(Db* db)
+{
+ QStandardItem* item = findItem(DbTreeItem::Type::DB, db);
+ if (!item)
+ {
+ qWarning() << "Refreshing schema of db that couldn't be found in the model:" << db->getName();
+ return;
+ }
+ refreshSchema(db, item);
+ applyFilter(item, currentFilter);
+}
+
+QList<DbTreeItem*> DbTreeModel::getAllItemsAsFlatList() const
+{
+ return getChildsAsFlatList(root());
+}
+
+QList<DbTreeItem*> DbTreeModel::getChildsAsFlatList(QStandardItem* item) const
+{
+ QList<DbTreeItem*> items;
+ QStandardItem* child = nullptr;
+ for (int i = 0; i < item->rowCount(); i++)
+ {
+ child = item->child(i);
+ items << dynamic_cast<DbTreeItem*>(child);
+ items += getChildsAsFlatList(child);
+ }
+ return items;
+}
+
+QVariant DbTreeModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QStandardItemModel::data(index, role);;
+
+ DbTreeItem* item = dynamic_cast<DbTreeItem*>(itemFromIndex(index));
+ switch (role)
+ {
+ case Qt::ToolTipRole:
+ {
+ return getToolTip(item);
+ }
+ }
+ return QStandardItemModel::data(index, role);
+}
+
+QString DbTreeModel::getToolTip(DbTreeItem* item) const
+{
+ if (!item)
+ return QString::null;
+
+ switch (item->getType())
+ {
+ case DbTreeItem::Type::DB:
+ return getDbToolTip(item);
+ case DbTreeItem::Type::TABLE:
+ return getTableToolTip(item);
+ default:
+ break;
+ }
+ return QString::null;
+}
+
+QString DbTreeModel::getDbToolTip(DbTreeItem* item) const
+{
+ QStringList rows;
+
+ Db* db = item->getDb();
+ QFile dbFile(db->getPath());
+ QString iconPath = db->isValid() ? ICONS.DATABASE.toImgSrc() : ICONS.DATABASE_INVALID.toImgSrc();
+
+ rows << toolTipHdrRowTmp.arg(iconPath).arg(tr("Database: %1", "dbtree tooltip").arg(db->getName()));
+ rows << toolTipRowTmp.arg("URI:").arg(db->getPath());
+
+ if (db->isValid())
+ {
+ rows << toolTipRowTmp.arg(tr("Version:", "dbtree tooltip")).arg(QString("SQLite %1").arg(db->getVersion()));
+ rows << toolTipRowTmp.arg(tr("File size:", "dbtree tooltip")).arg(formatFileSize(dbFile.size()));
+ rows << toolTipRowTmp.arg(tr("Encoding:", "dbtree tooltip")).arg(db->getEncoding());
+ }
+ else
+ {
+ InvalidDb* idb = dynamic_cast<InvalidDb*>(db);
+ rows << toolTipRowTmp.arg(tr("Error details:", "dbtree tooltip")).arg(idb->getError());
+ }
+
+ return toolTipTableTmp.arg(rows.join(""));
+}
+
+QString DbTreeModel::getTableToolTip(DbTreeItem* item) const
+{
+ QStringList rows;
+
+ rows << toolTipHdrRowTmp.arg(ICONS.TABLE.getPath()).arg(tr("Table : %1", "dbtree tooltip").arg(item->text()));
+
+ QStandardItem* columnsItem = item->child(0);
+ QStandardItem* indexesItem = item->child(1);
+ QStandardItem* triggersItem = item->child(2);
+
+ int columnCnt = columnsItem->rowCount();
+ int indexesCount = indexesItem->rowCount();
+ int triggersCount = triggersItem->rowCount();
+
+ QStringList columns;
+ for (int i = 0; i < columnCnt; i++)
+ columns << columnsItem->child(i)->text();
+
+ QStringList indexes;
+ for (int i = 0; i < indexesCount; i++)
+ indexes << indexesItem->child(i)->text();
+
+ QStringList triggers;
+ for (int i = 0; i < triggersCount; i++)
+ triggers << triggersItem->child(i)->text();
+
+ rows << toolTipIconRowTmp.arg(ICONS.COLUMN.getPath())
+ .arg(tr("Columns (%1):", "dbtree tooltip").arg(columnCnt))
+ .arg(columns.join(", "));
+ rows << toolTipIconRowTmp.arg(ICONS.INDEX.getPath())
+ .arg(tr("Indexes (%1):", "dbtree tooltip").arg(indexesCount))
+ .arg(indexes.join(", "));
+ rows << toolTipIconRowTmp.arg(ICONS.TRIGGER.getPath())
+ .arg(tr("Triggers (%1):", "dbtree tooltip").arg(triggersCount))
+ .arg(triggers.join(", "));
+
+ return toolTipTableTmp.arg(rows.join(""));
+}
+
+void DbTreeModel::refreshSchema(Db* db, QStandardItem *item)
+{
+ if (!db->isOpen())
+ return;
+
+ // Remember expanded state of this branch
+ QHash<QString, bool> expandedState;
+ collectExpandedState(expandedState, item);
+
+ // Delete child nodes
+ while (item->rowCount() > 0)
+ item->removeRow(0);
+
+ // Now prepare to create new branch
+ SchemaResolver resolver(db);
+ resolver.setIgnoreSystemObjects(!CFG_UI.General.ShowSystemObjects.get());
+
+ // Collect all db objects and build the db branch
+ bool sort = CFG_UI.General.SortObjects.get();
+ QStringList tables = resolver.getTables();
+ QStringList virtualTables;
+ for (const QString& table : tables)
+ {
+ if (resolver.isVirtualTable(table))
+ virtualTables << table;
+ }
+
+ QList<QStandardItem*> tableItems = refreshSchemaTables(tables, virtualTables, sort);
+ StrHash<QList<QStandardItem*>> allTableColumns = refreshSchemaTableColumns(resolver.getAllTableColumns());
+ StrHash<QList<QStandardItem*>> indexItems = refreshSchemaIndexes(resolver.getGroupedIndexes(), sort);
+ StrHash<QList<QStandardItem*>> triggerItems = refreshSchemaTriggers(resolver.getGroupedTriggers(), sort);
+ QList<QStandardItem*> viewItems = refreshSchemaViews(resolver.getViews(), sort);
+ refreshSchemaBuild(item, tableItems, indexItems, triggerItems, viewItems, allTableColumns);
+ populateChildItemsWithDb(item, db);
+ restoreExpandedState(expandedState, item);
+}
+
+void DbTreeModel::collectExpandedState(QHash<QString, bool> &state, QStandardItem *parentItem)
+{
+ if (!parentItem)
+ parentItem = root();
+
+ DbTreeItem* dbTreeItem = dynamic_cast<DbTreeItem*>(parentItem);
+ if (dbTreeItem)
+ state[dbTreeItem->signature()] = treeView->isExpanded(dbTreeItem->index());
+
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ collectExpandedState(state, parentItem->child(i));
+}
+
+QList<QStandardItem *> DbTreeModel::refreshSchemaTables(const QStringList &tables, const QStringList& virtualTables, bool sort)
+{
+ QStringList sortedTables = tables;
+ if (sort)
+ qSort(sortedTables);
+
+ QList<QStandardItem *> items;
+ foreach (const QString& table, sortedTables)
+ {
+ if (virtualTables.contains(table))
+ items += DbTreeItemFactory::createVirtualTable(table, this);
+ else
+ items += DbTreeItemFactory::createTable(table, this);
+ }
+
+ return items;
+}
+
+StrHash<QList<QStandardItem*>> DbTreeModel::refreshSchemaTableColumns(const StrHash<QStringList> &columns)
+{
+ QStringList sortedColumns;
+ bool sort = CFG_UI.General.SortColumns.get();
+ StrHash<QList<QStandardItem*>> items;
+ for (const QString& key : columns.keys())
+ {
+ sortedColumns = columns[key];
+ if (sort)
+ qSort(sortedColumns);
+
+ for (const QString& column : sortedColumns)
+ items[key] += DbTreeItemFactory::createColumn(column, this);
+ }
+ return items;
+}
+
+StrHash<QList<QStandardItem *> > DbTreeModel::refreshSchemaIndexes(const StrHash<QStringList> &indexes, bool sort)
+{
+ StrHash<QList<QStandardItem *> > items;
+ QStringList sortedIndexes;
+ for (const QString& key : indexes.keys())
+ {
+ sortedIndexes = indexes[key];
+ if (sort)
+ qSort(sortedIndexes);
+
+ for (const QString& index : sortedIndexes)
+ items[key] += DbTreeItemFactory::createIndex(index, this);
+ }
+ return items;
+}
+
+StrHash<QList<QStandardItem*>> DbTreeModel::refreshSchemaTriggers(const StrHash<QStringList> &triggers, bool sort)
+{
+ StrHash<QList<QStandardItem*>> items;
+ QStringList sortedTriggers;
+ for (const QString& key : triggers.keys())
+ {
+ sortedTriggers = triggers[key];
+ if (sort)
+ qSort(sortedTriggers);
+
+ for (const QString& trigger : sortedTriggers)
+ items[key] += DbTreeItemFactory::createTrigger(trigger, this);
+ }
+ return items;
+}
+
+QList<QStandardItem *> DbTreeModel::refreshSchemaViews(const QStringList &views, bool sort)
+{
+ QStringList sortedViews = views;
+ if (sort)
+ qSort(sortedViews);
+
+ QList<QStandardItem *> items;
+ foreach (const QString& view, views)
+ items += DbTreeItemFactory::createView(view, this);
+
+ return items;
+}
+
+void DbTreeModel::populateChildItemsWithDb(QStandardItem *parentItem, Db* db)
+{
+ QStandardItem* childItem = nullptr;
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ {
+ childItem = parentItem->child(i);
+ dynamic_cast<DbTreeItem*>(childItem)->setDb(db);
+ populateChildItemsWithDb(childItem, db);
+ }
+}
+
+void DbTreeModel::refreshSchemaBuild(QStandardItem *dbItem,
+ QList<QStandardItem*> tables,
+ StrHash<QList<QStandardItem*> > indexes,
+ StrHash<QList<QStandardItem*> > triggers,
+ QList<QStandardItem*> views,
+ StrHash<QList<QStandardItem*> > allTableColumns)
+{
+ DbTreeItem* tablesItem = DbTreeItemFactory::createTables(this);
+ DbTreeItem* viewsItem = DbTreeItemFactory::createViews(this);
+
+ dbItem->appendRow(tablesItem);
+ dbItem->appendRow(viewsItem);
+
+ DbTreeItem* columnsItem = nullptr;
+ DbTreeItem* indexesItem = nullptr;
+ DbTreeItem* triggersItem = nullptr;
+ foreach (QStandardItem* tableItem, tables)
+ {
+ tablesItem->appendRow(tableItem);
+
+ columnsItem = DbTreeItemFactory::createColumns(this);
+ indexesItem = DbTreeItemFactory::createIndexes(this);
+ triggersItem = DbTreeItemFactory::createTriggers(this);
+
+ tableItem->appendRow(columnsItem);
+ tableItem->appendRow(indexesItem);
+ tableItem->appendRow(triggersItem);
+
+ foreach (QStandardItem* columnItem, allTableColumns[tableItem->text()])
+ columnsItem->appendRow(columnItem);
+
+ foreach (QStandardItem* indexItem, indexes[tableItem->text()])
+ indexesItem->appendRow(indexItem);
+
+ foreach (QStandardItem* triggerItem, triggers[tableItem->text()])
+ triggersItem->appendRow(triggerItem);
+ }
+ foreach (QStandardItem* viewItem, views)
+ {
+ viewsItem->appendRow(viewItem);
+
+ triggersItem = DbTreeItemFactory::createTriggers(this);
+ viewItem->appendRow(triggersItem);
+ foreach (QStandardItem* triggerItem, triggers[viewItem->text()])
+ triggersItem->appendRow(triggerItem);
+ }
+}
+
+void DbTreeModel::restoreExpandedState(const QHash<QString, bool>& expandedState, QStandardItem* parentItem)
+{
+ DbTreeItem* parentDbTreeItem = dynamic_cast<DbTreeItem*>(parentItem);
+ QString sig = parentDbTreeItem->signature();
+ if (expandedState.contains(sig) && expandedState[sig])
+ treeView->expand(parentItem->index());
+
+ foreach (QStandardItem* child, parentDbTreeItem->childs())
+ restoreExpandedState(expandedState, child);
+}
+
+void DbTreeModel::dbConnected(Db* db)
+{
+ QStandardItem* item = findItem(DbTreeItem::Type::DB, db);
+ if (!item)
+ {
+ qWarning() << "Connected to db that couldn't be found in the model:" << db->getName();
+ return;
+ }
+ refreshSchema(db, item);
+ treeView->expand(item->index());
+ if (CFG_UI.General.ExpandTables.get())
+ treeView->expand(item->index().child(0, 0)); // also expand tables
+
+ if (CFG_UI.General.ExpandViews.get())
+ treeView->expand(item->index().child(1, 0)); // also expand views
+}
+
+void DbTreeModel::dbDisconnected(Db* db)
+{
+ QStandardItem* item = findItem(DbTreeItem::Type::DB, db);
+ if (!item)
+ {
+ qWarning() << "Disconnected from db that couldn't be found in the model:" << db->getName();
+ return;
+ }
+
+ while (item->rowCount() > 0)
+ item->removeRow(0);
+
+ treeView->collapse(item->index());
+}
+
+void DbTreeModel::dbUnloaded(Db* db)
+{
+ DbTreeItem* item = findItem(DbTreeItem::Type::DB, db->getName());
+ if (!item)
+ {
+ qCritical() << "No DB item found to update icon:" << db->getName();
+ return;
+ }
+ item->updateDbIcon();
+}
+
+void DbTreeModel::dbLoaded(Db* db)
+{
+ if (ignoreDbLoadedSignal)
+ return;
+
+ DbTreeItem* item = findItem(DbTreeItem::Type::DB, db->getName());
+ if (!item)
+ {
+ qCritical() << "No DB item found to update icon:" << db->getName();
+ return;
+ }
+ item->updateDbIcon();
+}
+
+void DbTreeModel::massSaveBegins()
+{
+ requireSchemaReloading = false;
+}
+
+void DbTreeModel::massSaveCommited()
+{
+ if (requireSchemaReloading)
+ {
+ for (Db* db : DBLIST->getDbList())
+ {
+ if (db->isOpen())
+ refreshSchema(db);
+ }
+ }
+}
+
+void DbTreeModel::markSchemaReloadingRequired()
+{
+ requireSchemaReloading = true;
+}
+
+DbTreeItem* DbTreeModel::findItem(DbTreeItem::Type type, const QString &name)
+{
+ return findItem(root(), type, name);
+}
+
+DbTreeItem *DbTreeModel::findItem(QStandardItem* parentItem, DbTreeItem::Type type, const QString& name)
+{
+ DbTreeItem* item = nullptr;
+ DbTreeItem* subItem = nullptr;
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ {
+ item = dynamic_cast<DbTreeItem*>(parentItem->child(i));
+
+ // Search recursively
+ if (item->hasChildren())
+ {
+ subItem = findItem(item, type, name);
+ if (subItem)
+ return subItem;
+ }
+
+ if (item->getType() != type)
+ continue;
+
+ if (item->text() != name)
+ continue;
+
+ return item;
+ }
+
+ return nullptr;
+}
+
+DbTreeItem *DbTreeModel::findItem(DbTreeItem::Type type, Db* db)
+{
+ return findItem(root(), type, db);
+}
+
+DbTreeItem *DbTreeModel::findItemBySignature(const QString &signature)
+{
+ QStringList parts = signature.split("_");
+ QStringList pair;
+ DbTreeItem* currItem = nullptr;
+ DbTreeItem::Type type;
+ QString name;
+ for (const QString& part : parts)
+ {
+ pair = part.split(".");
+ type = static_cast<DbTreeItem::Type>(pair.first().toInt());
+ name = QString::fromUtf8(QByteArray::fromBase64(pair.last().toLatin1()));
+ currItem = findItem((currItem ? currItem : root()), type, name);
+ if (!currItem)
+ return nullptr; // not found the target item
+ }
+ return currItem;
+}
+
+QList<DbTreeItem*> DbTreeModel::findItems(DbTreeItem::Type type)
+{
+ return findItems(root(), type);
+}
+
+DbTreeItem *DbTreeModel::findItem(QStandardItem* parentItem, DbTreeItem::Type type, Db* db)
+{
+ DbTreeItem* item = nullptr;
+ DbTreeItem* subItem = nullptr;
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ {
+ item = dynamic_cast<DbTreeItem*>(parentItem->child(i));
+
+ // Search recursively
+ if (item->hasChildren())
+ {
+ subItem = findItem(item, type, db);
+ if (subItem)
+ return subItem;
+ }
+
+ if (item->getType() != type)
+ continue;
+
+ if (item->text() != db->getName())
+ continue;
+
+ return item;
+ }
+
+ return nullptr;
+}
+
+QList<DbTreeItem*> DbTreeModel::findItems(QStandardItem* parentItem, DbTreeItem::Type type)
+{
+ QList<DbTreeItem*> items;
+ DbTreeItem* item = nullptr;
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ {
+ item = dynamic_cast<DbTreeItem*>(parentItem->child(i));
+
+ // Search recursively
+ if (item->getType() == DbTreeItem::Type::DIR)
+ items += findItems(item, type);
+
+ if (item->getType() != type)
+ continue;
+
+ items += item;
+ }
+
+ return items;
+}
+
+QStandardItem* DbTreeModel::root() const
+{
+ return invisibleRootItem();
+}
+
+void DbTreeModel::loadDbList()
+{
+ clear();
+ readGroups(DBLIST->getDbList());
+}
+
+void DbTreeModel::itemChangedVisibility(DbTreeItem* item)
+{
+ emit updateItemHidden(item);
+}
+
+void DbTreeModel::setTreeView(DbTreeView *value)
+{
+ treeView = value;
+ connect(treeView, &QTreeView::expanded, this, &DbTreeModel::expanded);
+ connect(treeView, &QTreeView::collapsed, this, &DbTreeModel::collapsed);
+ connect(this, SIGNAL(updateItemHidden(DbTreeItem*)), treeView, SLOT(updateItemHidden(DbTreeItem*)));
+}
+
+QStringList DbTreeModel::mimeTypes() const
+{
+ QStringList types = QStandardItemModel::mimeTypes();
+ types << MIMETYPE;
+ return types;
+}
+
+QMimeData *DbTreeModel::mimeData(const QModelIndexList &indexes) const
+{
+ QMimeData *data = QStandardItemModel::mimeData(indexes);
+ if (!data)
+ return nullptr;
+
+ if (indexes.size() == 0)
+ return nullptr;
+
+ QByteArray output;
+ QDataStream stream(&output, QIODevice::WriteOnly);
+
+ QList<QUrl> urlList;
+ QStringList textList;
+
+ DbTreeItem* item = nullptr;
+ stream << reinterpret_cast<qint32>(indexes.size());
+ for (const QModelIndex& idx : indexes)
+ {
+ item = dynamic_cast<DbTreeItem*>(itemFromIndex(idx));
+ stream << item->signature();
+
+ textList << item->text();
+ if (item->getType() == DbTreeItem::Type::DB)
+ urlList << QUrl("file://"+item->getDb()->getPath());
+ }
+ data->setData(MIMETYPE, output);
+ data->setText(textList.join("\n"));
+ data->setUrls(urlList);
+
+ return data;
+}
+
+bool DbTreeModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
+{
+ UNUSED(action);
+ // The result means: do we want the old item to be removed from the tree?
+ bool invokeStdAction = false;
+ bool res = pasteData(data, row, column, parent, Qt::IgnoreAction, &invokeStdAction);
+ if (!invokeStdAction)
+ return res;
+
+ return QStandardItemModel::dropMimeData(data, action, row, column, parent);
+}
+
+bool DbTreeModel::pasteData(const QMimeData* data, int row, int column, const QModelIndex& parent, Qt::DropAction defaultAction, bool* invokeStdAction)
+{
+ // The result means: do we want the old item to be removed from the tree?
+ DbTreeItem* dstItem = nullptr;
+ if (parent.isValid())
+ {
+ QModelIndex idx = parent.child(row, column);
+ if (idx.isValid())
+ dstItem = dynamic_cast<DbTreeItem*>(itemFromIndex(idx));
+ else // drop on top of the parent
+ dstItem = dynamic_cast<DbTreeItem*>(itemFromIndex(parent));
+ }
+ else
+ {
+ dstItem = dynamic_cast<DbTreeItem*>(item(row, column));
+ }
+
+ if (data->formats().contains(MIMETYPE))
+ return dropDbTreeItem(getDragItems(data), dstItem, defaultAction, *invokeStdAction);
+ else if (data->hasUrls())
+ return dropUrls(data->urls());
+ else
+ return false;
+}
+
+void DbTreeModel::interruptableStarted(Interruptable* obj)
+{
+ if (interruptables.size() == 0)
+ treeView->getDbTree()->showWidgetCover();
+
+ interruptables << obj;
+}
+
+void DbTreeModel::interruptableFinished(Interruptable* obj)
+{
+ interruptables.removeOne(obj);
+ if (interruptables.size() == 0)
+ treeView->getDbTree()->hideWidgetCover();
+}
+
+QList<DbTreeItem*> DbTreeModel::getDragItems(const QMimeData* data)
+{
+ QList<DbTreeItem*> items;
+ QByteArray byteData = data->data(MIMETYPE);
+ QDataStream stream(&byteData, QIODevice::ReadOnly);
+
+ qint32 itemCount;
+ stream >> itemCount;
+
+ DbTreeItem* item = nullptr;
+ QString signature;
+ for (qint32 i = 0; i < itemCount; i++)
+ {
+ stream >> signature;
+ item = findItemBySignature(signature);
+ if (item)
+ items << item;
+ }
+
+ return items;
+}
+
+QList<DbTreeItem*> DbTreeModel::getItemsForIndexes(const QModelIndexList& indexes) const
+{
+ QList<DbTreeItem*> items;
+ for (const QModelIndex& idx : indexes)
+ {
+ if (idx.isValid())
+ items << dynamic_cast<DbTreeItem*>(itemFromIndex(idx));
+ }
+
+ return items;
+}
+
+void DbTreeModel::staticInit()
+{
+}
+
+bool DbTreeModel::dropDbTreeItem(const QList<DbTreeItem*>& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction, bool& invokeStdDropAction)
+{
+ // The result means: do we want the old item to be removed from the tree?
+ if (srcItems.size() == 0)
+ return false;
+
+ DbTreeItem* srcItem = srcItems.first();
+ switch (srcItem->getType())
+ {
+ case DbTreeItem::Type::TABLE:
+ case DbTreeItem::Type::VIEW:
+ {
+ if (!dstItem)
+ return false;
+
+ if (srcItem->getDb() == dstItem->getDb())
+ return true;
+
+ return dropDbObjectItem(srcItems, dstItem, defaultAction);
+ }
+ case DbTreeItem::Type::DB:
+ case DbTreeItem::Type::DIR:
+ invokeStdDropAction = true;
+ break;
+ case DbTreeItem::Type::COLUMN:
+ case DbTreeItem::Type::TABLES:
+ case DbTreeItem::Type::INDEXES:
+ case DbTreeItem::Type::INDEX:
+ case DbTreeItem::Type::TRIGGERS:
+ case DbTreeItem::Type::TRIGGER:
+ case DbTreeItem::Type::VIEWS:
+ case DbTreeItem::Type::COLUMNS:
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ break;
+ }
+
+ return false;
+}
+
+bool DbTreeModel::dropDbObjectItem(const QList<DbTreeItem*>& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction)
+{
+ bool copy = false;
+ bool move = false;
+ bool includeData = false;
+ bool includeIndexes = false;
+ bool includeTriggers = false;
+
+ if (defaultAction == Qt::CopyAction)
+ {
+ copy = true;
+ includeData = true;
+ includeIndexes = true;
+ includeTriggers = true;
+ }
+ else if (defaultAction == Qt::MoveAction)
+ {
+ move = true;
+ includeData = true;
+ includeIndexes = true;
+ includeTriggers = true;
+ }
+ else
+ {
+ QMenu menu;
+ QAction* copyAction = menu.addAction(ICONS.ACT_COPY, tr("Copy"));
+ QAction* moveAction = menu.addAction(ICONS.ACT_CUT, tr("Move"));
+ menu.addSeparator();
+ QCheckBox *includeDataCheck = createCopyOrMoveMenuCheckBox(&menu, tr("Include data"));
+ QCheckBox *includeIndexesCheck = createCopyOrMoveMenuCheckBox(&menu, tr("Include indexes"));
+ QCheckBox *includeTriggersCheck = createCopyOrMoveMenuCheckBox(&menu, tr("Include triggers"));
+ menu.addSeparator();
+ menu.addAction(ICONS.ACT_ABORT, tr("Abort"));
+
+ connect(moveAction, &QAction::triggered, [&move]() {move = true;});
+ connect(copyAction, &QAction::triggered, [&copy]() {copy = true;});
+
+ menu.exec(treeView->mapToGlobal(treeView->getLastDropPosition()));
+
+ includeData = includeDataCheck->isChecked();
+ includeIndexes = includeIndexesCheck->isChecked();
+ includeTriggers = includeTriggersCheck->isChecked();
+ }
+
+ // The result means: do we want the old item to be removed from the tree?
+ if (!copy && !move)
+ return false;
+
+ moveOrCopyDbObjects(srcItems, dstItem, move, includeData, includeIndexes, includeTriggers);
+ return move;
+}
+
+QCheckBox* DbTreeModel::createCopyOrMoveMenuCheckBox(QMenu* menu, const QString& label)
+{
+ QWidget* parentWidget = new QWidget(menu);
+ parentWidget->setLayout(new QVBoxLayout());
+ QMargins margins = parentWidget->layout()->contentsMargins();
+ parentWidget->layout()->setContentsMargins(margins.left(), 0, margins.right(), 0);
+
+ QCheckBox *cb = new QCheckBox(label);
+ cb->setChecked(true);
+ parentWidget->layout()->addWidget(cb);
+
+ QWidgetAction *action = new QWidgetAction(menu);
+ action->setDefaultWidget(parentWidget);
+ menu->addAction(action);
+ return cb;
+}
+
+bool DbTreeModel::dropUrls(const QList<QUrl>& urls)
+{
+ for (const QUrl& url : urls)
+ {
+ if (!url.isLocalFile())
+ {
+ qDebug() << url.toString() + "skipped, not a local file.";
+ continue;
+ }
+
+ DbDialog dialog(DbDialog::ADD, MAINWINDOW);
+ dialog.setPath(url.toLocalFile());
+ dialog.exec();
+ }
+ return false;
+}
+
+void DbTreeModel::moveOrCopyDbObjects(const QList<DbTreeItem*>& srcItems, DbTreeItem* dstItem, bool move, bool includeData, bool includeIndexes, bool includeTriggers)
+{
+ if (srcItems.size() == 0)
+ return;
+
+ DbTreeItem* srcItem = srcItems.first();
+ Db* srcDb = srcItem->getDb();
+ Db* dstDb = dstItem->getDb();
+
+ QStringList srcNames;
+ for (DbTreeItem* item : srcItems)
+ srcNames << item->text();
+
+ interruptableStarted(dbOrganizer);
+ if (move)
+ dbOrganizer->moveObjectsToDb(srcDb, srcNames, dstDb, includeData, includeIndexes, includeTriggers);
+ else
+ dbOrganizer->copyObjectsToDb(srcDb, srcNames, dstDb, includeData, includeIndexes, includeTriggers);
+}
+
+bool DbTreeModel::confirmReferencedTables(const QStringList& tables)
+{
+ QMessageBox::StandardButton result = QMessageBox::question(MAINWINDOW, tr("Referenced tables"),
+ tr("Do you want to include following referenced tables as well:\n%1").arg(tables.join(", ")));
+
+ return result == QMessageBox::Yes;
+}
+
+bool DbTreeModel::resolveNameConflict(QString& nameInConflict)
+{
+ bool ok = false;
+ QInputDialog tmpDialog; // just for a cancel button text
+ QString result = QInputDialog::getText(MAINWINDOW, tr("Name conflict"),
+ tr("Following object already exists in the target database.\nPlease enter new, unique name, or "
+ "press '%1' to abort the operation:").arg(tmpDialog.cancelButtonText()),
+ QLineEdit::Normal, nameInConflict, &ok);
+
+ if (ok)
+ nameInConflict = result;
+
+ return ok;
+}
+
+bool DbTreeModel::confirmConversion(const QList<QPair<QString, QString> >& diffs)
+{
+ VersionConvertSummaryDialog dialog(MAINWINDOW);
+ dialog.setWindowTitle(tr("SQL statements conversion"));
+ dialog.setSides(diffs);
+ return dialog.exec() == QDialog::Accepted;
+}
+
+bool DbTreeModel::confirmConversionErrors(const QHash<QString,QSet<QString>>& errors)
+{
+ ErrorsConfirmDialog dialog(MAINWINDOW);
+ dialog.setTopLabel(tr("Following error occurred while converting SQL statements to the target SQLite version:"));
+ dialog.setBottomLabel(tr("Would you like to ignore those errors and proceed?"));
+ dialog.setErrors(errors);
+ return dialog.exec() == QDialog::Accepted;
+}
+bool DbTreeModel::getIgnoreDbLoadedSignal() const
+{
+ return ignoreDbLoadedSignal;
+}
+
+void DbTreeModel::setIgnoreDbLoadedSignal(bool value)
+{
+ ignoreDbLoadedSignal = value;
+}
+
+bool DbTreeModel::hasDbTreeItem(const QMimeData *data)
+{
+ return data->formats().contains(MIMETYPE);
+}
+
+void DbTreeModel::dbObjectsMoveFinished(bool success, Db* srcDb, Db* dstDb)
+{
+ if (!success)
+ {
+ interruptableFinished(dbOrganizer);
+ return;
+ }
+
+ DBTREE->refreshSchema(srcDb);
+ DBTREE->refreshSchema(dstDb);
+ interruptableFinished(dbOrganizer);
+}
+
+void DbTreeModel::dbObjectsCopyFinished(bool success, Db* srcDb, Db* dstDb)
+{
+ dbObjectsMoveFinished(success, srcDb, dstDb);
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h
new file mode 100644
index 0000000..c92fa2c
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h
@@ -0,0 +1,135 @@
+#ifndef DBTREEMODEL_H
+#define DBTREEMODEL_H
+
+#include "db/db.h"
+#include "dbtreeitem.h"
+#include "services/config.h"
+#include "guiSQLiteStudio_global.h"
+#include "common/strhash.h"
+#include <QStandardItemModel>
+#include <QObject>
+
+class DbManager;
+class DbTreeView;
+class DbPlugin;
+class DbObjectOrganizer;
+class QMenu;
+class QCheckBox;
+
+class GUI_API_EXPORT DbTreeModel : public QStandardItemModel
+{
+ Q_OBJECT
+
+ public:
+ DbTreeModel();
+ ~DbTreeModel();
+
+ void connectDbManagerSignals();
+ DbTreeItem* findItem(DbTreeItem::Type type, const QString &name);
+ DbTreeItem* findItem(DbTreeItem::Type type, Db* db);
+ DbTreeItem* findItemBySignature(const QString& signature);
+ QList<DbTreeItem*> findItems(DbTreeItem::Type type);
+ void move(QStandardItem* itemToMove, QStandardItem* newParentItem, int newRow = -1);
+ void move(QStandardItem* itemToMove, int newRow);
+ DbTreeItem *createGroup(const QString& name, QStandardItem *parent = nullptr);
+ void deleteGroup(QStandardItem* groupItem);
+ QStandardItem *root() const;
+ QStringList getGroupFor(QStandardItem* item);
+ void storeGroups();
+ void refreshSchema(Db* db);
+ QList<DbTreeItem*> getAllItemsAsFlatList() const;
+ void setTreeView(DbTreeView *value);
+ QVariant data(const QModelIndex &index, int role) const;
+ QStringList mimeTypes() const;
+ QMimeData* mimeData(const QModelIndexList &indexes) const;
+ bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
+ bool pasteData(const QMimeData* data, int row, int column, const QModelIndex& parent, Qt::DropAction defaultAction = Qt::IgnoreAction,
+ bool *invokeStdAction = nullptr);
+ void interruptableStarted(Interruptable* obj);
+ void interruptableFinished(Interruptable* obj);
+ bool getIgnoreDbLoadedSignal() const;
+ void setIgnoreDbLoadedSignal(bool value);
+ bool hasDbTreeItem(const QMimeData* data);
+ QList<DbTreeItem*> getDragItems(const QMimeData* data);
+ QList<DbTreeItem*> getItemsForIndexes(const QModelIndexList& indexes) const;
+
+ static DbTreeItem* findItem(QStandardItem *parentItem, DbTreeItem::Type type, const QString &name);
+ static DbTreeItem* findItem(QStandardItem* parentItem, DbTreeItem::Type type, Db* db);
+ static QList<DbTreeItem*> findItems(QStandardItem* parentItem, DbTreeItem::Type type);
+ static void staticInit();
+
+ static const constexpr char* MIMETYPE = "application/x-sqlitestudio-dbtreeitem";
+
+ private:
+ void readGroups(QList<Db*> dbList);
+ QList<Config::DbGroupPtr> childsToConfig(QStandardItem* item);
+ void restoreGroup(const Config::DbGroupPtr& group, QList<Db*>* dbList = nullptr, QStandardItem *parent = nullptr);
+ bool applyFilter(QStandardItem* parentItem, const QString& filter);
+ void refreshSchema(Db* db, QStandardItem* item);
+ void collectExpandedState(QHash<QString, bool>& state, QStandardItem* parentItem = nullptr);
+ QStandardItem* refreshSchemaDb(Db* db);
+ QList<QStandardItem*> refreshSchemaTables(const QStringList &tables, const QStringList& virtualTables, bool sort);
+ StrHash<QList<QStandardItem*> > refreshSchemaTableColumns(const StrHash<QStringList>& columns);
+ StrHash<QList<QStandardItem*> > refreshSchemaIndexes(const StrHash<QStringList>& indexes, bool sort);
+ StrHash<QList<QStandardItem*> > refreshSchemaTriggers(const StrHash<QStringList>& triggers, bool sort);
+ QList<QStandardItem*> refreshSchemaViews(const QStringList &views, bool sort);
+ void populateChildItemsWithDb(QStandardItem* parentItem, Db* db);
+ void refreshSchemaBuild(QStandardItem* dbItem, QList<QStandardItem*> tables, StrHash<QList<QStandardItem*> > indexes,
+ StrHash<QList<QStandardItem*> > triggers, QList<QStandardItem*> views, StrHash<QList<QStandardItem*> > allTableColumns);
+ void restoreExpandedState(const QHash<QString, bool>& expandedState, QStandardItem* parentItem);
+ QString getToolTip(DbTreeItem *item) const;
+ QString getDbToolTip(DbTreeItem *item) const;
+ QString getTableToolTip(DbTreeItem *item) const;
+ QList<DbTreeItem*> getChildsAsFlatList(QStandardItem* item) const;
+ bool dropDbTreeItem(const QList<DbTreeItem*>& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction, bool &invokeStdDropAction);
+ bool dropDbObjectItem(const QList<DbTreeItem*>& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction);
+ QCheckBox* createCopyOrMoveMenuCheckBox(QMenu* menu, const QString& label);
+ bool dropUrls(const QList<QUrl>& urls);
+ void moveOrCopyDbObjects(const QList<DbTreeItem*>& srcItems, DbTreeItem* dstItem, bool move, bool includeData, bool includeIndexes, bool includeTriggers);
+
+ static bool confirmReferencedTables(const QStringList& tables);
+ static bool resolveNameConflict(QString& nameInConflict);
+ static bool confirmConversion(const QList<QPair<QString,QString>>& diffs);
+ static bool confirmConversionErrors(const QHash<QString, QSet<QString> >& errors);
+
+ static const QString toolTipTableTmp;
+ static const QString toolTipHdrRowTmp;
+ static const QString toolTipRowTmp;
+ static const QString toolTipIconRowTmp;
+
+ DbTreeView* treeView = nullptr;
+ bool requireSchemaReloading = false;
+ DbObjectOrganizer* dbOrganizer = nullptr;
+ QList<Interruptable*> interruptables;
+ bool ignoreDbLoadedSignal = false;
+ QString currentFilter;
+
+ private slots:
+ void expanded(const QModelIndex &index);
+ void collapsed(const QModelIndex &index);
+ void dbAdded(Db* db);
+ void dbUpdated(const QString &oldName, Db* db);
+ void dbRemoved(Db* db);
+ void dbConnected(Db* db);
+ void dbDisconnected(Db* db);
+ void dbUnloaded(Db* db);
+ void dbLoaded(Db* db);
+ void massSaveBegins();
+ void massSaveCommited();
+ void markSchemaReloadingRequired();
+ void dbObjectsMoveFinished(bool success, Db* srcDb, Db* dstDb);
+ void dbObjectsCopyFinished(bool success, Db* srcDb, Db* dstDb);
+
+ public slots:
+ void loadDbList();
+ void itemChangedVisibility(DbTreeItem* item);
+ void applyFilter(const QString& filter);
+ void dbRemoved(const QString& name);
+ void dbRemoved(QStandardItem* item);
+ void interrupt();
+
+ signals:
+ void updateItemHidden(DbTreeItem* item);
+};
+
+#endif // DBTREEMODEL_H
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.cpp
new file mode 100644
index 0000000..7785b8f
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.cpp
@@ -0,0 +1,255 @@
+#include "dbtreeview.h"
+#include "dbtreemodel.h"
+#include "dbtreeitemdelegate.h"
+#include "mainwindow.h"
+#include "services/dbmanager.h"
+#include "common/unused.h"
+#include <QDragMoveEvent>
+#include <QMenu>
+#include <QList>
+#include <QMimeData>
+#include <QDebug>
+
+DbTreeView::DbTreeView(QWidget *parent) :
+ QTreeView(parent)
+{
+ contextMenu = new QMenu(this);
+ connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint)));
+
+ setHeaderHidden(true);
+ setContextMenuPolicy(Qt::CustomContextMenu);
+ setSelectionMode(QAbstractItemView::ExtendedSelection);
+
+ itemDelegate = new DbTreeItemDelegate();
+ setItemDelegate(itemDelegate);
+}
+
+DbTreeView::~DbTreeView()
+{
+ delete contextMenu;
+ delete itemDelegate;
+}
+
+void DbTreeView::setDbTree(DbTree *dbTree)
+{
+ this->dbTree = dbTree;
+}
+
+DbTree* DbTreeView::getDbTree() const
+{
+ return dbTree;
+}
+
+DbTreeItem *DbTreeView::currentItem()
+{
+ return dynamic_cast<DbTreeItem*>(model()->itemFromIndex(currentIndex()));
+}
+
+DbTreeItem *DbTreeView::itemAt(const QPoint &pos)
+{
+ return dynamic_cast<DbTreeItem*>(model()->itemFromIndex(indexAt(pos)));
+}
+
+QList<DbTreeItem *> DbTreeView::selectionItems()
+{
+ QList<DbTreeItem*> items;
+ QModelIndexList selectedIndexes = selectionModel()->selectedIndexes();
+ foreach (QModelIndex modIdx, selectedIndexes)
+ items += dynamic_cast<DbTreeItem*>(model()->itemFromIndex(modIdx));
+
+ return items;
+}
+
+DbTreeModel *DbTreeView::model() const
+{
+ return dynamic_cast<DbTreeModel*>(QTreeView::model());
+}
+
+void DbTreeView::showMenu(const QPoint &pos)
+{
+ contextMenu->clear();
+
+ DbTreeItem* itemUnderCursor = itemAt(pos);
+ if (!itemUnderCursor)
+ selectionModel()->clear();
+
+ DbTreeItem* item = getItemForAction();
+ dbTree->setupActionsForMenu(item, contextMenu);
+ if (contextMenu->actions().size() == 0)
+ return;
+
+ dbTree->updateActionStates(item);
+ contextMenu->popup(mapToGlobal(pos));
+}
+
+void DbTreeView::updateItemHidden(DbTreeItem* item)
+{
+ setRowHidden(item->index().row(), item->index().parent(), item->isHidden());
+}
+
+DbTreeItem *DbTreeView::getItemForAction(bool onlySelected) const
+{
+ QModelIndex idx = selectionModel()->currentIndex();
+ if (onlySelected && !selectionModel()->isSelected(idx))
+ return nullptr;
+
+ return dynamic_cast<DbTreeItem*>(model()->itemFromIndex(idx));
+}
+
+void DbTreeView::dragEnterEvent(QDragEnterEvent* e)
+{
+ QTreeView::dragEnterEvent(e);
+ if (e->mimeData()->hasUrls())
+ e->acceptProposedAction();
+}
+
+void DbTreeView::dragMoveEvent(QDragMoveEvent *event)
+{
+ QTreeView::dragMoveEvent(event);
+
+ DbTreeItem* dstItem = itemAt(event->pos());
+
+ // Depending on where we drop we need a type of item we drop ON,
+ // or type of parent item if we drop ABOVE/BELOW. If we drop on empty space,
+ // we leave type as default.
+ if (dstItem)
+ {
+ QAbstractItemView::DropIndicatorPosition dropPosition = dropIndicatorPosition();
+ switch (dropPosition)
+ {
+ case QAbstractItemView::OnItem:
+ break;
+ case QAbstractItemView::AboveItem:
+ case QAbstractItemView::BelowItem:
+ {
+ dstItem = dstItem->parentDbTreeItem();
+ break;
+ }
+ case QAbstractItemView::OnViewport:
+ dstItem = nullptr;
+ break;
+ }
+ }
+
+ //qDebug() << event->mimeData()->formats();
+ const QMimeData* data = event->mimeData();
+ if (dbTree->isMimeDataValidForItem(data, dstItem))
+ event->acceptProposedAction();
+ else
+ event->ignore();
+}
+
+void DbTreeView::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ DbTreeItem* itemUnderCursor = itemAt(event->pos());
+ if (itemUnderCursor && !handleDoubleClick(itemUnderCursor))
+ return;
+
+ QTreeView::mouseDoubleClickEvent(event);
+}
+
+bool DbTreeView::handleDoubleClick(DbTreeItem *item)
+{
+ switch (item->getType())
+ {
+ case DbTreeItem::Type::DIR:
+ break;
+ case DbTreeItem::Type::DB:
+ {
+ if (item->getDb()->isValid())
+ return handleDbDoubleClick(item);
+ }
+ case DbTreeItem::Type::TABLES:
+ break;
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ // TODO if module for virtual table is loaded - show virtual table window
+ break;
+ case DbTreeItem::Type::TABLE:
+ return handleTableDoubleClick(item);
+ case DbTreeItem::Type::INDEXES:
+ break;
+ case DbTreeItem::Type::INDEX:
+ return handleIndexDoubleClick(item);
+ case DbTreeItem::Type::TRIGGERS:
+ break;
+ case DbTreeItem::Type::TRIGGER:
+ return handleTriggerDoubleClick(item);
+ case DbTreeItem::Type::VIEWS:
+ break;
+ case DbTreeItem::Type::VIEW:
+ return handleViewDoubleClick(item);
+ case DbTreeItem::Type::COLUMNS:
+ break;
+ case DbTreeItem::Type::COLUMN:
+ return handleColumnDoubleClick(item);
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ break;
+ }
+
+ return true;
+}
+
+bool DbTreeView::handleDbDoubleClick(DbTreeItem *item)
+{
+ if (!item->getDb()->isOpen())
+ {
+ dbTree->getAction(DbTree::CONNECT_TO_DB)->trigger();
+ return false;
+ }
+ return true;
+}
+
+bool DbTreeView::handleTableDoubleClick(DbTreeItem *item)
+{
+ dbTree->openTable(item);
+ return false;
+}
+
+bool DbTreeView::handleIndexDoubleClick(DbTreeItem *item)
+{
+ dbTree->editIndex(item);
+ return false;
+}
+
+bool DbTreeView::handleTriggerDoubleClick(DbTreeItem *item)
+{
+ dbTree->editTrigger(item);
+ return false;
+}
+
+bool DbTreeView::handleViewDoubleClick(DbTreeItem *item)
+{
+ dbTree->openView(item);
+ return false;
+}
+
+bool DbTreeView::handleColumnDoubleClick(DbTreeItem *item)
+{
+ dbTree->editColumn(item);
+ return false;
+}
+
+QPoint DbTreeView::getLastDropPosition() const
+{
+ return lastDropPosition;
+}
+
+QModelIndexList DbTreeView::getSelectedIndexes() const
+{
+ QModelIndexList idxList = selectedIndexes();
+ if (currentIndex().isValid() && !idxList.contains(currentIndex()))
+ idxList << currentIndex();
+
+ return idxList;
+}
+
+void DbTreeView::dropEvent(QDropEvent* e)
+{
+ lastDropPosition = e->pos();
+ QTreeView::dropEvent(e);
+ if (!e->isAccepted() && e->mimeData()->hasUrls() && !dbTree->getModel()->hasDbTreeItem(e->mimeData()))
+ {
+ dbTree->getModel()->dropMimeData(e->mimeData(), Qt::CopyAction, -1, -1, dbTree->getModel()->root()->index());
+ e->accept();
+ }
+}
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h
new file mode 100644
index 0000000..3ec33b4
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h
@@ -0,0 +1,57 @@
+#ifndef DBTREEVIEW_H
+#define DBTREEVIEW_H
+
+#include "dbtree.h"
+#include "guiSQLiteStudio_global.h"
+#include <QTreeView>
+#include <QList>
+#include <QUrl>
+
+class QMenu;
+class QStandardItemModel;
+class DbTreeItemDelegate;
+
+class GUI_API_EXPORT DbTreeView : public QTreeView
+{
+ Q_OBJECT
+ public:
+ explicit DbTreeView(QWidget *parent = 0);
+ ~DbTreeView();
+
+ void setDbTree(DbTree* dbTree);
+ DbTree* getDbTree() const;
+
+ DbTreeItem *currentItem();
+ DbTreeItem *itemAt(const QPoint& pos);
+ QList<DbTreeItem *> selectionItems();
+ DbTreeModel *model() const;
+ DbTreeItem *getItemForAction(bool onlySelected = false) const;
+ QPoint getLastDropPosition() const;
+ QModelIndexList getSelectedIndexes() const;
+
+ protected:
+ void dragEnterEvent(QDragEnterEvent* e);
+ void dragMoveEvent(QDragMoveEvent *event);
+ void mouseDoubleClickEvent(QMouseEvent* event);
+ void dropEvent(QDropEvent*e);
+
+ private:
+ bool handleDoubleClick(DbTreeItem* item);
+ bool handleDbDoubleClick(DbTreeItem* item);
+ bool handleTableDoubleClick(DbTreeItem* item);
+ bool handleIndexDoubleClick(DbTreeItem* item);
+ bool handleTriggerDoubleClick(DbTreeItem* item);
+ bool handleViewDoubleClick(DbTreeItem* item);
+ bool handleColumnDoubleClick(DbTreeItem* item);
+
+ QMenu* contextMenu = nullptr;
+ DbTree* dbTree = nullptr;
+ DbTreeItemDelegate* itemDelegate = nullptr;
+ QPoint lastDropPosition;
+
+ private slots:
+ void showMenu(const QPoint &pos);
+ void updateItemHidden(DbTreeItem* item);
+};
+
+#endif // DBTREEVIEW_H