aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp')
-rw-r--r--SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp1222
1 files changed, 1222 insertions, 0 deletions
diff --git a/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp
new file mode 100644
index 0000000..8a71a10
--- /dev/null
+++ b/SQLiteStudio3/guiSQLiteStudio/dbtree/dbtreemodel.cpp
@@ -0,0 +1,1222 @@
+#include "dbtreemodel.h"
+#include "services/dbmanager.h"
+#include "dbtreeview.h"
+#include "iconmanager.h"
+#include "uiconfig.h"
+#include "schemaresolver.h"
+#include "dbtreeitemfactory.h"
+#include "common/unused.h"
+#include "services/pluginmanager.h"
+#include "plugins/dbplugin.h"
+#include "dbobjectorganizer.h"
+#include "dialogs/dbdialog.h"
+#include "dialogs/errorsconfirmdialog.h"
+#include "dialogs/versionconvertsummarydialog.h"
+#include "db/invaliddb.h"
+#include <QMimeData>
+#include <QDebug>
+#include <QFile>
+#include <QFileInfo>
+#include <QMessageBox>
+#include <QInputDialog>
+#include <QCheckBox>
+#include <QWidgetAction>
+#include <QClipboard>
+
+const QString DbTreeModel::toolTipTableTmp = "<table>%1</table>";
+const QString DbTreeModel::toolTipHdrRowTmp = "<tr><th><img src=\"%1\"/></th><th colspan=2>%2</th></tr>";
+const QString DbTreeModel::toolTipRowTmp = "<tr><td></td><td>%1</td><td align=\"right\">%2</td></tr>";
+const QString DbTreeModel::toolTipIconRowTmp = "<tr><td><img src=\"%1\"/></td><td>%2</td><td align=\"right\">%3</td></tr>";
+
+DbTreeModel::DbTreeModel()
+{
+ setItemPrototype(DbTreeItemFactory::createPrototype());
+ connectDbManagerSignals();
+
+ connect(CFG, SIGNAL(massSaveBegins()), this, SLOT(massSaveBegins()));
+ connect(CFG, SIGNAL(massSaveCommited()), this, SLOT(massSaveCommited()));
+ connect(CFG_UI.General.ShowSystemObjects, SIGNAL(changed(QVariant)), this, SLOT(markSchemaReloadingRequired()));
+
+ dbOrganizer = new DbObjectOrganizer(confirmReferencedTables, resolveNameConflict, confirmConversion, confirmConversionErrors);
+ dbOrganizer->setAutoDelete(false);
+ connect(dbOrganizer, SIGNAL(finishedDbObjectsCopy(bool,Db*,Db*)), this, SLOT(dbObjectsCopyFinished(bool,Db*,Db*)));
+ connect(dbOrganizer, SIGNAL(finishedDbObjectsMove(bool,Db*,Db*)), this, SLOT(dbObjectsMoveFinished(bool,Db*,Db*)));
+}
+
+DbTreeModel::~DbTreeModel()
+{
+}
+
+void DbTreeModel::connectDbManagerSignals()
+{
+ connect(DBLIST, SIGNAL(dbAdded(Db*)), this, SLOT(dbAdded(Db*)));
+ connect(DBLIST, SIGNAL(dbUpdated(QString,Db*)), this, SLOT(dbUpdated(QString,Db*)));
+ connect(DBLIST, SIGNAL(dbRemoved(Db*)), this, SLOT(dbRemoved(Db*)));
+ connect(DBLIST, SIGNAL(dbConnected(Db*)), this, SLOT(dbConnected(Db*)));
+ connect(DBLIST, SIGNAL(dbDisconnected(Db*)), this, SLOT(dbDisconnected(Db*)));
+ connect(DBLIST, SIGNAL(dbLoaded(Db*)), this, SLOT(dbLoaded(Db*)));
+ connect(DBLIST, SIGNAL(dbUnloaded(Db*)), this, SLOT(dbUnloaded(Db*)));
+}
+
+void DbTreeModel::move(QStandardItem *itemToMove, QStandardItem *newParentItem, int newRow)
+{
+ QStandardItem* currParent = dynamic_cast<DbTreeItem*>(itemToMove)->parentItem();
+ if (!newParentItem)
+ newParentItem = root();
+
+ if (newParentItem == currParent)
+ {
+ move(itemToMove, newRow);
+ return;
+ }
+
+ int oldRow = itemToMove->index().row();
+ currParent->takeRow(oldRow);
+
+ if (newRow > currParent->rowCount() || newRow < 0)
+ newParentItem->appendRow(itemToMove);
+ else
+ newParentItem->insertRow(newRow, itemToMove);
+}
+
+void DbTreeModel::move(QStandardItem *itemToMove, int newRow)
+{
+ QStandardItem* currParent = dynamic_cast<DbTreeItem*>(itemToMove)->parentItem();
+ int oldRow = itemToMove->index().row();
+ currParent->takeRow(oldRow);
+ if (newRow > currParent->rowCount() || newRow < 0)
+ currParent->appendRow(itemToMove);
+ else if (oldRow < newRow)
+ currParent->insertRow(newRow - 1, itemToMove);
+ else
+ currParent->insertRow(newRow, itemToMove);
+}
+
+void DbTreeModel::deleteGroup(QStandardItem *groupItem)
+{
+ QStandardItem* parentItem = dynamic_cast<DbTreeItem*>(groupItem)->parentItem();
+ if (!parentItem)
+ parentItem = root();
+
+ foreach (QStandardItem* child, dynamic_cast<DbTreeItem*>(groupItem)->childs())
+ move(child, parentItem);
+
+ parentItem->removeRow(groupItem->row());
+}
+
+DbTreeItem* DbTreeModel::createGroup(const QString& name, QStandardItem* parent)
+{
+ if (!parent)
+ parent = root();
+
+ DbTreeItem* item = DbTreeItemFactory::createDir(name, this);
+ parent->appendRow(item);
+ return item;
+}
+
+QStringList DbTreeModel::getGroupFor(QStandardItem *item)
+{
+ QStringList group;
+ while ((item = item->parent()) != nullptr)
+ {
+ if (dynamic_cast<DbTreeItem*>(item)->getType() == DbTreeItem::Type::DIR)
+ group.prepend(item->text());
+ }
+ return group;
+}
+
+void DbTreeModel::applyFilter(const QString &filter)
+{
+ applyFilter(root(), filter);
+ currentFilter = filter;
+}
+
+bool DbTreeModel::applyFilter(QStandardItem *parentItem, const QString &filter)
+{
+ bool empty = filter.isEmpty();
+ bool visibilityForParent = false;
+ DbTreeItem* item = nullptr;
+ QModelIndex index;
+ bool subFilterResult;
+ bool matched;
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ {
+ item = dynamic_cast<DbTreeItem*>(parentItem->child(i));
+ index = item->index();
+ subFilterResult = applyFilter(item, filter);
+ matched = empty || subFilterResult || item->text().contains(filter, Qt::CaseInsensitive);
+ treeView->setRowHidden(index.row(), index.parent(), !matched);
+
+ if (matched)
+ visibilityForParent = true;
+ }
+ return visibilityForParent;
+}
+
+void DbTreeModel::storeGroups()
+{
+ QList<Config::DbGroupPtr> groups = childsToConfig(invisibleRootItem());
+ CFG->storeGroups(groups);
+}
+
+void DbTreeModel::readGroups(QList<Db*> dbList)
+{
+ QList<Config::DbGroupPtr> groups = CFG->getGroups();
+ foreach (const Config::DbGroupPtr& group, groups)
+ restoreGroup(group, &dbList);
+
+ // Add rest of databases, not mentioned in groups
+ Config::DbGroupPtr group;
+ foreach (Db* db, dbList)
+ {
+ group = Config::DbGroupPtr::create();
+ group->referencedDbName = db->getName();
+ restoreGroup(group);
+ }
+}
+
+QList<Config::DbGroupPtr> DbTreeModel::childsToConfig(QStandardItem *item)
+{
+ QList<Config::DbGroupPtr> groups;
+ Config::DbGroupPtr group;
+ DbTreeItem* dbTreeItem = nullptr;
+ for (int i = 0; i < item->rowCount(); i++)
+ {
+ dbTreeItem = dynamic_cast<DbTreeItem*>(item->child(i));
+ switch (dbTreeItem->getType())
+ {
+ case DbTreeItem::Type::DIR:
+ {
+ group = Config::DbGroupPtr::create();
+ group->name = dbTreeItem->text();
+ group->order = i;
+ group->open = treeView->isExpanded(dbTreeItem->index());
+ group->childs = childsToConfig(dbTreeItem);
+ groups += group;
+ break;
+ }
+ case DbTreeItem::Type::DB:
+ {
+ group = Config::DbGroupPtr::create();
+ group->referencedDbName = dbTreeItem->text();
+ group->order = i;
+ group->open = dbTreeItem->getDb()->isOpen();
+ groups += group;
+ break;
+ }
+ default:
+ // no-op
+ break;
+ }
+ }
+ return groups;
+}
+
+void DbTreeModel::restoreGroup(const Config::DbGroupPtr& group, QList<Db*>* dbList, QStandardItem* parent)
+{
+ Db* db = nullptr;
+ DbTreeItem* item = nullptr;
+ if (group->referencedDbName.isNull())
+ {
+ item = DbTreeItemFactory::createDir(group->name, this);
+ }
+ else
+ {
+ // If db is managed by manager, it means it was successfully loaded.
+ // Otherwise there was a problem with the file, or with plugin for that database
+ // and we still want to have dbtree item for that database, we will just hide it.
+ // Later, when plugin is loaded, item might become visible.
+ item = DbTreeItemFactory::createDb(group->referencedDbName, this);
+ item->setDb(group->referencedDbName);
+
+ db = DBLIST->getByName(group->referencedDbName);
+ if (db && dbList)
+ dbList->removeOne(db);
+ }
+
+ if (!parent)
+ parent = invisibleRootItem();
+
+ parent->appendRow(item);
+
+ if (item->getType() == DbTreeItem::Type::DIR)
+ {
+ foreach (const Config::DbGroupPtr& childGroup, group->childs)
+ restoreGroup(childGroup, dbList, item);
+ }
+
+ if (group->open)
+ {
+ if (db)
+ {
+ if (db->open())
+ treeView->expand(item->index());
+ }
+ else
+ {
+ treeView->expand(item->index());
+ }
+ }
+}
+
+void DbTreeModel::expanded(const QModelIndex &index)
+{
+ QStandardItem* item = itemFromIndex(index);
+ if (!item->hasChildren())
+ {
+ treeView->collapse(index);
+ return;
+ }
+
+ if (dynamic_cast<DbTreeItem*>(item)->getType() == DbTreeItem::Type::DIR)
+ itemFromIndex(index)->setIcon(ICONS.DIRECTORY_OPEN);
+}
+
+void DbTreeModel::collapsed(const QModelIndex &index)
+{
+ QStandardItem* item = itemFromIndex(index);
+ if (dynamic_cast<DbTreeItem*>(item)->getType() == DbTreeItem::Type::DIR)
+ item->setIcon(ICONS.DIRECTORY_OPEN);
+}
+
+void DbTreeModel::dbAdded(Db* db)
+{
+ DbTreeItem* item = DbTreeItemFactory::createDb(db->getName(), this);
+ item->setDb(db);
+ root()->appendRow(item);
+}
+
+void DbTreeModel::dbUpdated(const QString& oldName, Db* db)
+{
+ DbTreeItem* item = dynamic_cast<DbTreeItem*>(findItem(DbTreeItem::Type::DB, oldName));
+ if (!item)
+ {
+ qWarning() << "Updated database in db model that couldn't be found in the model:" << oldName;
+ return;
+ }
+
+ item->setText(db->getName());
+ item->setDb(db->getName());
+}
+
+void DbTreeModel::dbRemoved(Db* db)
+{
+ dbRemoved(db->getName());
+}
+
+void DbTreeModel::dbRemoved(const QString& name)
+{
+ QStandardItem* item = findItem(DbTreeItem::Type::DB, name);
+ if (!item)
+ {
+ qWarning() << "Removed database from db model that couldn't be found in the model:" << name;
+ return;
+ }
+ dbRemoved(item);
+}
+
+void DbTreeModel::dbRemoved(QStandardItem* item)
+{
+ QStandardItem* parent = item->parent();
+ if (!parent)
+ parent = root();
+
+ parent->removeRow(item->index().row());
+ if (!parent->hasChildren())
+ treeView->collapse(parent->index());
+}
+
+void DbTreeModel::interrupt()
+{
+ dbOrganizer->interrupt();
+}
+
+void DbTreeModel::refreshSchema(Db* db)
+{
+ QStandardItem* item = findItem(DbTreeItem::Type::DB, db);
+ if (!item)
+ {
+ qWarning() << "Refreshing schema of db that couldn't be found in the model:" << db->getName();
+ return;
+ }
+ refreshSchema(db, item);
+ applyFilter(item, currentFilter);
+}
+
+QList<DbTreeItem*> DbTreeModel::getAllItemsAsFlatList() const
+{
+ return getChildsAsFlatList(root());
+}
+
+QList<DbTreeItem*> DbTreeModel::getChildsAsFlatList(QStandardItem* item) const
+{
+ QList<DbTreeItem*> items;
+ QStandardItem* child = nullptr;
+ for (int i = 0; i < item->rowCount(); i++)
+ {
+ child = item->child(i);
+ items << dynamic_cast<DbTreeItem*>(child);
+ items += getChildsAsFlatList(child);
+ }
+ return items;
+}
+
+QVariant DbTreeModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QStandardItemModel::data(index, role);;
+
+ DbTreeItem* item = dynamic_cast<DbTreeItem*>(itemFromIndex(index));
+ switch (role)
+ {
+ case Qt::ToolTipRole:
+ {
+ return getToolTip(item);
+ }
+ }
+ return QStandardItemModel::data(index, role);
+}
+
+QString DbTreeModel::getToolTip(DbTreeItem* item) const
+{
+ if (!item)
+ return QString::null;
+
+ switch (item->getType())
+ {
+ case DbTreeItem::Type::DB:
+ return getDbToolTip(item);
+ case DbTreeItem::Type::TABLE:
+ return getTableToolTip(item);
+ default:
+ break;
+ }
+ return QString::null;
+}
+
+QString DbTreeModel::getDbToolTip(DbTreeItem* item) const
+{
+ QStringList rows;
+
+ Db* db = item->getDb();
+ QFile dbFile(db->getPath());
+ QString iconPath = db->isValid() ? ICONS.DATABASE.toImgSrc() : ICONS.DATABASE_INVALID.toImgSrc();
+
+ rows << toolTipHdrRowTmp.arg(iconPath).arg(tr("Database: %1", "dbtree tooltip").arg(db->getName()));
+ rows << toolTipRowTmp.arg("URI:").arg(db->getPath());
+
+ if (db->isValid())
+ {
+ rows << toolTipRowTmp.arg(tr("Version:", "dbtree tooltip")).arg(QString("SQLite %1").arg(db->getVersion()));
+ rows << toolTipRowTmp.arg(tr("File size:", "dbtree tooltip")).arg(formatFileSize(dbFile.size()));
+ rows << toolTipRowTmp.arg(tr("Encoding:", "dbtree tooltip")).arg(db->getEncoding());
+ }
+ else
+ {
+ InvalidDb* idb = dynamic_cast<InvalidDb*>(db);
+ rows << toolTipRowTmp.arg(tr("Error details:", "dbtree tooltip")).arg(idb->getError());
+ }
+
+ return toolTipTableTmp.arg(rows.join(""));
+}
+
+QString DbTreeModel::getTableToolTip(DbTreeItem* item) const
+{
+ QStringList rows;
+
+ rows << toolTipHdrRowTmp.arg(ICONS.TABLE.getPath()).arg(tr("Table : %1", "dbtree tooltip").arg(item->text()));
+
+ QStandardItem* columnsItem = item->child(0);
+ QStandardItem* indexesItem = item->child(1);
+ QStandardItem* triggersItem = item->child(2);
+
+ int columnCnt = columnsItem->rowCount();
+ int indexesCount = indexesItem->rowCount();
+ int triggersCount = triggersItem->rowCount();
+
+ QStringList columns;
+ for (int i = 0; i < columnCnt; i++)
+ columns << columnsItem->child(i)->text();
+
+ QStringList indexes;
+ for (int i = 0; i < indexesCount; i++)
+ indexes << indexesItem->child(i)->text();
+
+ QStringList triggers;
+ for (int i = 0; i < triggersCount; i++)
+ triggers << triggersItem->child(i)->text();
+
+ rows << toolTipIconRowTmp.arg(ICONS.COLUMN.getPath())
+ .arg(tr("Columns (%1):", "dbtree tooltip").arg(columnCnt))
+ .arg(columns.join(", "));
+ rows << toolTipIconRowTmp.arg(ICONS.INDEX.getPath())
+ .arg(tr("Indexes (%1):", "dbtree tooltip").arg(indexesCount))
+ .arg(indexes.join(", "));
+ rows << toolTipIconRowTmp.arg(ICONS.TRIGGER.getPath())
+ .arg(tr("Triggers (%1):", "dbtree tooltip").arg(triggersCount))
+ .arg(triggers.join(", "));
+
+ return toolTipTableTmp.arg(rows.join(""));
+}
+
+void DbTreeModel::refreshSchema(Db* db, QStandardItem *item)
+{
+ if (!db->isOpen())
+ return;
+
+ // Remember expanded state of this branch
+ QHash<QString, bool> expandedState;
+ collectExpandedState(expandedState, item);
+
+ // Delete child nodes
+ while (item->rowCount() > 0)
+ item->removeRow(0);
+
+ // Now prepare to create new branch
+ SchemaResolver resolver(db);
+ resolver.setIgnoreSystemObjects(!CFG_UI.General.ShowSystemObjects.get());
+
+ // Collect all db objects and build the db branch
+ bool sort = CFG_UI.General.SortObjects.get();
+ QStringList tables = resolver.getTables();
+ QStringList virtualTables;
+ for (const QString& table : tables)
+ {
+ if (resolver.isVirtualTable(table))
+ virtualTables << table;
+ }
+
+ QList<QStandardItem*> tableItems = refreshSchemaTables(tables, virtualTables, sort);
+ StrHash<QList<QStandardItem*>> allTableColumns = refreshSchemaTableColumns(resolver.getAllTableColumns());
+ StrHash<QList<QStandardItem*>> indexItems = refreshSchemaIndexes(resolver.getGroupedIndexes(), sort);
+ StrHash<QList<QStandardItem*>> triggerItems = refreshSchemaTriggers(resolver.getGroupedTriggers(), sort);
+ QList<QStandardItem*> viewItems = refreshSchemaViews(resolver.getViews(), sort);
+ refreshSchemaBuild(item, tableItems, indexItems, triggerItems, viewItems, allTableColumns);
+ populateChildItemsWithDb(item, db);
+ restoreExpandedState(expandedState, item);
+}
+
+void DbTreeModel::collectExpandedState(QHash<QString, bool> &state, QStandardItem *parentItem)
+{
+ if (!parentItem)
+ parentItem = root();
+
+ DbTreeItem* dbTreeItem = dynamic_cast<DbTreeItem*>(parentItem);
+ if (dbTreeItem)
+ state[dbTreeItem->signature()] = treeView->isExpanded(dbTreeItem->index());
+
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ collectExpandedState(state, parentItem->child(i));
+}
+
+QList<QStandardItem *> DbTreeModel::refreshSchemaTables(const QStringList &tables, const QStringList& virtualTables, bool sort)
+{
+ QStringList sortedTables = tables;
+ if (sort)
+ qSort(sortedTables);
+
+ QList<QStandardItem *> items;
+ foreach (const QString& table, sortedTables)
+ {
+ if (virtualTables.contains(table))
+ items += DbTreeItemFactory::createVirtualTable(table, this);
+ else
+ items += DbTreeItemFactory::createTable(table, this);
+ }
+
+ return items;
+}
+
+StrHash<QList<QStandardItem*>> DbTreeModel::refreshSchemaTableColumns(const StrHash<QStringList> &columns)
+{
+ QStringList sortedColumns;
+ bool sort = CFG_UI.General.SortColumns.get();
+ StrHash<QList<QStandardItem*>> items;
+ for (const QString& key : columns.keys())
+ {
+ sortedColumns = columns[key];
+ if (sort)
+ qSort(sortedColumns);
+
+ for (const QString& column : sortedColumns)
+ items[key] += DbTreeItemFactory::createColumn(column, this);
+ }
+ return items;
+}
+
+StrHash<QList<QStandardItem *> > DbTreeModel::refreshSchemaIndexes(const StrHash<QStringList> &indexes, bool sort)
+{
+ StrHash<QList<QStandardItem *> > items;
+ QStringList sortedIndexes;
+ for (const QString& key : indexes.keys())
+ {
+ sortedIndexes = indexes[key];
+ if (sort)
+ qSort(sortedIndexes);
+
+ for (const QString& index : sortedIndexes)
+ items[key] += DbTreeItemFactory::createIndex(index, this);
+ }
+ return items;
+}
+
+StrHash<QList<QStandardItem*>> DbTreeModel::refreshSchemaTriggers(const StrHash<QStringList> &triggers, bool sort)
+{
+ StrHash<QList<QStandardItem*>> items;
+ QStringList sortedTriggers;
+ for (const QString& key : triggers.keys())
+ {
+ sortedTriggers = triggers[key];
+ if (sort)
+ qSort(sortedTriggers);
+
+ for (const QString& trigger : sortedTriggers)
+ items[key] += DbTreeItemFactory::createTrigger(trigger, this);
+ }
+ return items;
+}
+
+QList<QStandardItem *> DbTreeModel::refreshSchemaViews(const QStringList &views, bool sort)
+{
+ QStringList sortedViews = views;
+ if (sort)
+ qSort(sortedViews);
+
+ QList<QStandardItem *> items;
+ foreach (const QString& view, views)
+ items += DbTreeItemFactory::createView(view, this);
+
+ return items;
+}
+
+void DbTreeModel::populateChildItemsWithDb(QStandardItem *parentItem, Db* db)
+{
+ QStandardItem* childItem = nullptr;
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ {
+ childItem = parentItem->child(i);
+ dynamic_cast<DbTreeItem*>(childItem)->setDb(db);
+ populateChildItemsWithDb(childItem, db);
+ }
+}
+
+void DbTreeModel::refreshSchemaBuild(QStandardItem *dbItem,
+ QList<QStandardItem*> tables,
+ StrHash<QList<QStandardItem*> > indexes,
+ StrHash<QList<QStandardItem*> > triggers,
+ QList<QStandardItem*> views,
+ StrHash<QList<QStandardItem*> > allTableColumns)
+{
+ DbTreeItem* tablesItem = DbTreeItemFactory::createTables(this);
+ DbTreeItem* viewsItem = DbTreeItemFactory::createViews(this);
+
+ dbItem->appendRow(tablesItem);
+ dbItem->appendRow(viewsItem);
+
+ DbTreeItem* columnsItem = nullptr;
+ DbTreeItem* indexesItem = nullptr;
+ DbTreeItem* triggersItem = nullptr;
+ foreach (QStandardItem* tableItem, tables)
+ {
+ tablesItem->appendRow(tableItem);
+
+ columnsItem = DbTreeItemFactory::createColumns(this);
+ indexesItem = DbTreeItemFactory::createIndexes(this);
+ triggersItem = DbTreeItemFactory::createTriggers(this);
+
+ tableItem->appendRow(columnsItem);
+ tableItem->appendRow(indexesItem);
+ tableItem->appendRow(triggersItem);
+
+ foreach (QStandardItem* columnItem, allTableColumns[tableItem->text()])
+ columnsItem->appendRow(columnItem);
+
+ foreach (QStandardItem* indexItem, indexes[tableItem->text()])
+ indexesItem->appendRow(indexItem);
+
+ foreach (QStandardItem* triggerItem, triggers[tableItem->text()])
+ triggersItem->appendRow(triggerItem);
+ }
+ foreach (QStandardItem* viewItem, views)
+ {
+ viewsItem->appendRow(viewItem);
+
+ triggersItem = DbTreeItemFactory::createTriggers(this);
+ viewItem->appendRow(triggersItem);
+ foreach (QStandardItem* triggerItem, triggers[viewItem->text()])
+ triggersItem->appendRow(triggerItem);
+ }
+}
+
+void DbTreeModel::restoreExpandedState(const QHash<QString, bool>& expandedState, QStandardItem* parentItem)
+{
+ DbTreeItem* parentDbTreeItem = dynamic_cast<DbTreeItem*>(parentItem);
+ QString sig = parentDbTreeItem->signature();
+ if (expandedState.contains(sig) && expandedState[sig])
+ treeView->expand(parentItem->index());
+
+ foreach (QStandardItem* child, parentDbTreeItem->childs())
+ restoreExpandedState(expandedState, child);
+}
+
+void DbTreeModel::dbConnected(Db* db)
+{
+ QStandardItem* item = findItem(DbTreeItem::Type::DB, db);
+ if (!item)
+ {
+ qWarning() << "Connected to db that couldn't be found in the model:" << db->getName();
+ return;
+ }
+ refreshSchema(db, item);
+ treeView->expand(item->index());
+ if (CFG_UI.General.ExpandTables.get())
+ treeView->expand(item->index().child(0, 0)); // also expand tables
+
+ if (CFG_UI.General.ExpandViews.get())
+ treeView->expand(item->index().child(1, 0)); // also expand views
+}
+
+void DbTreeModel::dbDisconnected(Db* db)
+{
+ QStandardItem* item = findItem(DbTreeItem::Type::DB, db);
+ if (!item)
+ {
+ qWarning() << "Disconnected from db that couldn't be found in the model:" << db->getName();
+ return;
+ }
+
+ while (item->rowCount() > 0)
+ item->removeRow(0);
+
+ treeView->collapse(item->index());
+}
+
+void DbTreeModel::dbUnloaded(Db* db)
+{
+ DbTreeItem* item = findItem(DbTreeItem::Type::DB, db->getName());
+ if (!item)
+ {
+ qCritical() << "No DB item found to update icon:" << db->getName();
+ return;
+ }
+ item->updateDbIcon();
+}
+
+void DbTreeModel::dbLoaded(Db* db)
+{
+ if (ignoreDbLoadedSignal)
+ return;
+
+ DbTreeItem* item = findItem(DbTreeItem::Type::DB, db->getName());
+ if (!item)
+ {
+ qCritical() << "No DB item found to update icon:" << db->getName();
+ return;
+ }
+ item->updateDbIcon();
+}
+
+void DbTreeModel::massSaveBegins()
+{
+ requireSchemaReloading = false;
+}
+
+void DbTreeModel::massSaveCommited()
+{
+ if (requireSchemaReloading)
+ {
+ for (Db* db : DBLIST->getDbList())
+ {
+ if (db->isOpen())
+ refreshSchema(db);
+ }
+ }
+}
+
+void DbTreeModel::markSchemaReloadingRequired()
+{
+ requireSchemaReloading = true;
+}
+
+DbTreeItem* DbTreeModel::findItem(DbTreeItem::Type type, const QString &name)
+{
+ return findItem(root(), type, name);
+}
+
+DbTreeItem *DbTreeModel::findItem(QStandardItem* parentItem, DbTreeItem::Type type, const QString& name)
+{
+ DbTreeItem* item = nullptr;
+ DbTreeItem* subItem = nullptr;
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ {
+ item = dynamic_cast<DbTreeItem*>(parentItem->child(i));
+
+ // Search recursively
+ if (item->hasChildren())
+ {
+ subItem = findItem(item, type, name);
+ if (subItem)
+ return subItem;
+ }
+
+ if (item->getType() != type)
+ continue;
+
+ if (item->text() != name)
+ continue;
+
+ return item;
+ }
+
+ return nullptr;
+}
+
+DbTreeItem *DbTreeModel::findItem(DbTreeItem::Type type, Db* db)
+{
+ return findItem(root(), type, db);
+}
+
+DbTreeItem *DbTreeModel::findItemBySignature(const QString &signature)
+{
+ QStringList parts = signature.split("_");
+ QStringList pair;
+ DbTreeItem* currItem = nullptr;
+ DbTreeItem::Type type;
+ QString name;
+ for (const QString& part : parts)
+ {
+ pair = part.split(".");
+ type = static_cast<DbTreeItem::Type>(pair.first().toInt());
+ name = QString::fromUtf8(QByteArray::fromBase64(pair.last().toLatin1()));
+ currItem = findItem((currItem ? currItem : root()), type, name);
+ if (!currItem)
+ return nullptr; // not found the target item
+ }
+ return currItem;
+}
+
+QList<DbTreeItem*> DbTreeModel::findItems(DbTreeItem::Type type)
+{
+ return findItems(root(), type);
+}
+
+DbTreeItem *DbTreeModel::findItem(QStandardItem* parentItem, DbTreeItem::Type type, Db* db)
+{
+ DbTreeItem* item = nullptr;
+ DbTreeItem* subItem = nullptr;
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ {
+ item = dynamic_cast<DbTreeItem*>(parentItem->child(i));
+
+ // Search recursively
+ if (item->hasChildren())
+ {
+ subItem = findItem(item, type, db);
+ if (subItem)
+ return subItem;
+ }
+
+ if (item->getType() != type)
+ continue;
+
+ if (item->text() != db->getName())
+ continue;
+
+ return item;
+ }
+
+ return nullptr;
+}
+
+QList<DbTreeItem*> DbTreeModel::findItems(QStandardItem* parentItem, DbTreeItem::Type type)
+{
+ QList<DbTreeItem*> items;
+ DbTreeItem* item = nullptr;
+ for (int i = 0; i < parentItem->rowCount(); i++)
+ {
+ item = dynamic_cast<DbTreeItem*>(parentItem->child(i));
+
+ // Search recursively
+ if (item->getType() == DbTreeItem::Type::DIR)
+ items += findItems(item, type);
+
+ if (item->getType() != type)
+ continue;
+
+ items += item;
+ }
+
+ return items;
+}
+
+QStandardItem* DbTreeModel::root() const
+{
+ return invisibleRootItem();
+}
+
+void DbTreeModel::loadDbList()
+{
+ clear();
+ readGroups(DBLIST->getDbList());
+}
+
+void DbTreeModel::itemChangedVisibility(DbTreeItem* item)
+{
+ emit updateItemHidden(item);
+}
+
+void DbTreeModel::setTreeView(DbTreeView *value)
+{
+ treeView = value;
+ connect(treeView, &QTreeView::expanded, this, &DbTreeModel::expanded);
+ connect(treeView, &QTreeView::collapsed, this, &DbTreeModel::collapsed);
+ connect(this, SIGNAL(updateItemHidden(DbTreeItem*)), treeView, SLOT(updateItemHidden(DbTreeItem*)));
+}
+
+QStringList DbTreeModel::mimeTypes() const
+{
+ QStringList types = QStandardItemModel::mimeTypes();
+ types << MIMETYPE;
+ return types;
+}
+
+QMimeData *DbTreeModel::mimeData(const QModelIndexList &indexes) const
+{
+ QMimeData *data = QStandardItemModel::mimeData(indexes);
+ if (!data)
+ return nullptr;
+
+ if (indexes.size() == 0)
+ return nullptr;
+
+ QByteArray output;
+ QDataStream stream(&output, QIODevice::WriteOnly);
+
+ QList<QUrl> urlList;
+ QStringList textList;
+
+ DbTreeItem* item = nullptr;
+ stream << reinterpret_cast<qint32>(indexes.size());
+ for (const QModelIndex& idx : indexes)
+ {
+ item = dynamic_cast<DbTreeItem*>(itemFromIndex(idx));
+ stream << item->signature();
+
+ textList << item->text();
+ if (item->getType() == DbTreeItem::Type::DB)
+ urlList << QUrl("file://"+item->getDb()->getPath());
+ }
+ data->setData(MIMETYPE, output);
+ data->setText(textList.join("\n"));
+ data->setUrls(urlList);
+
+ return data;
+}
+
+bool DbTreeModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
+{
+ UNUSED(action);
+ // The result means: do we want the old item to be removed from the tree?
+ bool invokeStdAction = false;
+ bool res = pasteData(data, row, column, parent, Qt::IgnoreAction, &invokeStdAction);
+ if (!invokeStdAction)
+ return res;
+
+ return QStandardItemModel::dropMimeData(data, action, row, column, parent);
+}
+
+bool DbTreeModel::pasteData(const QMimeData* data, int row, int column, const QModelIndex& parent, Qt::DropAction defaultAction, bool* invokeStdAction)
+{
+ // The result means: do we want the old item to be removed from the tree?
+ DbTreeItem* dstItem = nullptr;
+ if (parent.isValid())
+ {
+ QModelIndex idx = parent.child(row, column);
+ if (idx.isValid())
+ dstItem = dynamic_cast<DbTreeItem*>(itemFromIndex(idx));
+ else // drop on top of the parent
+ dstItem = dynamic_cast<DbTreeItem*>(itemFromIndex(parent));
+ }
+ else
+ {
+ dstItem = dynamic_cast<DbTreeItem*>(item(row, column));
+ }
+
+ if (data->formats().contains(MIMETYPE))
+ return dropDbTreeItem(getDragItems(data), dstItem, defaultAction, *invokeStdAction);
+ else if (data->hasUrls())
+ return dropUrls(data->urls());
+ else
+ return false;
+}
+
+void DbTreeModel::interruptableStarted(Interruptable* obj)
+{
+ if (interruptables.size() == 0)
+ treeView->getDbTree()->showWidgetCover();
+
+ interruptables << obj;
+}
+
+void DbTreeModel::interruptableFinished(Interruptable* obj)
+{
+ interruptables.removeOne(obj);
+ if (interruptables.size() == 0)
+ treeView->getDbTree()->hideWidgetCover();
+}
+
+QList<DbTreeItem*> DbTreeModel::getDragItems(const QMimeData* data)
+{
+ QList<DbTreeItem*> items;
+ QByteArray byteData = data->data(MIMETYPE);
+ QDataStream stream(&byteData, QIODevice::ReadOnly);
+
+ qint32 itemCount;
+ stream >> itemCount;
+
+ DbTreeItem* item = nullptr;
+ QString signature;
+ for (qint32 i = 0; i < itemCount; i++)
+ {
+ stream >> signature;
+ item = findItemBySignature(signature);
+ if (item)
+ items << item;
+ }
+
+ return items;
+}
+
+QList<DbTreeItem*> DbTreeModel::getItemsForIndexes(const QModelIndexList& indexes) const
+{
+ QList<DbTreeItem*> items;
+ for (const QModelIndex& idx : indexes)
+ {
+ if (idx.isValid())
+ items << dynamic_cast<DbTreeItem*>(itemFromIndex(idx));
+ }
+
+ return items;
+}
+
+void DbTreeModel::staticInit()
+{
+}
+
+bool DbTreeModel::dropDbTreeItem(const QList<DbTreeItem*>& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction, bool& invokeStdDropAction)
+{
+ // The result means: do we want the old item to be removed from the tree?
+ if (srcItems.size() == 0)
+ return false;
+
+ DbTreeItem* srcItem = srcItems.first();
+ switch (srcItem->getType())
+ {
+ case DbTreeItem::Type::TABLE:
+ case DbTreeItem::Type::VIEW:
+ {
+ if (!dstItem)
+ return false;
+
+ if (srcItem->getDb() == dstItem->getDb())
+ return true;
+
+ return dropDbObjectItem(srcItems, dstItem, defaultAction);
+ }
+ case DbTreeItem::Type::DB:
+ case DbTreeItem::Type::DIR:
+ invokeStdDropAction = true;
+ break;
+ case DbTreeItem::Type::COLUMN:
+ case DbTreeItem::Type::TABLES:
+ case DbTreeItem::Type::INDEXES:
+ case DbTreeItem::Type::INDEX:
+ case DbTreeItem::Type::TRIGGERS:
+ case DbTreeItem::Type::TRIGGER:
+ case DbTreeItem::Type::VIEWS:
+ case DbTreeItem::Type::COLUMNS:
+ case DbTreeItem::Type::VIRTUAL_TABLE:
+ case DbTreeItem::Type::ITEM_PROTOTYPE:
+ break;
+ }
+
+ return false;
+}
+
+bool DbTreeModel::dropDbObjectItem(const QList<DbTreeItem*>& srcItems, DbTreeItem* dstItem, Qt::DropAction defaultAction)
+{
+ bool copy = false;
+ bool move = false;
+ bool includeData = false;
+ bool includeIndexes = false;
+ bool includeTriggers = false;
+
+ if (defaultAction == Qt::CopyAction)
+ {
+ copy = true;
+ includeData = true;
+ includeIndexes = true;
+ includeTriggers = true;
+ }
+ else if (defaultAction == Qt::MoveAction)
+ {
+ move = true;
+ includeData = true;
+ includeIndexes = true;
+ includeTriggers = true;
+ }
+ else
+ {
+ QMenu menu;
+ QAction* copyAction = menu.addAction(ICONS.ACT_COPY, tr("Copy"));
+ QAction* moveAction = menu.addAction(ICONS.ACT_CUT, tr("Move"));
+ menu.addSeparator();
+ QCheckBox *includeDataCheck = createCopyOrMoveMenuCheckBox(&menu, tr("Include data"));
+ QCheckBox *includeIndexesCheck = createCopyOrMoveMenuCheckBox(&menu, tr("Include indexes"));
+ QCheckBox *includeTriggersCheck = createCopyOrMoveMenuCheckBox(&menu, tr("Include triggers"));
+ menu.addSeparator();
+ menu.addAction(ICONS.ACT_ABORT, tr("Abort"));
+
+ connect(moveAction, &QAction::triggered, [&move]() {move = true;});
+ connect(copyAction, &QAction::triggered, [&copy]() {copy = true;});
+
+ menu.exec(treeView->mapToGlobal(treeView->getLastDropPosition()));
+
+ includeData = includeDataCheck->isChecked();
+ includeIndexes = includeIndexesCheck->isChecked();
+ includeTriggers = includeTriggersCheck->isChecked();
+ }
+
+ // The result means: do we want the old item to be removed from the tree?
+ if (!copy && !move)
+ return false;
+
+ moveOrCopyDbObjects(srcItems, dstItem, move, includeData, includeIndexes, includeTriggers);
+ return move;
+}
+
+QCheckBox* DbTreeModel::createCopyOrMoveMenuCheckBox(QMenu* menu, const QString& label)
+{
+ QWidget* parentWidget = new QWidget(menu);
+ parentWidget->setLayout(new QVBoxLayout());
+ QMargins margins = parentWidget->layout()->contentsMargins();
+ parentWidget->layout()->setContentsMargins(margins.left(), 0, margins.right(), 0);
+
+ QCheckBox *cb = new QCheckBox(label);
+ cb->setChecked(true);
+ parentWidget->layout()->addWidget(cb);
+
+ QWidgetAction *action = new QWidgetAction(menu);
+ action->setDefaultWidget(parentWidget);
+ menu->addAction(action);
+ return cb;
+}
+
+bool DbTreeModel::dropUrls(const QList<QUrl>& urls)
+{
+ for (const QUrl& url : urls)
+ {
+ if (!url.isLocalFile())
+ {
+ qDebug() << url.toString() + "skipped, not a local file.";
+ continue;
+ }
+
+ DbDialog dialog(DbDialog::ADD, MAINWINDOW);
+ dialog.setPath(url.toLocalFile());
+ dialog.exec();
+ }
+ return false;
+}
+
+void DbTreeModel::moveOrCopyDbObjects(const QList<DbTreeItem*>& srcItems, DbTreeItem* dstItem, bool move, bool includeData, bool includeIndexes, bool includeTriggers)
+{
+ if (srcItems.size() == 0)
+ return;
+
+ DbTreeItem* srcItem = srcItems.first();
+ Db* srcDb = srcItem->getDb();
+ Db* dstDb = dstItem->getDb();
+
+ QStringList srcNames;
+ for (DbTreeItem* item : srcItems)
+ srcNames << item->text();
+
+ interruptableStarted(dbOrganizer);
+ if (move)
+ dbOrganizer->moveObjectsToDb(srcDb, srcNames, dstDb, includeData, includeIndexes, includeTriggers);
+ else
+ dbOrganizer->copyObjectsToDb(srcDb, srcNames, dstDb, includeData, includeIndexes, includeTriggers);
+}
+
+bool DbTreeModel::confirmReferencedTables(const QStringList& tables)
+{
+ QMessageBox::StandardButton result = QMessageBox::question(MAINWINDOW, tr("Referenced tables"),
+ tr("Do you want to include following referenced tables as well:\n%1").arg(tables.join(", ")));
+
+ return result == QMessageBox::Yes;
+}
+
+bool DbTreeModel::resolveNameConflict(QString& nameInConflict)
+{
+ bool ok = false;
+ QInputDialog tmpDialog; // just for a cancel button text
+ QString result = QInputDialog::getText(MAINWINDOW, tr("Name conflict"),
+ tr("Following object already exists in the target database.\nPlease enter new, unique name, or "
+ "press '%1' to abort the operation:").arg(tmpDialog.cancelButtonText()),
+ QLineEdit::Normal, nameInConflict, &ok);
+
+ if (ok)
+ nameInConflict = result;
+
+ return ok;
+}
+
+bool DbTreeModel::confirmConversion(const QList<QPair<QString, QString> >& diffs)
+{
+ VersionConvertSummaryDialog dialog(MAINWINDOW);
+ dialog.setWindowTitle(tr("SQL statements conversion"));
+ dialog.setSides(diffs);
+ return dialog.exec() == QDialog::Accepted;
+}
+
+bool DbTreeModel::confirmConversionErrors(const QHash<QString,QSet<QString>>& errors)
+{
+ ErrorsConfirmDialog dialog(MAINWINDOW);
+ dialog.setTopLabel(tr("Following error occurred while converting SQL statements to the target SQLite version:"));
+ dialog.setBottomLabel(tr("Would you like to ignore those errors and proceed?"));
+ dialog.setErrors(errors);
+ return dialog.exec() == QDialog::Accepted;
+}
+bool DbTreeModel::getIgnoreDbLoadedSignal() const
+{
+ return ignoreDbLoadedSignal;
+}
+
+void DbTreeModel::setIgnoreDbLoadedSignal(bool value)
+{
+ ignoreDbLoadedSignal = value;
+}
+
+bool DbTreeModel::hasDbTreeItem(const QMimeData *data)
+{
+ return data->formats().contains(MIMETYPE);
+}
+
+void DbTreeModel::dbObjectsMoveFinished(bool success, Db* srcDb, Db* dstDb)
+{
+ if (!success)
+ {
+ interruptableFinished(dbOrganizer);
+ return;
+ }
+
+ DBTREE->refreshSchema(srcDb);
+ DBTREE->refreshSchema(dstDb);
+ interruptableFinished(dbOrganizer);
+}
+
+void DbTreeModel::dbObjectsCopyFinished(bool success, Db* srcDb, Db* dstDb)
+{
+ dbObjectsMoveFinished(success, srcDb, dstDb);
+}