/* 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 "archiveview.h" #include "archivecontroller.h" #include "mappingtablewidget.h" #include "smglobals.h" #include "helper.h" ArchiveView::ArchiveView(QWidget *parent) : QWidget(parent) { QSettings s; mArchiveModel = static_cast(SmGlobals::instance()->model("ArchiveModel")); connect(mArchiveModel, SIGNAL(needRefresh()), this, SLOT(refreshArchive())); connect(mArchiveModel, SIGNAL(databaseError(QString)), this, SLOT(showDatabaseError(QString))); mProxy = new ArchiveProxy; mProxy->setSourceModel(mArchiveModel); mTree = new ArchiveTree; mTree->setModel(mProxy); mTree->setColumnHidden(ArchiveModel::GenericId, true); mTree->setColumnHidden(ArchiveModel::SeriesPartId, true); mTree->setColumnHidden(ArchiveModel::SeriesPart, true); mTree->setColumnHidden(ArchiveModel::Type, true); mTree->setColumnHidden(ArchiveModel::Favorite, true); mTree->setColumnHidden(ArchiveModel::Subtitle, true); mTree->setColumnHidden(ArchiveModel::Count, true); mTree->resizeColumnToContents(ArchiveModel::Name); mTree->setSortingEnabled(true); mTree->setEditTriggers(QAbstractItemView::NoEditTriggers); connect(mTree, SIGNAL(expanded(QModelIndex)), this, SLOT(expandItem(QModelIndex))); connect(mTree, SIGNAL(collapsed(QModelIndex)), this, SLOT(collapseItem(QModelIndex))); QString sortOrderName = s.value("archivemodel/sortorder", "Series Name").toString(); mSortOrder = new QComboBox; mSortOrder->addItems(mArchiveModel->availableOrders()); mSortOrder->setCurrentText(sortOrderName); // this is _not_ redundant! when sortOrderName is the first // item in the list, nothing will happen otherwise! mArchiveModel->setOrder(sortOrderName); connect(mSortOrder, SIGNAL(currentIndexChanged(QString)), mArchiveModel, SLOT(setOrder(QString))); connect(mArchiveModel, SIGNAL(modelReset()), this, SLOT(doSelection())); QLabel *l1 = new QLabel(tr("Filter")); mFilter = new QLineEdit; QString savedFilter = s.value("archivemodel/filter", QString()).toString(); connect(mFilter, SIGNAL(returnPressed()), this, SLOT(setFilter())); QPushButton *filter = new QPushButton(tr("Filter")); connect(filter, SIGNAL(clicked()), this, SLOT(setFilter())); QPushButton *clear = new QPushButton(tr("Clear")); mFilter->setText(savedFilter); connect(clear, SIGNAL(clicked()), this, SLOT(clearFilter())); mProxy->setFilter(savedFilter, currentSortOrder()); QHBoxLayout *filterLayout = new QHBoxLayout; filterLayout->addWidget(l1); filterLayout->addWidget(mFilter); filterLayout->addWidget(filter); filterLayout->addWidget(clear); QVBoxLayout *treeLayout = new QVBoxLayout; treeLayout->addWidget(mSortOrder); treeLayout->addLayout(filterLayout); treeLayout->addWidget(mTree); QWidget *treeWidget = new QWidget; treeWidget->setLayout(treeLayout); mFiles = new ArchiveFiles; mFiles->setItemDelegateForColumn(ArchiveFilesModel::FileType, new FileTypeDelegate(this)); mFiles->setItemDelegateForColumn(ArchiveFilesModel::DvdNo, new DvdNoDelegate(this)); mFiles->setItemDelegateForColumn(ArchiveFilesModel::Size, new SizeDelegate(this)); mFiles->setItemDelegateForColumn(ArchiveFilesModel::SizeDur, new DurationDelegate(this)); mFiles->setItemDelegateForColumn(ArchiveFilesModel::FileNumber, new EmptyDelegate(this)); mFiles->setSelectionMode(QAbstractItemView::ExtendedSelection); mFiles->setSelectionBehavior(QAbstractItemView::SelectRows); mFilesModel = new ArchiveFilesModel(QStringList() << QChar(0x26A7) << tr("Seriespartid") << tr("Filename") << tr("Md5Sum") << tr("Size") << tr("Dvd") << tr("Type") << tr("No") << tr("Q") << tr("Fileid") << tr("S/Dur") << tr("Full Path"), this); mFilesProxy = new ArchiveFilesProxy; mFilesProxy->setSourceModel(mFilesModel); mFiles->setModel(mFilesProxy); QHBoxLayout *bottomRightLayout = new QHBoxLayout; mActorView = new SmTreeView; mActorView->setHeaderHidden(true); mActorModel = new QStandardItemModel(this); mActorView->setModel(mActorModel); QGroupBox *actorBox = new QGroupBox(tr("Actors")); QHBoxLayout *actorLayout = new QHBoxLayout; actorLayout->addWidget(mActorView); actorBox->setLayout(actorLayout); mGenreView = new SmTreeView; mGenreView->setHeaderHidden(true); mGenreModel = new QStandardItemModel(this); mGenreView->setModel(mGenreModel); QGroupBox *genreBox = new QGroupBox(tr("Genres")); QHBoxLayout *genreLayout = new QHBoxLayout; genreLayout->addWidget(mGenreView); genreBox->setLayout(genreLayout); mMetadataView = new QTextEdit; mMetadataView->setReadOnly(true); QGroupBox *metadataBox = new QGroupBox("Metadata"); QHBoxLayout *metadataLayout = new QHBoxLayout; metadataLayout->addWidget(mMetadataView); metadataBox->setLayout(metadataLayout); bottomRightLayout->addWidget(actorBox); bottomRightLayout->addWidget(genreBox); bottomRightLayout->addWidget(metadataBox); QVBoxLayout *rightLayout = new QVBoxLayout; rightLayout->addWidget(mFiles); rightLayout->addLayout(bottomRightLayout); QWidget *rightWidget = new QWidget; rightLayout->setStretchFactor(mFiles, 3); rightWidget->setLayout(rightLayout); mTreeSplitter = new QSplitter(Qt::Horizontal); mTreeSplitter->addWidget(treeWidget); mTreeSplitter->addWidget(rightWidget); mTreeSplitter->setStretchFactor(0, 1); mTreeSplitter->setStretchFactor(1, 4); QHBoxLayout *mainLayout = new QHBoxLayout; mainLayout->addWidget(mTreeSplitter); setLayout(mainLayout); initController(); } int ArchiveView::currentSortOrder() const { return mArchiveModel->availableOrdersHash().value(mSortOrder->currentText()); } void ArchiveView::readSettings(){ QSettings s; QVariantList list; list = s.value("archivemodel/expandedgenres").toList(); mExpandedItems[ArchiveModel::Genre] = list; list = s.value("archivemodel/expandedactors").toList(); mExpandedItems[ArchiveModel::Actor] = list; list = s.value("archivemodel/expandedseries").toList(); mExpandedItems[ArchiveModel::SeriesName] = list; mCurrentArchivePath = s.value("archivemodel/selectedseries").toStringList(); QByteArray splitterState = s.value("archivemodel/vsplitter").toByteArray(); mTreeSplitter->restoreState(splitterState); doSelection(); mFiles->readHeaderConfig(); mFiles->setSortingEnabled(true); } void ArchiveView::writeSettings() { QSettings s; s.setValue("archivemodel/sortorder", mSortOrder->currentText()); s.setValue("archivemodel/filter", mFilter->text()); s.setValue("archivemodel/expandedgenres", mExpandedItems.value(ArchiveModel::Genre)); s.setValue("archivemodel/expandedactors", mExpandedItems.value(ArchiveModel::Actor)); s.setValue("archivemodel/expandedseries", mExpandedItems.value(ArchiveModel::SeriesName)); s.setValue("archivemodel/selectedseries", mCurrentArchivePath); s.setValue("archivemodel/vsplitter", mTreeSplitter->saveState()); mFiles->writeHeaderConfig(); } void ArchiveView::refreshArchive(){ writeSettings(); mArchiveModel->refresh(); doSelection(); } void ArchiveView::doSelection(){ QVariantList list = mExpandedItems.value(currentSortOrder()); foreach(QVariant p, list){ QStringList path = p.toStringList(); QModelIndexList expand = mArchiveModel->pathToIndex(path); foreach(QModelIndex idx, expand){ mTree->setExpanded(mProxy->mapFromSource(idx), true); } } if(!mCurrentArchivePath.isEmpty()){ QModelIndexList l = mArchiveModel->pathToIndex(mCurrentArchivePath); if(!l.isEmpty()){ QModelIndex sel = mProxy->mapFromSource(l.last()); mTree->selectionModel()->select(sel, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); } } } void ArchiveView::setFilter(){ QString filter = mFilter->text(); mProxy->setFilter(filter, currentSortOrder()); mTree->expandAll(); } void ArchiveView::clearFilter(){ mFilter->clear(); mProxy->setFilter(QString(), currentSortOrder()); mTree->collapseAll(); } void ArchiveView::showDatabaseError(const QString &errorMsg){ QMessageBox::critical(this, tr("Database Error"), errorMsg); } void ArchiveView::expandItem(const QModelIndex &idx){ QModelIndex realIdx = mProxy->mapToSource(idx); if(realIdx.isValid()){ QStringList path = mArchiveModel->indexToPath(realIdx); QVariantList list = mExpandedItems.value(currentSortOrder()); if(!list.contains(path)){ list << mArchiveModel->indexToPath(realIdx); } mExpandedItems[currentSortOrder()] = list; } } void ArchiveView::collapseItem(const QModelIndex &idx){ QModelIndex realIdx = mProxy->mapToSource(idx); if(realIdx.isValid()){ QVariantList list = mExpandedItems.value(currentSortOrder()); list.removeAll(mArchiveModel->indexToPath(realIdx)); mExpandedItems[currentSortOrder()] = list; } } void ArchiveView::initController(){ mController = new ArchiveController(this); mController->setArchiveView(this); mController->setArchiveTree(mTree, mProxy); mController->setArchiveFiles(mFiles, mFilesProxy); mController->setModels(mArchiveModel, mFilesModel); mController->setMappingModels(mActorModel, mGenreModel); mController->setMetadata(mMetadataView); mController->init(); SmGlobals::instance()->setArchiveController(mController); } ArchiveTree::ArchiveTree(QWidget *parent) : SmTreeView(parent) {} void ArchiveTree::setModel(ArchiveProxy *model){ mProxy = model; mModel = qobject_cast(mProxy->sourceModel()); QTreeView::setModel(model); } void ArchiveTree::rename(){ QModelIndex idx = currentIndex(); int nodeType = idx.data(ArchiveModel::TypeRole).toInt(); if(nodeType == ArchiveModel::SeriesPartNode){ impossible(); return; } QString currentName = idx.data(ArchiveModel::NameRole).toString(); QString question = QString(tr("Rename %1 to:")).arg(currentName); QString newName = QInputDialog::getText(this, tr("Rename"), question, QLineEdit::Normal, currentName); if(!newName.isEmpty()){ QModelIndex realIdx = mProxy->mapToSource(idx); mModel->setData(realIdx, newName, Qt::EditRole); } } void ArchiveTree::remove(){ QModelIndex idx = currentIndex(); QModelIndex realIdx = mProxy->mapToSource(idx); QString warningMsg = QString("Really remove %1?").arg(idx.data().toString()); int retval = QMessageBox::warning(this, tr("Question"), warningMsg, QMessageBox::Yes | QMessageBox::No); if(retval == QMessageBox::Yes){ if(!mModel->removeNode(realIdx)){ impossible(); } } } void ArchiveTree::editActors(){ MappingEditor e(tr("Actors"), this); QStringList actors = mModel->allActors(); e.widget()->fillCompleter(actors); QModelIndex idx = currentIndex(); int pId = idx.data(ArchiveModel::SeriesPartIdRole).toInt(); QStringList curActors = mModel->actors(QSet() << pId); e.widget()->setCurrentItems(curActors); int res = e.exec(); if(res == QDialog::Accepted){ QStringList selActors = e.widget()->items(); mModel->setActors(pId, selActors); ArchiveController *c = SmGlobals::instance()->archiveController(); c->treeSelectionChanged(QItemSelection(), QItemSelection()); } } void ArchiveTree::editGenres(){ MappingEditor e(tr("Genres"), this); QStringList genres = mModel->allGenres(); e.widget()->fillCompleter(genres); QModelIndex idx = currentIndex(); int pId = idx.data(ArchiveModel::SeriesPartIdRole).toInt(); QStringList curGenres = mModel->genres(QSet() << pId); e.widget()->setCurrentItems(curGenres); int res = e.exec(); if(res == QDialog::Accepted){ QStringList selGenres = e.widget()->items(); mModel->setGenres(pId, selGenres); ArchiveController *c = SmGlobals::instance()->archiveController(); c->treeSelectionChanged(QItemSelection(), QItemSelection()); } } void ArchiveTree::editMetadata(){ MetadataEditor e(this); ArchiveController *c = SmGlobals::instance()->archiveController(); QStringList sources = c->archiveTreeModel()->allSources(); e.setSources(sources); QStringList relGroups = c->archiveTreeModel()->allReleaseGroups(); e.setReleaseGroups(relGroups); QModelIndex idx = currentIndex(); int pId = idx.data(ArchiveModel::SeriesPartIdRole).toInt(); QList curMetadata = c->archiveTreeModel()->metadataList(pId); e.setMetadata(curMetadata); int res = e.exec(); if(res == QDialog::Accepted){ QList data = e.metadata(); mModel->setMetadata(pId, data); c->treeSelectionChanged(QItemSelection(), QItemSelection()); } } void ArchiveTree::impossible(const QString msg){ QMessageBox::critical(this, tr("Error"), msg); } ArchiveFiles::ArchiveFiles(const QString &headerSettings, QWidget *parent) : SmTreeView(headerSettings, parent){ setEditTriggers(QAbstractItemView::NoEditTriggers); } ArchiveProxy::ArchiveProxy(QObject *parent) : QSortFilterProxyModel(parent) {} void ArchiveProxy::setFilter(const QString &filter, int sortOrder){ mFilter = QRegExp(filter); mSortOrder = sortOrder; invalidateFilter(); } bool ArchiveProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { if(mFilter.isEmpty()){ return true; } QModelIndex nameIdx = sourceModel()->index(sourceRow, ArchiveModel::Name, sourceParent); ArchiveModel *model = qobject_cast(sourceModel()); return model->matchRecursive(nameIdx, mFilter); } ArchiveFilesProxy::ArchiveFilesProxy(QObject *parent) : QSortFilterProxyModel(parent) {} /* Delegate for file type */ FileTypeDelegate::FileTypeDelegate(QObject *parent) : QStyledItemDelegate(parent){ mFiletypeMap = SmGlobals::instance()->filetypeMap(); } QString FileTypeDelegate::displayText(const QVariant &value, const QLocale &locale) const{ Q_UNUSED(locale); int type = value.toInt(); QString retval = mFiletypeMap.value(type); if(!retval.isEmpty()){ return retval; } return tr("n/a"); } QWidget *FileTypeDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const{ Q_UNUSED(option); Q_UNUSED(index); QComboBox *retval = new QComboBox(parent); retval->addItems(mFiletypeMap.values()); return retval; } /* Mapping Editor Widget */ MappingEditorWidget::MappingEditorWidget(const QString &caption, QWidget *parent) : QWidget(parent), mCaption(caption){ // the view mModel = new QStandardItemModel(this); mModel->setHorizontalHeaderItem(0, new QStandardItem(mCaption)); mProxy = new QSortFilterProxyModel(this); mProxy->setSourceModel(mModel); QVBoxLayout *viewLayout = new QVBoxLayout; mView = new SmTreeView; viewLayout->addWidget(mView); mView->setModel(mProxy); mView->setSortingEnabled(true); // line editor QHBoxLayout *editLayout = new QHBoxLayout; QLabel *l2 = new QLabel(tr("Add:")); mEditor = new QLineEdit; mCompleter = new QCompleter(this); mCompleterModel = new QStringListModel(this); mCompleter->setModel(mCompleterModel); mEditor->setCompleter(mCompleter); editLayout->addWidget(l2); editLayout->addWidget(mEditor); // edit buttons QHBoxLayout *editButtonLayout = new QHBoxLayout; editButtonLayout->addStretch(); mRemove = new QPushButton(tr("&Remove")); connect(mRemove, SIGNAL(clicked()), this, SLOT(removeItem())); editButtonLayout->addWidget(mRemove); mAdd = new QPushButton(tr("&Add")); connect(mAdd, SIGNAL(clicked()), this, SLOT(addItem())); editButtonLayout->addWidget(mAdd); // main layout QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(viewLayout); mainLayout->addLayout(editLayout); mainLayout->addLayout(editButtonLayout); setLayout(mainLayout); } void MappingEditorWidget::fillCompleter(const QStringList &completions){ mCompleterModel->setStringList(completions); } void MappingEditorWidget::setCurrentItems(const QStringList &items){ foreach(QString i, items){ QStandardItem *item = new QStandardItem(i); item->setIcon(QIcon(":/huge_bra.png")); mModel->appendRow(item); } } QStringList MappingEditorWidget::items() const{ QStringList retval; for(int i = 0; i < mModel->rowCount(); ++i){ QStandardItem *item = mModel->item(i); retval << item->text(); } return retval; } void MappingEditorWidget::clear(){ mModel->clear(); mModel->setHorizontalHeaderItem(0, new QStandardItem(mCaption)); mEditor->clear(); } void MappingEditorWidget::addItem(){ QString itemName = mEditor->text(); if(itemName.isEmpty()){ return; } QList found = mModel->findItems(itemName, Qt::MatchFixedString); if(found.size()){ return; } QStandardItem *newItem = new QStandardItem(itemName); newItem->setIcon(QIcon(":/huge_bra.png")); mModel->appendRow(newItem); mProxy->sort(0, mProxy->sortOrder()); mEditor->clear(); } void MappingEditorWidget::removeItem(){ QModelIndexList sel = mView->selectionModel()->selectedRows(); if(sel.isEmpty()){ return; } QModelIndex real = mProxy->mapToSource(sel.first()); mModel->removeRow(real.row()); } /* Mapping Editor */ MappingEditor::MappingEditor(const QString &caption, QWidget *parent, Qt::WindowFlags f) : QDialog(parent, f) { // Mapping Editor Widget mWidget = new MappingEditorWidget(caption); // buttons QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addStretch(); mCancel = new QPushButton(tr("Cancel")); connect(mCancel, SIGNAL(clicked()), this, SLOT(reject())); buttonLayout->addWidget(mCancel); mAccept = new QPushButton(tr("Accept")); connect(mAccept, SIGNAL(clicked()), this, SLOT(accept())); buttonLayout->addWidget(mAccept); // dialog layout QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(mWidget); mainLayout->addLayout(buttonLayout); setLayout(mainLayout); setMinimumWidth(300); } /* Metadata Editor */ MetadataEditor::MetadataEditor(QWidget *parent, Qt::WindowFlags f) : QDialog(parent, f){ QLabel *captionL = new QLabel(tr("Edit Metadata")); QFormLayout *dataLayout = new QFormLayout; mReleaseYear = new QSpinBox; mReleaseYear->setMinimum(1900); mReleaseYear->setMaximum(3000); dataLayout->addRow(tr("&Release Year"), mReleaseYear); mSource = new QComboBox; mSource->setEditable(true); dataLayout->addRow(tr("&Source"), mSource); mRelGroup = new QComboBox; mRelGroup->setEditable(true); dataLayout->addRow(tr("Release &Group"), mRelGroup); mSubject = new QLineEdit; dataLayout->addRow(tr("Sub&ject"), mSubject); mEncoderOpts = new QLineEdit; dataLayout->addRow(tr("&Encoder Opts."), mEncoderOpts); mPasses = new QSpinBox; mPasses->setMinimum(0); mPasses->setMaximum(10); dataLayout->addRow(tr("&Passes"), mPasses); QVBoxLayout *commentLayout = new QVBoxLayout; QLabel *commentL = new QLabel(tr("Comment")); commentLayout->addWidget(commentL); mComment = new QTextEdit; commentLayout->addWidget(mComment); QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addStretch(); mCancel = new QPushButton(tr("Cancel")); connect(mCancel, SIGNAL(clicked()), this, SLOT(reject())); buttonLayout->addWidget(mCancel); mAccept = new QPushButton(tr("Accept")); connect(mAccept, SIGNAL(clicked()), this, SLOT(accept())); buttonLayout->addWidget(mAccept); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(captionL); mainLayout->addLayout(dataLayout); mainLayout->addLayout(commentLayout); mainLayout->addLayout(buttonLayout); setLayout(mainLayout); setMinimumWidth(400); } void MetadataEditor::setSources(const QStringList &sources){ mSource->addItems(sources); } void MetadataEditor::setReleaseGroups(const QStringList &relGroups){ mRelGroup->addItems(relGroups); } void MetadataEditor::setMetadata(const QList &data){ mReleaseYear->setValue(data.at(ArchiveModel::ReleaseYear).toInt()); mSource->setCurrentText(data.at(ArchiveModel::Source).toString()); mSubject->setText(data.at(ArchiveModel::Subject).toString()); mRelGroup->setCurrentText(data.at(ArchiveModel::ReleaseGroup).toString()); mEncoderOpts->setText(data.at(ArchiveModel::EncoderOpts).toString()); mPasses->setValue(data.at(ArchiveModel::Passes).toInt()); mComment->setText(data.at(ArchiveModel::Comment).toString()); mAdded = data.at(ArchiveModel::Added).toDate(); } QList MetadataEditor::metadata() const{ QList retval; for(int i = 0; i < ArchiveModel::MetadataNumFields; ++i){ retval << QVariant(); } retval[ArchiveModel::ReleaseYear] = mReleaseYear->value(); retval[ArchiveModel::Source] = mSource->currentText(); retval[ArchiveModel::Subject] = mSubject->text(); retval[ArchiveModel::ReleaseGroup] = mRelGroup->currentText(); retval[ArchiveModel::EncoderOpts] = mEncoderOpts->text(); retval[ArchiveModel::Passes] = mPasses->value(); retval[ArchiveModel::Comment] = mComment->toPlainText(); retval[ArchiveModel::Added] = mAdded; return retval; } /* Delegate for File no. */ QString FileNoDelegate::displayText(const QVariant &value, const QLocale &locale) const{ Q_UNUSED(locale); int no = value.toInt(); if(no){ return QString::number(no); } return QString(); } QWidget *FileNoDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const{ Q_UNUSED(option); QSpinBox *retval = new QSpinBox(parent); int no = index.data().toInt(); retval->setValue(no); return retval; } /* Delegate for Dvd no. */ QString DvdNoDelegate::displayText(const QVariant &value, const QLocale &locale) const{ Q_UNUSED(locale); int no = value.toInt(); if(no < 0){ return tr("(local)"); } QString retval = QString(tr("#%1")).arg(QString::number(no)); return retval; } /* Delegate for size */ QString SizeDelegate::displayText(const QVariant &value, const QLocale &locale) const{ // locale.toString() doesn't work, maybe b/c it's const Q_UNUSED(locale); QLocale l; return l.toString(value.toLongLong()); } /* Delegate for duration */ QString DurationDelegate::displayText(const QVariant &value, const QLocale &locale) const{ Q_UNUSED(locale); if(value.toString().contains("x")){ return value.toString(); } qint64 secs = value.toInt(); if(secs == 0){ return tr("n/a"); } Helper::Duration dur(secs); return dur.toString(); } /* Empty delegate */ QString EmptyDelegate::displayText(const QVariant &value, const QLocale &locale) const{ int i = value.toInt(); if(i < 1){ return QString(); } return QStyledItemDelegate::displayText(value, locale); }