/* 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 "seriestreewidget.h" #include "smtreemodel.h" #include "smglobals.h" #include "seriestreemodel.h" #include "filestreemodel.h" #include "helper.h" #include "hoverwindow.h" SeriesTreeWidget::SeriesTreeWidget(QWidget *parent) : QWidget(parent){ //filter bar QLabel *l1 = new QLabel(tr("&Filter:")); mFilterEdit = new QLineEdit; l1->setBuddy(mFilterEdit); mFilter = new QPushButton(tr("Filter")); mClear = new QPushButton(tr("Clear")); QHBoxLayout *filterLayout = new QHBoxLayout; connect(mFilter, SIGNAL(clicked()), this, SLOT(filter())); connect(mFilterEdit, SIGNAL(returnPressed()), this, SLOT(filter())); connect(mClear, SIGNAL(clicked()), this, SLOT(clearFilter())); filterLayout->addWidget(l1); filterLayout->addWidget(mFilterEdit); filterLayout->addWidget(mFilter); filterLayout->addWidget(mClear); //the view mView = new SeriesTreeView; mProxy = new SeriesTreeSortModel(this); mModel = static_cast(SmGlobals::instance()->model("SeriesModel")); mProxy->setSourceModel(mModel); mView->setModel(mProxy); mView->setSortingEnabled(true); for(int i = 1; i < 5 ;++i){ mView->setColumnHidden(i, true); } mView->resizeColumnToContents(0); mView->setSelectionMode(QAbstractItemView::ExtendedSelection); mView->setEditTriggers(QAbstractItemView::NoEditTriggers); mView->setExpandsOnDoubleClick(false); connect(mModel, SIGNAL(needResort()), this, SLOT(resort())); connect(mView, SIGNAL(expanded(QModelIndex)), this, SLOT(itemExpanded(QModelIndex))); connect(mView, SIGNAL(collapsed(QModelIndex)), this, SLOT(itemCollaped(QModelIndex))); //cover dialog mCoverDialog = new AddCoverDialog(this); //layout QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(filterLayout); mainLayout->addWidget(mView); setLayout(mainLayout); } void SeriesTreeWidget::newSeries(){ QList data; data << tr("") << QVariant() << QVariant() << QVariant() << SeriesTreeModel::NewSeries; if(mModel->addRow(data, QModelIndex())){ QModelIndex newRow = mModel->index(mModel->rowCount(QModelIndex()) - 1, 0, QModelIndex()); if(newRow.isValid()){ QModelIndex proxyIndex = mProxy->mapFromSource(newRow); mView->selectionModel()->select(proxyIndex, QItemSelectionModel::ClearAndSelect); mView->edit(proxyIndex); } } } void SeriesTreeWidget::seriesAdded(QString seriesName, int seriesPart, bool resort){ if(resort){ mProxy->invalidate(); foreach(QString e, mExpandedItems){ QModelIndex idx = mProxy->mapFromSource(mModel->findValue(e)); mView->expand(idx); } } QModelIndex destIdx = mModel->findValue(seriesName); if(destIdx.isValid()){ QModelIndex proxySeries = mProxy->mapFromSource(destIdx); mView->expand(proxySeries); mView->scrollTo(proxySeries, QAbstractItemView::PositionAtCenter); QModelIndex seriesPartIdx = mModel->findValue(seriesPart, destIdx, SeriesTreeModel::SeriesPart); if(seriesPartIdx.isValid()){ destIdx = seriesPartIdx; } } if(destIdx.isValid()){ QModelIndex proxyIndex = mProxy->mapFromSource(destIdx); mView->selectionModel()->select(proxyIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current | QItemSelectionModel::Rows); } } void SeriesTreeWidget::deleteFromSeries(){ QModelIndexList selected = mView->selectionModel()->selectedRows(); if(selected.isEmpty()){ return; } QList series; QList parts; foreach(QModelIndex s, selected){ QModelIndex real = mProxy->mapToSource(s); if(real.data(SeriesTreeModel::TypeRole).toInt() == SeriesTreeModel::Series || real.data(SeriesTreeModel::TypeRole).toInt() == SeriesTreeModel::NewSeries){ series << QPersistentModelIndex(real); } if(real.data(SeriesTreeModel::TypeRole).toInt() == SeriesTreeModel::Part){ parts << QPersistentModelIndex(real); } } QList removeParts; foreach(QPersistentModelIndex s, parts){ if(series.contains(s.parent())){ removeParts << s; } } foreach(QPersistentModelIndex s, removeParts){ parts.removeAll(s); } QFileInfoList files; foreach(QPersistentModelIndex s, series){ files.append(mModel->findFiles(s)); } foreach(QPersistentModelIndex p, parts){ files.append(mModel->findFiles(p)); } QString message; if(!files.isEmpty()){ message = QString(tr("

This operation will delete %1 file(s) permanently:

")).arg(files.count()); message.append(tr("
    ")); int ctr = 0; foreach(QFileInfo f, files){ ++ctr; QString li = QString(tr("
  • %1
  • ")).arg(f.absoluteFilePath()); message.append(li); if(ctr == 20){ QString elide = QString(tr("
  • (%1 more files)
  • ")).arg(files.count() - ctr); message.append(elide); break; } } message.append(tr("
")); message.append(tr("

Continue?

")); }else{ message = QString(tr("

This operation will delete no files. Continue?

")); } int retval = QMessageBox::critical(this, tr("Delete Series"), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if(retval == QMessageBox::Yes){ foreach(QPersistentModelIndex s, series){ mModel->deleteFromSeries(s); } foreach(QPersistentModelIndex s, parts){ mModel->deleteFromSeries(s); } } } void SeriesTreeWidget::readSettings(){ QSettings s; QStringList expanded = s.value("archive/expanded").toStringList(); expandItems(expanded); int sortOrder = s.value("archive/sortorder", Qt::DescendingOrder).toInt(); mView->sortByColumn(0, static_cast(sortOrder)); QString selectedSeries = s.value("archive/selectedseries").toString(); QModelIndex seriesIdx = mModel->findValue(selectedSeries); if(seriesIdx.isValid()){ QModelIndex real = mProxy->mapFromSource(seriesIdx); mView->scrollTo(real, QAbstractItemView::PositionAtCenter); mView->selectionModel()->select(real, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current | QItemSelectionModel::Rows); mView->setCurrentIndex(real); } } void SeriesTreeWidget::writeSettings(){ QSettings s; s.setValue("archive/expanded", mExpandedItems); s.setValue("archive/sortorder", mProxy->sortOrder()); QModelIndexList selected = mView->selectionModel()->selectedRows(); if(!selected.isEmpty()){ s.setValue("archive/selectedseries", selected.at(0).data(SeriesTreeModel::NameRole).toString()); } } void SeriesTreeWidget::expandCurrent(){ QModelIndexList selected = mView->selectionModel()->selectedRows(); if(selected.isEmpty()){ return; } QModelIndex idx = selected.at(0); mView->expand(idx); } void SeriesTreeWidget::filter(){ QString filterText = mFilterEdit->text(); if(filterText.isEmpty()){ clearFilter(); return; } QRegExp pattern("^([a-z]):\\s+"); QString op; if(pattern.indexIn(filterText) != -1){ op = pattern.cap(1); filterText.remove(pattern); } if(op == "a"){ mModel->setMappingFilter(filterText, "actor"); }else if(op == "g"){ mModel->setMappingFilter(filterText, "genre"); }else if(op == "q"){ mModel->setFileFilter(filterText, "siquality", SeriesTreeModel::NumericQuery); }else if(op == "n"){ mModel->setFileFilter(filterText, "tfilename", SeriesTreeModel::TextQuery); }else if(op == "d"){ mModel->setFileFilter(filterText, "idvd", SeriesTreeModel::NumericQuery); }else if(op == "s"){ mModel->setFileFilter(filterText, "bisize", SeriesTreeModel::NumericQuery); }else{ mProxy->setFilterRegExp(mFilterEdit->text()); } } void SeriesTreeWidget::clearFilter(){ mModel->clearFilter(); mProxy->setFilterRegExp(QString()); mFilterEdit->clear(); expandItems(mExpandedItems); } void SeriesTreeWidget::resort(){ mView->sortByColumn(0, mProxy->sortOrder()); mView->scrollTo(mView->selectionModel()->currentIndex(), QAbstractItemView::PositionAtCenter); } void SeriesTreeWidget::itemExpanded(const QModelIndex &what){ mExpandedItems << what.data(SeriesTreeModel::NameRole).toString(); } void SeriesTreeWidget::itemCollaped(const QModelIndex &what){ QString itemName = what.data(SeriesTreeModel::NameRole).toString(); if(mExpandedItems.contains(itemName)){ mExpandedItems.removeAll(itemName); } } void SeriesTreeWidget::expandItems(const QStringList &items){ foreach(QString s, items){ QModelIndex idx = mProxy->mapFromSource(mModel->findValue(s)); mView->expand(idx); } } void SeriesTreeWidget::editItem(){ QModelIndex current = mView->selectionModel()->currentIndex(); if(current.isValid()){ mView->edit(current); } } void SeriesTreeWidget::addCover(){ QModelIndexList selected = mView->selectionModel()->selectedRows(); if(selected.isEmpty()){ return; } if(selected.count() > 1){ QMessageBox::critical(this, tr("Error"), tr("Please don't select more than one item when adding covers.")); return; } QModelIndex real = mProxy->mapToSource(selected.at(0)); if(real.data(SeriesTreeModel::TypeRole).toInt() != SeriesTreeModel::Part){ QMessageBox::critical(this, tr("Error"), tr("Please select a series part when adding covers.")); return; } int retval = mCoverDialog->exec(); if(retval == QDialog::Accepted){ QString fileName = mCoverDialog->file(); QFileInfo fi(fileName); if(fi.exists()){ FilesTreeModel *filesModel = static_cast(SmGlobals::instance()->model("FilesModel")); int seriesPartId = real.data(SeriesTreeModel::SeriesPartIdRole).toInt(); int type = mCoverDialog->fileType(); if(filesModel->addFile(fileName, type, 0, -1, seriesPartId, -1)){ QString md5sum = Helper::md5Sum(fileName); QString targetFile = Helper::createArchivePath(fileName, md5sum); QFile::rename(fileName, targetFile); filesModel->setIds(QList() << seriesPartId); emit filesReload(); } } } } SeriesTreeView::SeriesTreeView(QWidget *parent) : QTreeView(parent), mHoverWin(new HoverWindow), mHover(false) { setAttribute(Qt::WA_Hover); readConfig(); } void SeriesTreeView::readConfig(){ QSettings s; mHover = s.value("ui/hoverarchive", true).toBool(); mHoverWin->setWindowOpacity(s.value("ui/hoveropacity", 10).toFloat() / 10.0); } void SeriesTreeView::contextMenuEvent(QContextMenuEvent *e){ QMenu contextMenu(this); foreach(QAction *a, actions()){ contextMenu.addAction(a); } contextMenu.exec(e->globalPos()); } bool SeriesTreeView::event(QEvent *e){ QSettings s; if(!s.value("ui/hoverarchive").toBool()){ return true; } QModelIndex curIdx; QHoverEvent *hEvent = static_cast(e); QPoint hotSpot(hEvent->pos().x(), hEvent->pos().y() - SmGlobals::instance()->cursorOffset()); QPoint globalPos = mapToGlobal(hotSpot); QPoint where = globalPos + QPoint(30, 30); curIdx = indexAt(hotSpot); if((e->type() == QEvent::HoverEnter) || (e->type() == QEvent::HoverMove)){ if(!curIdx.isValid()){ return true; } if(curIdx.parent() != QModelIndex()){ mHoverWin->setVisible(false); mCurHover = QModelIndex(); return true; } } if(e->type() == QEvent::HoverEnter){ mCurHover = curIdx; mHoverWin->setContent(curIdx.data(Qt::DisplayRole).toString(), children(curIdx)); mHoverWin->setPos(where); mHoverWin->setVisible(true); return true; } if(e->type() == QEvent::HoverMove){ if(curIdx != mCurHover){ mCurHover = curIdx; mHoverWin->setContent(curIdx.data(Qt::DisplayRole).toString(), children(curIdx)); mHoverWin->setVisible(false); mHoverWin->setPos(where); mHoverWin->setVisible(true); return true; }else{ mHoverWin->setPos(where); return true; } } if(e->type() == QEvent::HoverLeave){ mHoverWin->setVisible(false); return true; } return QTreeView::event(e); } QStringList SeriesTreeView::children(const QModelIndex &idx) const{ if(!idx.isValid()){ return QStringList(); } QStringList retval; QModelIndex curIdx = idx.child(0,0); while(curIdx.isValid()){ retval << curIdx.data(Qt::DisplayRole).toString(); curIdx = idx.model()->index(curIdx.row() + 1, curIdx.column(), curIdx.parent()); } return retval; } SeriesTreeSortModel::SeriesTreeSortModel(QObject *parent) : QSortFilterProxyModel(parent) {} bool SeriesTreeSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const{ if(left.data(SeriesTreeModel::TypeRole).toInt() == SeriesTreeModel::Part && right.data(SeriesTreeModel::TypeRole).toInt() == SeriesTreeModel::Part){ return left.data(SeriesTreeModel::SeriesPartRole).toInt() < right.data(SeriesTreeModel::SeriesPartRole).toInt(); } return QSortFilterProxyModel::lessThan(left, right); } AddCoverDialog::AddCoverDialog(QWidget *parent, Qt::WindowFlags f) : QDialog(parent, f){ //File selection QLabel *l1 = new QLabel(tr("Select file")); mFile = new QLineEdit; QPushButton *mSelectFile = new QPushButton(tr("Browse...")); connect(mSelectFile, SIGNAL(clicked()), this, SLOT(selectFile())); QHBoxLayout *fileLayout = new QHBoxLayout; fileLayout->addWidget(l1); fileLayout->addWidget(mFile); fileLayout->addWidget(mSelectFile); //file type mFilesModel = static_cast(SmGlobals::instance()->model("FilesModel")); QHash coverTypes = mFilesModel->coverTypes(); mFileType = new QComboBox; QStringList types; foreach(QString t, coverTypes.values()){ types << t; } connect(mFileType, SIGNAL(currentIndexChanged(QString)), this, SLOT(typeChanged(QString))); mFileType->addItems(types); QHBoxLayout *typesLayout = new QHBoxLayout; typesLayout->setAlignment(Qt::AlignCenter); typesLayout->addWidget(mFileType); //buttons mOk = new QPushButton(tr("Ok")); connect(mOk, SIGNAL(clicked()), this, SLOT(accept())); mCancel = new QPushButton(tr("Cancel")); connect(mCancel, SIGNAL(clicked()), this, SLOT(reject())); QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addStretch(); buttonLayout->addWidget(mOk); buttonLayout->addWidget(mCancel); //main layout QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(fileLayout); mainLayout->addLayout(typesLayout); mainLayout->addLayout(buttonLayout); setLayout(mainLayout); } const QString AddCoverDialog::file() const { return mFile->text(); } void AddCoverDialog::selectFile(){ QString startDir = mLastOpenedDir; if(startDir.isEmpty()){ QSettings s; startDir = s.value("paths/selecteddir").toString(); } QString file = QFileDialog::getOpenFileName(this, tr("Select cover"), startDir); mFile->setText(file); QFileInfo fi(file); mLastOpenedDir = fi.absolutePath(); } void AddCoverDialog::typeChanged(const QString &type){ QHash::const_iterator it = mFilesModel->coverTypes().constBegin(); while(it != mFilesModel->coverTypes().constEnd()){ if(it.value() == type){ mCurrentType = it.key(); return; } ++it; } }