/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "moviepropertiesdialog.h" #include "smtreeview.h" #include "smtreeitem.h" #include "smtreemodel.h" #include "searchdialog.h" #include "smglobals.h" #include "helper.h" FilenamesAndMetadata::FilenamesAndMetadata(QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags){ // Define GUI items setWindowTitle(tr("Search...")); QLabel *l1 = new QLabel(tr("Search")); mSearch = new QLineEdit; connect(mSearch, &QLineEdit::returnPressed, this, &FilenamesAndMetadata::search); QToolBar *searchTB = new QToolBar; QAction *doSearchA = new QAction(Helper::icon(Qt::transparent, qApp->palette().color(QPalette::Text), QChar(0x2245), true, false), tr("Search"), this); connect(doSearchA, &QAction::triggered, this, &FilenamesAndMetadata::search); searchTB->addAction(doSearchA); QAction *clearSearchA= new QAction(Helper::icon(Qt::transparent, qApp->palette().color(QPalette::Text), QChar(0x2694), true, false), tr("Clear"), this); connect(clearSearchA, &QAction::triggered, [=] { mSearch->clear(); }); searchTB->addAction(clearSearchA); QHBoxLayout *topLayout = new QHBoxLayout; topLayout->addWidget(l1); topLayout->addWidget(mSearch); topLayout->addWidget(searchTB); // init Model and View const QStringList headers = QStringList() << tr("Found") << tr("Series") << tr("SeriesPartId"); mModel = new SmTreeModel(headers, this); mProxy = new QSortFilterProxyModel(this); mProxy->setSourceModel(mModel); mResult = new QTreeView; mResult->setModel(mProxy); mResult->setColumnHidden(2, true); mResult->setSortingEnabled(true); mResult->setEditTriggers(QAbstractItemView::NoEditTriggers); QHBoxLayout *bottomLayout = new QHBoxLayout; bottomLayout->addWidget(mResult); // Layout QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(topLayout); mainLayout->addLayout(bottomLayout); setLayout(mainLayout); } void FilenamesAndMetadata::writeSettings(){ QSettings s; s.setValue("FilenameAndMetadataHeaders", mResult->header()->saveState()); s.setValue("FilenameAndMetdataText", mSearch->text()); } void FilenamesAndMetadata::readSettings(){ QSettings s; mSearch->setText(s.value("FilenameAndMetdataText").toString()); mResult->header()->restoreState(s.value("FilenameAndMetadataHeaders").toByteArray()); search(); } void FilenamesAndMetadata::search(){ if(mSearch->text().isEmpty()){ return; } QSqlDatabase db = QSqlDatabase::database("treedb"); SmTreeItem *root = new SmTreeItem(3, nullptr); //search metadata QSqlQuery metadataQ(db); metadataQ.prepare("SELECT iseriespart_id, tsubject, series.tseries_name, seriesparts.tsubtitle FROM metadata, seriesparts, series WHERE tsubject ~* :re AND metadata.iseriespart_id = seriesparts.iseriesparts_id AND seriesparts.iseries_id = series.iseries_id"); metadataQ.bindValue(":re", mSearch->text()); SmTreeItem *metadataItem = new SmTreeItem(QVariantList() << tr("Metadata") << QVariant() << QVariant(), root); root->appendChild(metadataItem); int ctr = 0; metadataQ.exec(); while(metadataQ.next()){ ++ctr; appendChild(metadataQ.value(0), metadataQ.value(1), metadataQ.value(2), metadataQ.value(3), metadataItem); } if(ctr == 0){ appendEmpty(metadataItem); } //search filenames QSqlQuery filenameQ(db); filenameQ.prepare("SELECT tfilename, iseriespart_id, series.tseries_name, seriesparts.tsubtitle FROM files, seriesparts, series WHERE tfilename ~* :re AND files.iseriespart_id = seriesparts.iseriesparts_id AND seriesparts.iseries_id = series.iseries_id"); filenameQ.bindValue(":re", mSearch->text()); SmTreeItem *filenameItem = new SmTreeItem(QVariantList() << tr("Filenames") << QVariant() << QVariant(), root); root->appendChild(filenameItem); ctr = 0; filenameQ.exec(); while(filenameQ.next()){ ++ctr; appendChild(filenameQ.value(1), filenameQ.value(0), filenameQ.value(2), filenameQ.value(3), filenameItem); } if(ctr == 0){ appendEmpty(filenameItem); } mModel->setRoot(root); mResult->expandAll(); } void FilenamesAndMetadata::appendChild(QVariant id, QVariant subject, QVariant name, QVariant sub, SmTreeItem *parent){ QString match; if(!sub.toString().isEmpty()){ match = QString("%1 - %2").arg(name.toString()).arg(sub.toString()); }else{ match = name.toString(); } QVariantList itemData = QVariantList() << subject << match << id; SmTreeItem *retval = new SmTreeItem(itemData, parent); parent->appendChild(retval); } void FilenamesAndMetadata::appendEmpty(SmTreeItem *parent){ SmTreeItem *emptyItem = new SmTreeItem(QVariantList() << tr("no match!") << QVariant() << QVariant(), parent); parent->appendChild(emptyItem); } ActorsAndMore::ActorsAndMore(QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags) { //search bar QLabel *typeL = new QLabel(tr("Search by:")); mTypeSel = new QComboBox; mTypeSel->addItem(tr("Actor"), Actor); mTypeSel->addItem(tr("Subtitle"), Title); mSearch = new QLineEdit; connect(mSearch, &QLineEdit::returnPressed, this, &ActorsAndMore::doSearch); QToolBar *searchTB = new QToolBar; QAction *doSearchA = new QAction(Helper::icon(Qt::transparent, qApp->palette().color(QPalette::Text), QChar(0x2245), true, false), tr("Search"), this); connect(doSearchA, &QAction::triggered, this, &ActorsAndMore::doSearch); searchTB->addAction(doSearchA); QAction *clearSearchA= new QAction(Helper::icon(Qt::transparent, qApp->palette().color(QPalette::Text), QChar(0x2694), true, false), tr("Clear"), this); connect(clearSearchA, &QAction::triggered, [=] { mSearch->clear(); }); searchTB->addAction(clearSearchA); QHBoxLayout *topHBL = new QHBoxLayout; topHBL->addWidget(typeL); topHBL->addWidget(mSearch); topHBL->addWidget(mTypeSel); topHBL->addWidget(searchTB); // result view mResultModel = new QStandardItemModel; QSortFilterProxyModel *resultProxy = new QSortFilterProxyModel; resultProxy->setSourceModel(mResultModel); mResultView = new SmView; mResultView->setModel(resultProxy); connect(mResultView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ActorsAndMore::doData); QAction *resultCollapseAllA = new QAction(Helper::icon(Qt::transparent, qApp->palette().color(QPalette::Text), QChar(0x2191), true, false), tr("Collapse all"), this); connect(resultCollapseAllA, &QAction::triggered, this, &ActorsAndMore::collapseAllResult); mResultView->addAction(resultCollapseAllA); QAction *resultExpandAllA = new QAction(Helper::icon(Qt::transparent, qApp->palette().color(QPalette::Text), QChar(0x2193), true, false), tr("Expand all"), this); connect(resultExpandAllA, &QAction::triggered, this, &ActorsAndMore::expandAllResult); mResultView->addAction(resultExpandAllA); // data view mDataModel = new QStandardItemModel; QSortFilterProxyModel *dataProxy = new QSortFilterProxyModel; dataProxy->setSourceModel(mDataModel); mDataView = new SmView; mDataView->setExpandsOnDoubleClick(false); connect(mDataView, &QTreeView::doubleClicked, this, &ActorsAndMore::dataDoubleClicked); mDataView->setModel(dataProxy); QAction *dataCollapseAllA = new QAction(Helper::icon(Qt::transparent, qApp->palette().color(QPalette::Text), QChar(0x2191), true, false), tr("Collapse all"), this); connect(dataCollapseAllA, &QAction::triggered, this, &ActorsAndMore::collapseAllData); mDataView->addAction(dataCollapseAllA); QAction *dataExpandAllA = new QAction(Helper::icon(Qt::transparent, qApp->palette().color(QPalette::Text), QChar(0x2193), true, false), tr("Expand all"), this); connect(dataExpandAllA, &QAction::triggered, this, &ActorsAndMore::expandAllData); mDataView->addAction(dataExpandAllA); QHBoxLayout *resultHBL = new QHBoxLayout; resultHBL->addWidget(mResultView); resultHBL->addWidget(mDataView); //main Layout QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(topHBL); mainLayout->addLayout(resultHBL); setLayout(mainLayout); } void ActorsAndMore::writeSettings(){ QSettings s; s.setValue("ActorsAndMoreText", mSearch->text()); s.setValue("searchType", mTypeSel->currentText()); } void ActorsAndMore::readSettings(){ QSettings s; mSearch->setText(s.value("ActorsAndMoreText").toString()); mTypeSel->setCurrentText(s.value("searchType").toString()); doSearch(); } void ActorsAndMore::doSearch(){ QString input = mSearch->text(); if(input.isEmpty()){ return; } int searchType = mTypeSel->currentData().toInt(); if(searchType == Actor){ searchActor(input); }else if(searchType == Title){ searchSubtitle(input); } } void ActorsAndMore::searchActor(const QString &actor){ mResultView->setSortingEnabled(false); mResultModel->clear(); mResultModel->setColumnCount(1); mResultModel->setHeaderData(0, Qt::Horizontal, tr("Actor")); QIcon icon = SmGlobals::instance()->iconFor("actor"); QStandardItem *root = mResultModel->invisibleRootItem(); QSqlDatabase db = QSqlDatabase::database("treedb"); QSqlQuery actorQ(db); actorQ.prepare("SELECT tactorname, iactors_id FROM actors WHERE tactorname ~ :name"); actorQ.bindValue(":name", actor); actorQ.exec(); while(actorQ.next()){ QStandardItem *cur = new QStandardItem(actorQ.value(0).toString()); cur->setIcon(icon); cur->setData(actorQ.value(1), IdRole); cur->setEditable(false); getGenresForActor(cur); root->appendRow(cur); } mResultView->setSortingEnabled(true); mResultView->sortByColumn(0, Qt::AscendingOrder); } void ActorsAndMore::searchSubtitle(const QString &subtitle){ mResultView->setSortingEnabled(false); mResultModel->clear(); mResultModel->setColumnCount(1); mResultModel->setHeaderData(0, Qt::Horizontal, tr("Title")); QIcon icon = SmGlobals::instance()->iconFor("series"); QStandardItem *root = mResultModel->invisibleRootItem(); QSqlDatabase db = QSqlDatabase::database("treedb"); QSqlQuery titleQ(db); titleQ.prepare("SELECT DISTINCT(series.iseries_id), tseries_name FROM series, seriesparts WHERE seriesparts.tsubtitle ~ :title AND seriesparts.iseries_id = series.iseries_id ORDER BY tseries_name"); titleQ.bindValue(":title", subtitle); titleQ.exec(); while(titleQ.next()){ QStandardItem *cur = new QStandardItem(titleQ.value(1).toString()); cur->setIcon(icon); cur->setData(titleQ.value(0), IdRole); cur->setEditable(false); root->appendRow(cur); } mResultView->setSortingEnabled(true); mResultView->sortByColumn(0, Qt::AscendingOrder); } void ActorsAndMore::getGenresForActor(QStandardItem *actorItem){ QStringList res; QIcon icon = SmGlobals::instance()->iconFor("genre"); QSqlDatabase db = QSqlDatabase::database("treedb"); QSqlQuery genreQ(db); genreQ.prepare("SELECT DISTINCT(genres.tgenrename) FROM genres, seriesparts, seriesparts_actormap, seriesparts_genremap WHERE seriesparts_actormap.iseriesparts_id = seriesparts.iseriesparts_id AND seriesparts.iseriesparts_id = seriesparts_genremap.iseriesparts_id AND seriesparts_genremap.igenres_id = genres.igenres_id AND seriesparts_actormap.iactors_id = :id ORDER BY genres.tgenrename"); genreQ.bindValue(":id", actorItem->data(IdRole)); genreQ.exec(); while(genreQ.next()){ res << genreQ.value(0).toString(); } if(!res.isEmpty()){ for(QString g : res){ QStandardItem *cur = new QStandardItem(g); cur->setIcon(icon); cur->setEditable(false); cur->setData(actorItem->data(IdRole), IdRole); actorItem->appendRow(cur); } } } void ActorsAndMore::doData(const QModelIndex &cur, const QModelIndex &prev){ Q_UNUSED(prev) int searchType = mTypeSel->currentData().toInt(); if(searchType == Actor){ getDataForActor(cur); }else if(searchType == Title){ getDataForTitle(cur); } } void ActorsAndMore::dataDoubleClicked(const QModelIndex &index){ QModelIndex cur = index; while(cur.parent().isValid()){ cur = cur.parent(); } int searchType = mTypeSel->currentData().toInt(); if(searchType == Actor || searchType == Title){ MoviePropertiesDialog propDlg(this); propDlg.init(cur.data(IdRole).toInt()); propDlg.exec(); } } void ActorsAndMore::getDataForActor(QModelIndex cur){ mDataModel->clear(); mDataModel->setColumnCount(1); mDataModel->setHeaderData(0, Qt::Horizontal, tr("Series")); QIcon icon = SmGlobals::instance()->iconFor("series"); QVector seriesParts; QStandardItem *root = mDataModel->invisibleRootItem(); QSqlDatabase db = QSqlDatabase::database("treedb"); QSqlQuery seriesPartIdQ(db); seriesPartIdQ.prepare("SELECT DISTINCT(seriesparts.iseriesparts_id) FROM seriesparts_actormap, seriesparts WHERE seriesparts_actormap.iactors_id = :id AND seriesparts_actormap.iseriesparts_id = seriesparts.iseriesparts_id"); seriesPartIdQ.bindValue(":id", cur.data(IdRole)); seriesPartIdQ.exec(); while(seriesPartIdQ.next()){ seriesParts << seriesPartIdQ.value(0).toInt(); } QSqlQuery displayQ(db); displayQ.prepare("SELECT series.tseries_name, seriesparts.iseriespart, seriesparts.iseriesparts_id, seriesparts.tsubtitle FROM series, seriesparts WHERE seriesparts.iseriesparts_id = :id AND seriesparts.iseries_id = series.iseries_id"); for(int part : seriesParts){ displayQ.bindValue(":id", part); displayQ.exec(); while(displayQ.next()){ int sPart = displayQ.value(1).toInt(); QString curDisp; if(sPart > 0){ curDisp = QString("%1 %2").arg(displayQ.value(0).toString()).arg(sPart, 3, 10, QChar('0')); }else{ QString sub = displayQ.value(3).toString(); if(sub.isEmpty()){ curDisp = QString("%1 - ").arg(displayQ.value(0).toString()); }else{ curDisp = QString("%1 - %2").arg(displayQ.value(0).toString()).arg(sub); } } QStandardItem *cur = new QStandardItem(curDisp); cur->setIcon(icon); cur->setData(displayQ.value(2), IdRole); cur->setEditable(false); QSqlQuery filenameQ(db); filenameQ.prepare("SELECT tfilename FROM files WHERE iseriespart_id = :fid"); filenameQ.bindValue(":fid", displayQ.value(2)); filenameQ.exec(); while(filenameQ.next()){ QStandardItem *curFile = new QStandardItem(filenameQ.value(0).toString()); curFile->setIcon(icon); curFile->setEditable(false); cur->appendRow(curFile); } root->appendRow(cur); } mDataView->setSortingEnabled(true); mDataView->sortByColumn(0, Qt::AscendingOrder); } } void ActorsAndMore::getDataForTitle(QModelIndex cur){ mDataModel->clear(); mDataModel->setColumnCount(1); mDataModel->setHeaderData(0, Qt::Horizontal, tr("Subtitle")); QIcon icon = SmGlobals::instance()->iconFor("series"); QStandardItem *root = mDataModel->invisibleRootItem(); QSqlDatabase db = QSqlDatabase::database("treedb"); QSqlQuery titleDataQ(db); titleDataQ.prepare("SELECT tsubtitle, iseriesparts_id FROM seriesparts WHERE iseries_id = :id AND tsubtitle ~ :title ORDER BY tsubtitle"); titleDataQ.bindValue(":id", cur.data(IdRole)); titleDataQ.bindValue(":title", mSearch->text()); titleDataQ.exec(); while(titleDataQ.next()){ QStandardItem *curTitle = new QStandardItem(titleDataQ.value(0).toString()); curTitle->setIcon(icon); curTitle->setData(titleDataQ.value(1), IdRole); curTitle->setEditable(false); root->appendRow(curTitle); } mDataView->setSortingEnabled(true); mDataView->sortByColumn(0, Qt::AscendingOrder); } SearchDialog::SearchDialog(QWidget *parent, Qt::WindowFlags flags) : QDialog(parent, flags) { QHBoxLayout *gbLayout = new QHBoxLayout; QGroupBox *metaFnGb = new QGroupBox(tr("Metadata and Filenames")); mFilenameAndMetadataW = new FilenamesAndMetadata; gbLayout->addWidget(mFilenameAndMetadataW); metaFnGb->setLayout(gbLayout); QHBoxLayout *gbLayout2 = new QHBoxLayout; QGroupBox *actorsAndMoreGb = new QGroupBox(tr("Actors and more...")); mActorsAndMoreW = new ActorsAndMore; gbLayout2->addWidget(mActorsAndMoreW); actorsAndMoreGb->setLayout(gbLayout2); QSplitter *splitter = new QSplitter(Qt::Vertical); splitter->addWidget(metaFnGb); splitter->addWidget(actorsAndMoreGb); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(splitter); setLayout(mainLayout); setWindowTitle(tr("Search Dialog")); readSettings(); } void SearchDialog::writeSettings(){ QSettings s; s.setValue("searchdlgpos", pos()); s.setValue("searchdlgsize", size()); mFilenameAndMetadataW->writeSettings(); mActorsAndMoreW->writeSettings(); } void SearchDialog::readSettings(){ QSettings s; move(s.value("searchdlgpos").toPoint()); resize(s.value("searchdlgsize").toSize()); mFilenameAndMetadataW->readSettings(); mActorsAndMoreW->readSettings(); }