/* 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 #include #include #include #include #include #include #include #include #include "archivetreeview.h" #include "smglobals.h" #include "smtreemodel.h" #include "smtreeitem.h" #include "seriestreewidget.h" #include "filestreewidget.h" #include "filestreemodel.h" #include "seriestreemodel.h" #include "mappingtablewidget.h" #include "mappingtablemodel.h" #include "seriesmetadatamodel.h" #include "helper.h" ArchiveTreeView::ArchiveTreeView(QWidget *parent) : QWidget(parent){ // models mActorsModel = static_cast(SmGlobals::instance()->model("actors")); mGenresModel = static_cast(SmGlobals::instance()->model("genres")); // series view mSeriesWidget = new SeriesTreeWidget(this); QItemSelectionModel *selModel = mSeriesWidget->seriesTree()->selectionModel(); connect(selModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(currentChanged(QItemSelection,QItemSelection))); connect(mSeriesWidget->seriesTree(), SIGNAL(doubleClicked(QModelIndex)), this, SLOT(playSelected())); mSeriesModel = static_cast(SmGlobals::instance()->model("SeriesModel")); // files view mFilesWidget = new FilesTreeWidget; mFilesModel = qobject_cast(SmGlobals::instance()->model("FilesModel")); connect(mSeriesWidget, SIGNAL(filesReload()), mFilesWidget->filesTree(), SLOT(expandAll())); // actors and genre QWidget *genreActorWidget = new QWidget; QHBoxLayout *genreActorLayout = new QHBoxLayout; mActorsWidget = new MappingTableWidget("actors"); mGenresWidget = new MappingTableWidget("genres"); mMetaData = new MetadataDisplay; genreActorLayout->addWidget(mActorsWidget); genreActorLayout->addWidget(mGenresWidget); genreActorLayout->addWidget(mMetaData); genreActorWidget->setLayout(genreActorLayout); // fileview, actors and genres QSplitter *verticalSplitter = new QSplitter(Qt::Vertical); verticalSplitter->addWidget(mFilesWidget); verticalSplitter->addWidget(genreActorWidget); verticalSplitter->setStretchFactor(0, 3); verticalSplitter->setStretchFactor(1, 1); // misc mNoCoverDialog = new NoCoverMovieDialog(this); mNoCoverDialog->setHidden(true); connect(mNoCoverDialog->view(), SIGNAL(doubleClicked(QModelIndex)), this, SLOT(selectMovie(QModelIndex))); mFilesWidget->filesTree()->setAlternatingRowColors(true); mSeriesWidget->seriesTree()->setAlternatingRowColors(true); // layout QHBoxLayout *mainLayout = new QHBoxLayout; QSplitter *splitter = new QSplitter; splitter->addWidget(mSeriesWidget); splitter->addWidget(verticalSplitter); splitter->setStretchFactor(0, 2); splitter->setStretchFactor(1, 3); mainLayout->addWidget(splitter); setLayout(mainLayout); constructWindowTitle(); readSettings(); } void ArchiveTreeView::setFileViewMode(int mode){ mSeriesModel->setRowFilter(mode); mSeriesWidget->seriesProxy()->invalidate(); constructWindowTitle(); mSeriesWidget->expandItems(); } void ArchiveTreeView::showAllFiles(){ int seriesRow = 0; QModelIndex series = mSeriesModel->index(seriesRow, 0, QModelIndex()); QList ids; while(series.isValid()){ int childRow = 0; QModelIndex child = mSeriesModel->index(childRow, 0, series); while(child.isValid()){ if(mSeriesWidget->seriesProxy()->acceptedByFilter(child.row(), child.parent())){ ids << child.data(SeriesTreeModel::SeriesPartIdRole).toInt(); } child = mSeriesModel->index(++childRow, 0, child.parent()); } series = mSeriesModel->index(++seriesRow, 0, series.parent()); } mFilesModel->setIds(ids); mFilesWidget->filesTree()->expandAll(); mFilesWidget->filesTree()->resizeColumnToContents(0); } void ArchiveTreeView::cleanDatabase(const QString &table){ MappingTableModel *model = 0; if(table == "genres"){ model = mGenresModel; }else if(table == "actors"){ model = mActorsModel; } Q_ASSERT(model); QHash zeroUsers = model->zeroUsers(); if(zeroUsers.isEmpty()){ QMessageBox::information(this, tr("Message"), tr("Nothing to clean up!")); return; } QString message = QString(tr("

Really delete these items from %1?

")).arg(table); message.append("

    "); QStringList userNames = zeroUsers.keys(); qSort(userNames); foreach(QString s, userNames){ message.append(QString("
  • %1
  • ").arg(s)); } message.append("
"); int answer = QMessageBox::question(this, tr("Question"), message, QMessageBox::Yes | QMessageBox::No); if(answer == QMessageBox::Yes){ QList ids = zeroUsers.values(); model->deleteItems(ids); } } void ArchiveTreeView::showNoCoverDialog(){ mNoCoverDialog->show(); } void ArchiveTreeView::selectMovie(const QModelIndex &idx){ if(!idx.isValid()){ return; } QVariant movieName = idx.data(NoCoverMovieModel::SeriesNameRole); QVariant part = idx.data(NoCoverMovieModel::SeriesPartRole); QModelIndex movieIdx = mSeriesModel->findValue(movieName, QModelIndex(), SeriesTreeModel::Name); if(!movieIdx.isValid()){ return; } QModelIndex partIdx = mSeriesModel->findValue(part, movieIdx, SeriesTreeModel::SeriesPart, 0); QModelIndex toSelect = partIdx.isValid() ? mSeriesWidget->seriesProxy()->mapFromSource(partIdx) : mSeriesWidget->seriesProxy()->mapFromSource(movieIdx); mSeriesWidget->seriesTree()->expand(mSeriesWidget->seriesProxy()->mapFromSource(movieIdx)); mSeriesWidget->seriesTree()->selectionModel()->select(toSelect, QItemSelectionModel::Clear | QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); mSeriesWidget->seriesTree()->scrollTo(toSelect, QAbstractItemView::PositionAtCenter); mNoCoverDialog->hide(); } void ArchiveTreeView::copyPath(int type){ QModelIndexList selected = mFilesWidget->filesTree()->selectionModel()->selectedRows(); if(selected.isEmpty()){ return; } QString fullPath = selected.at(0).data(FilesTreeModel::FullPathRole).toString(); QFileInfo fi(fullPath); if(!fi.exists()){ int dvdNo = selected.at(0).data(FilesTreeModel::DvdNoRole).toInt(); QString msg = QString(tr("File not found. File is archived on DVD #%1")).arg(QString::number(dvdNo)); QMessageBox::critical(this, tr("Error"), msg); return; } QClipboard *clipBoard = QApplication::clipboard(); if(type == UnixFullPath){ clipBoard->setText(fi.absoluteFilePath()); } if(type == UnixDir){ clipBoard->setText(fi.absolutePath()); } if(type == WindowsFullPath){ clipBoard->setText(createWindowsPath(fi.absoluteFilePath())); } if(type == WindowsDir){ clipBoard->setText(createWindowsPath(fi.absolutePath())); } } void ArchiveTreeView::readSettings(){ seriesWidget()->seriesTree()->readSettings(); filesWidget()->filesTree()->readSettings(); mFilesModel->readSettings(); QSettings s; QString iconName = s.value("ui/foldericon", "Dildo").toString(); const QHash icons = SmGlobals::instance()->icons(); QIcon decorationIcon = QIcon(icons.value(iconName)); mFilesModel->setDecorationIcon(decorationIcon); mSeriesModel->setDecorationIcon(decorationIcon); MappingTableItemModel *actorsModel = mActorsWidget->model(); actorsModel->setDecorationIcon(decorationIcon); MappingTableItemModel *genreModel = mGenresWidget->model(); genreModel->setDecorationIcon(decorationIcon); } void ArchiveTreeView::currentChanged(const QItemSelection &selected, const QItemSelection &deselected){ Q_UNUSED(selected); Q_UNUSED(deselected); QModelIndexList selectedSeries = mSeriesWidget->seriesTree()->selectionModel()->selectedRows(); mFilesWidget->resetSize(); if(selectedSeries.isEmpty()){ return; } QList selectedPartIds; SeriesTreeSortModel *proxy = mSeriesWidget->seriesProxy(); foreach(QModelIndex i, selectedSeries){ QModelIndex real = proxy->mapToSource(i); if(real.isValid()){ int type = real.data(SeriesTreeModel::TypeRole).toInt(); if(type == SeriesTreeModel::Series){ QList childIds = mSeriesModel->childrenColumnList(real, SeriesTreeModel::SeriesPartId); foreach(QVariant id, childIds){ int realId = id.toInt(); if(!selectedPartIds.contains(realId)){ selectedPartIds << realId; } } }else if(type == SeriesTreeModel::Part){ int realId = i.data(SeriesTreeModel::SeriesPartIdRole).toInt(); if(!selectedPartIds.contains(realId)){ selectedPartIds << realId; } } } } mFilesModel->setIds(selectedPartIds); mFilesWidget->filesTree()->expandAll(); mFilesWidget->filesTree()->header()->resizeSections(QHeaderView::ResizeToContents); setMappingItems(selectedPartIds, mActorsModel, mActorsWidget); setMappingItems(selectedPartIds, mGenresModel, mGenresWidget); mMetaData->setSeriesPartId(selectedPartIds.at(0)); } void ArchiveTreeView::setMappingItems(QListseriesPartIds, MappingTableModel *model, MappingTableWidget *widget){ QStringList items; foreach(int id, seriesPartIds){ QList actorNames = model->mappings(id); foreach(QVariant v, actorNames){ if(!items.contains(v.toString())){ items << v.toString(); } } } qSort(items); widget->setStringList(items); widget->setEditEnabled(seriesPartIds.count() == 1); if(seriesPartIds.count() == 1){ widget->setCurrentId(seriesPartIds.at(0)); }else{ widget->setCurrentId(-1); } } void ArchiveTreeView::playSelected(const QString &preferred){ QModelIndexList selected = mSeriesWidget->seriesTree()->selectionModel()->selectedRows(); if(selected.isEmpty()){ return; } QStringList files; foreach(QModelIndex idx, selected){ QModelIndex real = mSeriesWidget->seriesProxy()->mapToSource(idx); QFileInfoList movies = mSeriesModel->findSortedMovies(real); foreach(QFileInfo fi, movies){ if(!files.contains(fi.absoluteFilePath())){ files << fi.absoluteFilePath(); } } } QPair progData = Helper::programData("movieviewer", preferred); QString program = progData.first; QStringList args = progData.second; args << files; QProcess::startDetached(program, args); } void ArchiveTreeView::constructWindowTitle(){ QString modeString = mFilesModel->modeName(mFilesModel->mode()); mWindowTitle = QString("Movie Archive - [%1]").arg(modeString); emit needWindowTitleChange(mWindowTitle); } const QString ArchiveTreeView::createWindowsPath(const QString &path) const{ QString retval = path; QSettings s; const QString strip = s.value("paths/strippath").toString(); retval.remove(strip); retval.replace('/', '\\'); retval = QString("%1\\%2").arg(s.value("paths/windowsdrive").toString()).arg(retval); return retval; } //NoCoverMovieModel NoCoverMovieModel::NoCoverMovieModel(const QStringList &headers, QObject *parent) : SmTreeModel(headers, parent){ mDb = QSqlDatabase::database("treedb"); mDataQuery = new QSqlQuery(mDb); mDataQuery->prepare("SELECT files.iseriespart_id, series.tseries_name, seriesparts.iseriespart FROM files, series, seriesparts WHERE files.iseriespart_id = seriesparts.iseriesparts_id AND seriesparts.iseries_id = series.iseries_id GROUP BY files.iseriespart_id,series.tseries_name,seriesparts.iseriespart HAVING COUNT(iseriespart_id) = 1 ORDER BY series.tseries_name"); refresh(); } NoCoverMovieModel::~NoCoverMovieModel(){ delete mDataQuery; mDb = QSqlDatabase(); } QVariant NoCoverMovieModel::data(const QModelIndex &index, int role) const{ if(!index.isValid()){ return QVariant(); } SmTreeItem *item = itemAt(index); if(role == SeriesNameRole){ return item->data(SeriesName); } if(role == SeriesPartRole){ return item->data(SeriesPart); } if(role == SeriesIdRole){ return item->data(SeriesId); } return SmTreeModel::data(index, role); } void populate(); void NoCoverMovieModel::refresh(){ SmTreeItem *rootItem = new SmTreeItem(4); mDataQuery->exec(); while(mDataQuery->next()){ QString displayName = QString("%1 %2").arg(mDataQuery->value(1).toString()).arg(QString::number(mDataQuery->value(2).toInt())); QList data; data << displayName << mDataQuery->value(1) << mDataQuery->value(2) << mDataQuery->value(0); SmTreeItem *child = new SmTreeItem(data, rootItem); rootItem->appendChild(child); } setRoot(rootItem); emit refreshed(); } Qt::ItemFlags NoCoverMovieModel::flags(const QModelIndex &index) const{ if(!index.isValid()){ return 0; } return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } //NoCoverMovieView NoCoverMovieView::NoCoverMovieView(QWidget *parent) : QTreeView(parent) {} void NoCoverMovieView::contextMenuEvent(QContextMenuEvent *e){ QMenu ctxMenu; foreach(QAction *a, actions()){ ctxMenu.addAction(a); } ctxMenu.exec(e->globalPos()); } //NoCoverMovieDialog NoCoverMovieDialog::NoCoverMovieDialog(QWidget *parent, Qt::WindowFlags flags) : QDialog(parent, flags){ //model + view mView = new NoCoverMovieView; QStringList headers = QStringList() << "Movie" << "Name" << "Part" << "Id"; mModel = new NoCoverMovieModel(headers, this); mView->setModel(mModel); connect(mModel, SIGNAL(refreshed()), this, SLOT(updateLabel())); mView->setColumnHidden(1, true); mView->setColumnHidden(2, true); mView->setColumnHidden(3, true); mView->resizeColumnToContents(0); //actions mBrowserA = new QAction(tr("Search with google..."), this); connect(mBrowserA, SIGNAL(triggered()), this, SLOT(openInBrowser())); mView->addAction(mBrowserA); mCopyA = new QAction(tr("Copy name"), this); connect(mCopyA, SIGNAL(triggered()), this, SLOT(copyToClipboard())); mView->addAction(mCopyA); //buttons QHBoxLayout *buttonLayout = new QHBoxLayout; mClose = new QPushButton(tr("Close")); connect(mClose, SIGNAL(clicked()), this, SLOT(hide())); mRefresh = new QPushButton(tr("Refresh")); connect(mRefresh, SIGNAL(clicked()), mModel, SLOT(refresh())); buttonLayout->addWidget(mRefresh); buttonLayout->addStretch(); buttonLayout->addWidget(mClose); //label mMovieLabel = new QLabel(labelText()); //main QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(mMovieLabel); mainLayout->addWidget(mView); mainLayout->addLayout(buttonLayout); setLayout(mainLayout); } void NoCoverMovieDialog::openInBrowser(){ QString selText = selectedText(); if(selText.isEmpty()){ return; } selText.append(" covers"); QByteArray searchString = QUrl::toPercentEncoding(selText); QString url = QString("http://www.google.com/search?hl=en&q=%1").arg(QString(searchString)); QStringList args = QStringList() << "openURL" << url; bool success = QProcess::startDetached("kfmclient", args); if(!success){ QMessageBox::critical(this, tr("Error"), tr("Launching browser failed. Most likely kfmclient is not installed")); } } void NoCoverMovieDialog::copyToClipboard(){ QString selText = selectedText(); if(selText.isEmpty()){ return; } QClipboard *clip = QApplication::clipboard(); clip->setText(selText); } void NoCoverMovieDialog::updateLabel(){ mMovieLabel->setText(labelText()); } const QString NoCoverMovieDialog::selectedText() const{ QModelIndexList selected = mView->selectionModel()->selectedRows(); if(selected.isEmpty()){ return QString(); } QModelIndex real = mModel->index(selected.at(0).row(), 0, selected.at(0).parent()); if(!real.isValid()){ return QString(); } return real.data().toString(); } const QString NoCoverMovieDialog::labelText() const{ QString retval = QString(tr("Movies without covers (%1)")).arg(QString::number(mModel->rowCount(QModelIndex()))); return retval; }