diff options
author | Arno <am@disconnect.de> | 2012-02-24 20:35:27 +0100 |
---|---|---|
committer | Arno <am@disconnect.de> | 2012-02-24 20:35:27 +0100 |
commit | b8e16c3bddb706ecc195e86eaafb89ed90f9bfc3 (patch) | |
tree | b14578ba8f801f701777b73d3dbb22730570585a | |
parent | e82af6117dfcf4ccbebb712caaf1b8f9c68599ba (diff) | |
download | SheMov-b8e16c3bddb706ecc195e86eaafb89ed90f9bfc3.tar.gz SheMov-b8e16c3bddb706ecc195e86eaafb89ed90f9bfc3.tar.bz2 SheMov-b8e16c3bddb706ecc195e86eaafb89ed90f9bfc3.zip |
Implement MappingTreeWidget
This is a rather large commit. It implements MappingTreeWidget using
MappingTreeModel unsurprisingly this uncovered some exciting bugs.
Fixes the following bugs in MappingTreeModel:
* use insertRows() and removeRows() when addings children, because
dataChanged() won't do it.
* don't use a prepared QSqlQuery when fetching children recursively.
This won't work because the query is still active when we invoke
ourselves again. Put the query on the stack instead
* Keep the model sorted.
Also add an entry for a MappingTreeEditor to the File-Menu.
-rw-r--r-- | mappingtreemodel.cpp | 78 | ||||
-rw-r--r-- | mappingtreemodel.h | 2 | ||||
-rw-r--r-- | mappingtreewidget.cpp | 199 | ||||
-rw-r--r-- | mappingtreewidget.h | 65 | ||||
-rw-r--r-- | shemov.cpp | 14 | ||||
-rw-r--r-- | shemov.h | 4 | ||||
-rw-r--r-- | shemov.pro | 6 | ||||
-rw-r--r-- | smglobals.cpp | 9 | ||||
-rw-r--r-- | smtreemodel.cpp | 5 |
9 files changed, 347 insertions, 35 deletions
diff --git a/mappingtreemodel.cpp b/mappingtreemodel.cpp index e48c608..78a5fb9 100644 --- a/mappingtreemodel.cpp +++ b/mappingtreemodel.cpp @@ -15,14 +15,12 @@ MappingTreeModel::MappingTreeModel(QStringList &headers, QObject *parent) : SmTr //init database mDb = QSqlDatabase::database("treedb"); mTypesQ = new QSqlQuery(mDb); - mTypesQ->prepare("SELECT imappings_type_id, tmappings_type_name FROM mapppings_type"); + mTypesQ->prepare("SELECT imappings_type_id, tmappings_type_name FROM mappings_type"); getMappingTypes(); //prepare Queries mTypeParentsQ = new QSqlQuery(mDb); - mTypeParentsQ->prepare("SELECT imapping_id, tmapping_name, tscreated FROM mappings WHERE imapping_id NOT IN (SELECT imapping_id FROM mappings_parents) AND imapping_type = :type"); - mChildrenQ = new QSqlQuery(mDb); - mChildrenQ->prepare("SELECT mappings.imapping_id, mappings.tmapping_name, mappings.tscreated FROM mappings, mappings_parents WHERE mappings_parents.iparent_id = :id AND mappings.imapping_id = mappings_parents.imapping_id"); + mTypeParentsQ->prepare("SELECT imapping_id, tmapping_name, tscreated FROM mappings WHERE imapping_id NOT IN (SELECT imapping_id FROM mappings_parents) AND imapping_type = :type ORDER BY tmapping_name"); mUpdateTypeQ = new QSqlQuery(mDb); mUpdateTypeQ->prepare("UPDATE mappings_type SET tmappings_type_name = :value WHERE imappings_type_id = :id"); mUpdateChildQ = new QSqlQuery(mDb); @@ -36,7 +34,7 @@ MappingTreeModel::MappingTreeModel(QStringList &headers, QObject *parent) : SmTr mSelectChildQ = new QSqlQuery(mDb); mSelectChildQ->prepare("SELECT imapping_id, tmapping_name, tscreated FROM mappings WHERE tmapping_name = :name AND imapping_type = :type"); mAddParentQ = new QSqlQuery(mDb); - mAddParentQ->prepare("INSERT INTO mappings_parents VALUES(:id, :parentid"); + mAddParentQ->prepare("INSERT INTO mappings_parents (imapping_id, iparent_id) VALUES(:id, :parentid)"); mDeleteChildQ = new QSqlQuery(mDb); mDeleteChildQ->prepare("DELETE FROM mappings WHERE imapping_id = :id"); } @@ -44,7 +42,6 @@ MappingTreeModel::MappingTreeModel(QStringList &headers, QObject *parent) : SmTr MappingTreeModel::~MappingTreeModel(){ delete mTypesQ; delete mTypeParentsQ; - delete mChildrenQ; delete mUpdateTypeQ; delete mUpdateChildQ; delete mAddMappingTypeQ; @@ -104,9 +101,9 @@ bool MappingTreeModel::setData(const QModelIndex &index, const QVariant &value, if(!index.isValid()){ return false; } - if(role == Qt::EditRole || role == NameRole){ + SmTreeItem *item = itemAt(index); + if(role == Qt::EditRole){ mDb.transaction(); - SmTreeItem *item = itemAt(index); QSqlQuery *q = 0; if(item == root()){ q = mUpdateTypeQ; @@ -123,6 +120,15 @@ bool MappingTreeModel::setData(const QModelIndex &index, const QVariant &value, } mDb.rollback(); } + if(role == NameRole){ + item->setData(Name, value); + } + if(role == IdRole){ + item->setData(Id, value); + } + if(role == AddedRole){ + item->setData(Added, value); + } return false; } @@ -160,15 +166,20 @@ bool MappingTreeModel::addChild(const QVariant &name, const QModelIndex &parent) if(mAddChildQ->exec()){ mSelectChildQ->bindValue(":name", name); mSelectChildQ->bindValue(":type", mType); + mSelectChildQ->exec(); while(mSelectChildQ->next()){ - QList<QVariant> newData; - newData << mSelectChildQ->value(1) << mSelectChildQ->value(0) << mSelectChildQ->value(2); - SmTreeItem *childItem = new SmTreeItem(newData, pItem); - pItem->appendChild(childItem); - mAddParentQ->bindValue(":id", childItem->data(Id)); - mAddParentQ->bindValue(":parentid", pItem->data(Id)); - mAddParentQ->exec(); - emit dataChanged(parent, parent); + int where = lowerBound(pItem, mSelectChildQ->value(1), Name); + if(insertRows(where, 1, parent)){ + QModelIndex newIdx = index(where, 0, parent); + setData(newIdx, mSelectChildQ->value(1), NameRole); + setData(newIdx, mSelectChildQ->value(0), IdRole); + setData(newIdx, mSelectChildQ->value(2), AddedRole); + } + if(pItem->parent() != root()){ + mAddParentQ->bindValue(":id", mSelectChildQ->value(0)); //Id + mAddParentQ->bindValue(":parentid", pItem->data(Id)); + mAddParentQ->exec(); + } return true; } } @@ -185,10 +196,7 @@ bool MappingTreeModel::deleteChild(const QModelIndex &idx){ } mDeleteChildQ->bindValue(":id", item->data(Id)); if(mDeleteChildQ->exec()){ - const QModelIndex &parent = idx.parent(); - SmTreeItem *pItem = itemAt(parent); - pItem->removeChild(item->row()); - emit dataChanged(parent, parent); + removeRows(idx.row(), 1, idx.parent()); return true; } return false; @@ -202,14 +210,16 @@ void MappingTreeModel::populate(){ mTypeParentsQ->bindValue(":type", mType); if(mTypeParentsQ->exec()){ SmTreeItem *rootItem = new SmTreeItem(NumFields); - rootItem->setData(Name, mappingTypeNameFromId(mType)); - rootItem->setData(Id, mType); + SmTreeItem *firstChild = new SmTreeItem(NumFields, rootItem); + firstChild->setData(Name, mappingTypeNameFromId(mType)); + firstChild->setData(Id, mType); + rootItem->appendChild(firstChild); //collect children recursive while(mTypeParentsQ->next()){ QList<QVariant> childData; childData << mTypeParentsQ->value(1) << mTypeParentsQ->value(0) << mTypeParentsQ->value(2); - SmTreeItem *childItem = new SmTreeItem(childData, rootItem); - rootItem->appendChild(childItem); + SmTreeItem *childItem = new SmTreeItem(childData, firstChild); + firstChild->appendChild(childItem); getChildrenRecursive(childItem); } setRoot(rootItem); @@ -231,14 +241,26 @@ void MappingTreeModel::getMappingTypes(){ } void MappingTreeModel::getChildrenRecursive(SmTreeItem *item){ - mChildrenQ->bindValue(":id", item->data(Id)); - if(mChildrenQ->exec()){ - while(mChildrenQ->next()){ + QSqlQuery cq(mDb); + cq.prepare("SELECT mappings.imapping_id, mappings.tmapping_name, mappings.tscreated FROM mappings, mappings_parents WHERE mappings_parents.iparent_id = :id AND mappings.imapping_id = mappings_parents.imapping_id ORDER BY mappings.tmapping_name"); + cq.bindValue(":id", item->data(Id)); + if(cq.exec()){ + while(cq.next()){ QList<QVariant> childData; - childData << mChildrenQ->value(1) << mChildrenQ->value(0) << mChildrenQ->value(2); + childData << cq.value(1) << cq.value(0) << cq.value(2); SmTreeItem *child = new SmTreeItem(childData, item); item->appendChild(child); getChildrenRecursive(child); } } } + +int MappingTreeModel::lowerBound(SmTreeItem *item, const QVariant &value, int column) const { + for(int i = 0; i < item->childCount(); ++i){ + SmTreeItem *child = item->child(i); + if(child->data(column).toString() > value.toString()){ + return i; + } + } + return item->childCount(); +} diff --git a/mappingtreemodel.h b/mappingtreemodel.h index a18ce57..451ab83 100644 --- a/mappingtreemodel.h +++ b/mappingtreemodel.h @@ -52,10 +52,10 @@ class MappingTreeModel : public SmTreeModel { }; void getMappingTypes(); void getChildrenRecursive(SmTreeItem *item); + int lowerBound(SmTreeItem *item, const QVariant &value, int column = 0) const; QSqlDatabase mDb; QSqlQuery *mTypesQ; QSqlQuery *mTypeParentsQ; - QSqlQuery *mChildrenQ; QSqlQuery *mUpdateTypeQ; QSqlQuery *mUpdateChildQ; QSqlQuery *mAddMappingTypeQ; diff --git a/mappingtreewidget.cpp b/mappingtreewidget.cpp new file mode 100644 index 0000000..e5b1f90 --- /dev/null +++ b/mappingtreewidget.cpp @@ -0,0 +1,199 @@ +/* + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version + 2 of the License, or (at your option) any later version. +*/ + +#include <QSortFilterProxyModel> +#include <QComboBox> +#include <QPushButton> +#include <QLineEdit> +#include <QVBoxLayout> +#include <QHBoxLayout> +#include <QInputDialog> +#include <QStringListModel> +#include <QMessageBox> +#include <QSettings> +#include <QHideEvent> +#include <QAction> +#include <QMenu> + +#include "mappingtreewidget.h" +#include "mappingtreemodel.h" +#include "smglobals.h" + +MappingTreeWidget::MappingTreeWidget(QWidget *parent) : QWidget(parent){ + //setup model + mModel = static_cast<MappingTreeModel*>(SmGlobals::instance()->model("MappingTree")); + mProxy = new QSortFilterProxyModel(this); + mProxy->setSourceModel(mModel); + mTypesModel = new QStringListModel(this); + mTypesModel->setStringList(mModel->mappingTypeNames()); + + //setup gui + mTree = new MappingTreeView; + mTree->setModel(mProxy); + mTree->setColumnHidden(1, true); + mTree->setColumnHidden(2, true); + mTree->setAlternatingRowColors(true); + mTree->expandAll(); + mTypeBox = new QComboBox; + mTypeBox->setModel(mTypesModel); + connect(mTypeBox, SIGNAL(currentIndexChanged(QString)), this, SLOT(typeChanged(QString))); + mAddType = new QPushButton(tr("Add &type")); + connect(mAddType, SIGNAL(clicked()), this, SLOT(addType())); + mDeleteType = new QPushButton(tr("Delete type")); + connect(mDeleteType, SIGNAL(clicked()), this, SLOT(deleteType())); + mEdit = new QLineEdit; + mAddChild = new QPushButton(tr("&Add")); + connect(mAddChild, SIGNAL(clicked()), this, SLOT(addChild())); + mDeleteChild = new QPushButton(tr("Delete")); + connect(mDeleteChild, SIGNAL(clicked()), this, SLOT(deleteChild())); + QHBoxLayout *typesButtonLayout = new QHBoxLayout; + typesButtonLayout->addWidget(mDeleteType); + typesButtonLayout->addWidget(mAddType); + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addStretch(); + buttonLayout->addWidget(mDeleteChild); + buttonLayout->addWidget(mAddChild); + + //select type and populate model + QSettings s; + QString lastType = s.value("ui/mappingtreetype").toString(); + if(lastType.isEmpty()){ + if(!mTypesModel->stringList().isEmpty()){ + lastType = mTypesModel->stringList().at(0); + } + } + int typeId = mModel->mappingTypeIdFromName(lastType); + if(typeId != -1){ + mModel->setType(typeId); + mTypeBox->setCurrentIndex(mTypeBox->findText(lastType)); + typeChanged(lastType); + } + + //setup actions + mDeleteChildA = new QAction(tr("Delete"), this); + connect(mDeleteChildA, SIGNAL(triggered()), this, SLOT(deleteChild())); + mTree->addAction(mDeleteChildA); + mEditChildA = new QAction(tr("Edit..."), this); + connect(mEditChildA, SIGNAL(triggered()), this, SLOT(editChild())); + mTree->addAction(mEditChildA); + + //widget layout and tab order + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(mTypeBox); + mainLayout->addLayout(typesButtonLayout); + mainLayout->addWidget(mTree); + mainLayout->addWidget(mEdit); + mainLayout->addLayout(buttonLayout); + setLayout(mainLayout); + mEdit->setFocus(); + setTabOrder(mEdit, mAddChild); +} + +void MappingTreeWidget::addChild(){ + QModelIndex sel = selected(); + if(!sel.isValid()){ + QMessageBox::critical(this, tr("Error"), tr("No parent item selected!")); + return; + } + QString value = mEdit->text().toLower().trimmed(); + if(value.isEmpty()){ + return; + } + QModelIndex real = mProxy->mapToSource(sel); + mModel->addChild(value, real); +} + +void MappingTreeWidget::addType(){ + QString typeName = QInputDialog::getText(this, tr("Enter type name"), tr("Name")).toLower().trimmed(); + if(typeName.isEmpty()){ + return; + } + if(mModel->addMappingType(typeName)){ + mTypesModel->setStringList(mModel->mappingTypeNames()); + int idxOf = mTypeBox->findText(typeName); + if(idxOf != -1){ + mTypeBox->setCurrentIndex(idxOf); + } + } +} + +void MappingTreeWidget::deleteChild(){ + QModelIndex sel = selected(); + if(!sel.isValid()){ + QMessageBox::critical(this, tr("Error"), tr("No item selected!")); + return; + } + QModelIndex real = mProxy->mapToSource(sel); + if(mModel->rowCount(real) > 0){ + QString msg = QString(tr("Cannot delete item %1, because it has %2 children!")).arg(real.data().toString()).arg(QString::number(mModel->rowCount(real))); + QMessageBox::critical(this, tr("Error"), msg); + return; + } + mModel->deleteChild(real); +} + +void MappingTreeWidget::deleteType(){ + QString curText = mTypeBox->currentText(); + if(curText.isEmpty()){ + return; + } + QString msg = QString(tr("Really delete %1?")).arg(curText); + int retval = QMessageBox::question(this, tr("Question"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + if(retval == QMessageBox::Yes){ + int typeId = mModel->mappingTypeIdFromName(curText); + if(typeId != -1){ + if(mModel->deleteMappingType(typeId)){ + mTypesModel->setStringList(mModel->mappingTypeNames()); + if(mTypesModel->stringList().count() > 0){ + mTypeBox->setCurrentIndex(0); + } + } + } + } +} + +void MappingTreeWidget::hideEvent(QHideEvent *event){ + QString type = mTypeBox->currentText(); + if(!type.isEmpty()){ + QSettings s; + s.setValue("ui/mappingtreetype", type); + } + event->accept(); +} + +void MappingTreeWidget::typeChanged(QString type){ + int typeId = mModel->mappingTypeIdFromName(type); + if(typeId != -1){ + mModel->setType(typeId); + mModel->populate(); + mTree->expandAll(); + } +} + +void MappingTreeWidget::editChild(){ + QModelIndex sel = selected(); + if(sel.isValid()){ + mTree->edit(sel); + } +} + +const QModelIndex MappingTreeWidget::selected() const{ + QModelIndexList sel = mTree->selectionModel()->selectedRows(); + if(sel.isEmpty()){ + return QModelIndex(); + } + return sel.first(); +} + + +MappingTreeView::MappingTreeView(QWidget *parent) : QTreeView(parent) {} + +void MappingTreeView::contextMenuEvent(QContextMenuEvent *e){ + QMenu ctxMenu(this); + ctxMenu.addActions(actions()); + ctxMenu.exec(e->globalPos()); +} diff --git a/mappingtreewidget.h b/mappingtreewidget.h new file mode 100644 index 0000000..bd34576 --- /dev/null +++ b/mappingtreewidget.h @@ -0,0 +1,65 @@ +/* + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version + 2 of the License, or (at your option) any later version. +*/ + +#ifndef MAPPINGTREEWIDGET_H +#define MAPPINGTREEWIDGET_H + +#include <QWidget> +#include <QTreeView> + +class MappingTreeView; +class MappingTreeModel; +class QComboBox; +class QPushButton; +class QSortFilterProxyModel; +class QStringListModel; +class QAction; + +class MappingTreeWidget : public QWidget { + Q_OBJECT + public: + explicit MappingTreeWidget(QWidget *parent = 0); + + public slots: + void addChild(); + void addType(); + void deleteChild(); + void deleteType(); + + protected: + virtual void hideEvent(QHideEvent *event); + + private slots: + void typeChanged(QString type); + void editChild(); + + private: + const QModelIndex selected() const; + MappingTreeView *mTree; + MappingTreeModel *mModel; + QSortFilterProxyModel *mProxy; + QStringListModel *mTypesModel; + QComboBox *mTypeBox; + QPushButton *mAddChild; + QPushButton *mDeleteChild; + QPushButton *mAddType; + QPushButton *mDeleteType; + QLineEdit *mEdit; + QAction *mDeleteChildA; + QAction *mEditChildA; +}; + +class MappingTreeView : public QTreeView { + Q_OBJECT + public: + MappingTreeView(QWidget *parent = 0); + + protected: + virtual void contextMenuEvent(QContextMenuEvent *e); +}; + +#endif // MAPPINGTREEWIDGET_H @@ -53,6 +53,7 @@ #include "mappingtableeditor.h" #include "mappingtablemodel.h" #include "dbanalyzer.h" +#include "mappingtreewidget.h" SheMov::SheMov(QWidget *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags), mOpenWithGroupFS(0), mOpenWithGroupAV(0) { //application icon @@ -328,6 +329,9 @@ void SheMov::createActions(){ connect(mConsistencyA, SIGNAL(triggered()), this, SLOT(checkConsistency())); mAnalyzerA = new QAction(QIcon(":/higheels.png"), tr("Analyze Db..."), this); connect(mAnalyzerA, SIGNAL(triggered()), this, SLOT(analyzeDb())); + mMappingEditorA = new QAction(tr("Mapping editor..."), this); + connect(mMappingEditorA, SIGNAL(triggered()), this, SLOT(mappingEditor())); + //connnect mQuitA = new QAction(tr("Quit"), this); mQuitA->setShortcut(tr("CTRL+q")); @@ -608,6 +612,7 @@ void SheMov::createMenus(){ fileMenu->addAction(mNewMovieWizardA); fileMenu->addAction(mConsistencyA); fileMenu->addAction(mAnalyzerA); + fileMenu->addAction(mMappingEditorA); //fileMenu->addAction(mShowNoCoverDialogA); fileMenu->addSeparator(); fileMenu->addAction(mQuitA); @@ -1015,3 +1020,12 @@ void SheMov::editMappings(QString table){ MappingTableEditor ed(table, this); ed.exec(); } + +void SheMov::mappingEditor(){ + QDialog dlg(this); + QHBoxLayout *dlgLayout = new QHBoxLayout; + MappingTreeWidget *mtw = new MappingTreeWidget; + dlgLayout->addWidget(mtw); + dlg.setLayout(dlgLayout); + dlg.exec(); +} @@ -23,6 +23,7 @@ class QActionGroup; class ArchiveTreeView; class NewMovieWizard; class DbAnalyzerDialog; +class MappingTreeWidget; class SheMov : public QMainWindow { Q_OBJECT @@ -52,6 +53,7 @@ class SheMov : public QMainWindow { void checkMount(bool mounted); void toggleFilterGroup(bool checked); void editMappings(QString table); + void mappingEditor(); signals: void configChanged(); @@ -105,10 +107,10 @@ class SheMov : public QMainWindow { QAction *mArchiveSelectedA; QAction *mConsistencyA; QAction *mAnalyzerA; + QAction *mMappingEditorA; //hmm QAction *mHoverDirectoriesA; - //TreeView Actions //Series Actions QAction *mNewSeriesA; @@ -36,7 +36,8 @@ SOURCES = main.cpp \ smdialog.cpp \ propertiesdialog.cpp \ dbanalyzer.cpp \ - mappingtreemodel.cpp + mappingtreemodel.cpp \ + mappingtreewidget.cpp HEADERS = listitem.h \ filesystemdirproxy.h \ filesystemwidget.h \ @@ -68,6 +69,7 @@ HEADERS = listitem.h \ smdialog.h \ propertiesdialog.h \ dbanalyzer.h \ - mappingtreemodel.h + mappingtreemodel.h \ + mappingtreewidget.h LIBS += -lmagic -lXfixes -lX11 RESOURCES = shemov.qrc diff --git a/smglobals.cpp b/smglobals.cpp index 5f26725..0cd0714 100644 --- a/smglobals.cpp +++ b/smglobals.cpp @@ -26,6 +26,7 @@ #include "filestreemodel.h" #include "mappingtablemodel.h" #include "seriesmetadatamodel.h" +#include "mappingtreemodel.h" #include "pictureviewer.h" SmGlobals *SmGlobals::mInstance = 0; @@ -86,7 +87,13 @@ QAbstractItemModel *SmGlobals::model(const QString &which){ SeriesMetadataModel *model = new SeriesMetadataModel(headers); mModels.insert(which, model); } - } + }else if(which == "MappingTree"){ + if(!mModels.contains("MappingTree")){ + QStringList headers = QStringList() << tr("Name") << tr("Id") << tr("Date"); + MappingTreeModel *model = new MappingTreeModel(headers); + mModels.insert(which, model); + } + } return mModels.contains(which) ? mModels.value(which) : 0; } diff --git a/smtreemodel.cpp b/smtreemodel.cpp index 86d2b77..98e7e45 100644 --- a/smtreemodel.cpp +++ b/smtreemodel.cpp @@ -40,9 +40,10 @@ int SmTreeModel::columnCount(const QModelIndex &parent) const{ } QModelIndex SmTreeModel::index(int row, int column, const QModelIndex &parent) const{ - if(parent.isValid() && (parent.column() != 0)){ + //this is totally bogus! Why? Tentatively leaving it in for now... + if(parent.isValid() && (parent.column() != 0)){ return QModelIndex(); - } + } SmTreeItem *parentItem = itemAt(parent); SmTreeItem *childItem = parentItem->child(row); if(childItem){ |