diff options
| author | 2014-12-06 17:33:25 -0500 | |
|---|---|---|
| committer | 2014-12-06 17:33:25 -0500 | |
| commit | 7167ce41b61d2ba2cdb526777a4233eb84a3b66a (patch) | |
| tree | a35c14143716e1f2c98f808c81f89426045a946f /SQLiteStudio3/guiSQLiteStudio/dbtree | |
Imported Upstream version 2.99.6upstream/2.99.6
Diffstat (limited to 'SQLiteStudio3/guiSQLiteStudio/dbtree')
| -rw-r--r-- | SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.cpp | 1557 | ||||
| -rw-r--r-- | SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.h | 205 | ||||
| -rw-r--r-- | SQLiteStudio3/guiSQLiteStudio/dbtree/dbtree.ui | 90 | ||||
| -rw-r--r-- | SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.cpp | 332 | ||||
| -rw-r--r-- | SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitem.h | 112 | ||||
| -rw-r--r-- | SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.cpp | 164 | ||||
| -rw-r--r-- | SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemdelegate.h | 28 | ||||
| -rw-r--r-- | SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.cpp | 74 | ||||
| -rw-r--r-- | SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeitemfactory.h | 26 | ||||
| -rw-r--r-- | SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp | 1222 | ||||
| -rw-r--r-- | SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.h | 135 | ||||
| -rw-r--r-- | SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.cpp | 255 | ||||
| -rw-r--r-- | SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreeview.h | 57 |
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 ¤t, 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 = 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 |
