/* 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 "filestreewidget.h" #include "smglobals.h" #include "filestreemodel.h" #include "seriestreemodel.h" #include "helper.h" #include "pictureviewer2.h" #include "filepropertiesdialog.h" #include "hoverwindow.h" #include "seriestreemodel.h" #include "seriesmetadatamodel.h" FilesTreeWidget::FilesTreeWidget(QWidget *parent) : QWidget(parent), mSelectedSize(0){ //the view mView = new FilesTreeView; mView->setSelectionMode(QAbstractItemView::ExtendedSelection); mView->setEditTriggers(QAbstractItemView::NoEditTriggers); mModel = static_cast(SmGlobals::instance()->model("FilesModel")); mProxy = new FilesTreeSortModel(this); mProxy->setSourceModel(mModel); mView->setModel(mProxy); mView->setSortingEnabled(true); QItemSelectionModel *selModel = mView->selectionModel(); connect(selModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(fileSelectionChanged())); connect(mView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(itemDoubleClicked(QModelIndex))); //layout QHBoxLayout *mainLayout = new QHBoxLayout; mainLayout->addWidget(mView); setLayout(mainLayout); //globals mSeriesModel = static_cast(SmGlobals::instance()->model("SeriesModel")); mPictureViewer = SmGlobals::instance()->pictureViewer(); } void FilesTreeWidget::resetSize(){ mSelectedSize = 0; emit selectedSize(mSelectedSize); } void FilesTreeWidget::moveToBurn(){ QModelIndexList selected = mView->selectionModel()->selectedRows(); if(selected.isEmpty()){ return; } QSettings s; QString burnDir = s.value("paths/burn").toString(); if(burnDir.isEmpty()){ QMessageBox::critical(this, tr("Error"), tr("No target directory configured.")); return; } foreach(QModelIndex i, selected){ int type = i.data(FilesTreeModel::FileTypeRole).toInt(); if(type == FilesTreeModel::BackCover || type == FilesTreeModel::FrontCover || type == FilesTreeModel::GeneralCover){ continue; } int seriesPartId = i.data(FilesTreeModel::SeriesPartIdRole).toInt(); int seriesId = mSeriesModel->seriesIdByPartId(seriesPartId); if(seriesId != -1){ QModelIndex seriesIdx = mSeriesModel->findValue(seriesId, QModelIndex(), SeriesTreeModel::SeriesId); if(seriesIdx.isValid()){ QString seriesName = seriesIdx.data(SeriesTreeModel::NameRole).toString(); QString dirName = seriesName.replace(' ', "."); QDir target(burnDir); target.mkdir(dirName); const QHash files = mModel->filesBySeriesPartId(seriesPartId); foreach(QString name, files.keys()){ QString sourceFile = Helper::createArchivePath(name, files.value(name)); QString targetFile = QString("%1/%2/%3").arg(burnDir).arg(dirName).arg(name); int fileType = mModel->fileType(files.value(name)); if(fileType == -1){ continue; } if(fileType == FilesTreeModel::Movie){ QFile::rename(sourceFile, targetFile); }else{ QFile::copy(sourceFile, targetFile); } } } } } } void FilesTreeWidget::removeFiles(){ QModelIndexList selected = mView->selectionModel()->selectedRows(); if(selected.isEmpty()){ return; } QString message = QString(tr("

Really delete these file(s):

    ")); foreach(QModelIndex i, selected){ message.append(QString(tr("
  • %1
  • ")).arg(i.data(FilesTreeModel::FileNameRole).toString())); } message.append("
"); int retval = QMessageBox::critical(this, tr("Question"), message, QMessageBox::Yes | QMessageBox::No); if(retval == QMessageBox::Yes){ QModelIndexList realSelected; foreach(QModelIndex i, selected){ realSelected << mProxy->mapToSource(i); } mModel->deleteFiles(realSelected); } } void FilesTreeWidget::moveToDirectory(){ QModelIndexList selected = mView->selectionModel()->selectedRows(); if(selected.isEmpty()){ return; } QSettings s; QString startDir = s.value("paths/selecteddir", QDir::homePath()).toString(); QString dir = QFileDialog::getExistingDirectory(this, tr("Select directory"), startDir); if(!dir.isEmpty()){ foreach(QModelIndex i, selected){ QString source = i.data(FilesTreeModel::FullPathRole).toString(); QString destination = QString("%1/%2").arg(dir).arg(i.data(FilesTreeModel::FileNameRole).toString()); QFile::rename(source, destination); } } } void FilesTreeWidget::fileProperties(){ QModelIndexList selected = mView->selectionModel()->selectedRows(); if(selected.isEmpty()){ return; } QModelIndex real = mProxy->mapToSource(selected.at(0)); if(real.isValid()){ int dvd = real.data(FilesTreeModel::DvdNoRole).toInt(); if(dvd != -1){ QMessageBox::critical(this, tr("Error"), tr("File is not available!")); return; } QString fullPath = real.data(FilesTreeModel::FullPathRole).toString(); QString mimeType = Helper::mimeType(fullPath); FilePropertiesDialog dlg(real.data(FilesTreeModel::SeriesPartIdRole).toInt(), this); if(mimeType.startsWith("video")){ QList > fileData = mModel->streamInfo(real); dlg.setFileName(real.data(FilesTreeModel::FileNameRole).toString()); dlg.setStreamData(fileData); }else if(mimeType.startsWith("image")){ QMap imageData = mModel->pictureInfo(real); dlg.addData("Image data", imageData); QMap textData = mModel->pictureMetaInfo(real); if(!textData.isEmpty()){ dlg.addData("Meta data", textData); } int fileType = real.data(FilesTreeModel::FileTypeRole).toInt(); QString fileTypeName = mModel->fileTypes().value(fileType); QString seriesName = real.data(FilesTreeModel::SeriesNameRole).toString(); QString seriesPart = QString::number(real.data(FilesTreeModel::SeriesPartRole).toInt()); QString label = QString(tr("%1\n%2 for %3 %4")).arg(real.data(FilesTreeModel::FileNameRole).toString()).arg(fileTypeName).arg(seriesName).arg(seriesPart); dlg.setFileName(label); } dlg.exec(); } } void FilesTreeWidget::edit(int column){ QModelIndexList currentSel = mView->selectionModel()->selectedRows(); if(currentSel.isEmpty()){ return; } const QHash cols = mModel->editableColumns(); if(!cols.values().contains(column)){ return; } PersistenModelIndexList sIdxes; foreach(QModelIndex idx, currentSel){ QModelIndex pIdx = filesTree()->model()->index(idx.row(), column, idx.parent()); if(pIdx.isValid()){ sIdxes << mProxy->mapToSource(pIdx); } } QString msg = cols.key(column); if(column == FilesTreeModel::FileType){ QStringList fileTypes = mModel->fileTypes().values(); qSort(fileTypes); int inputIdx = fileTypes.indexOf(sIdxes.first().data().toString()); QString item = QInputDialog::getItem(this, msg, msg, fileTypes, inputIdx, false); if(!item.isEmpty()){ int fileTypeInt = mModel->fileTypes().key(item); foreach(QModelIndex curIdx, sIdxes){ mModel->setData(curIdx, fileTypeInt, Qt::EditRole); } } return; } bool dialogOk = false; int value = -1; if(column == FilesTreeModel::PartNo){ value = QInputDialog::getInt(this, msg, msg, sIdxes.first().data().toInt(), -1, 2147483647, 1, &dialogOk); } if(column == FilesTreeModel::Quality){ value = QInputDialog::getInt(this, msg, msg, sIdxes.first().data().toInt(), -1, 10, 1, &dialogOk); } if(column == FilesTreeModel::DvdNo){ int nextDvdNo = mSeriesModel->findNextDvdNo(); value = QInputDialog::getInt(this, msg, msg, nextDvdNo, -1, 2147483647, 1, &dialogOk); } if(dialogOk){ foreach(QModelIndex curIdx, sIdxes){ if(column == FilesTreeModel::DvdNo){ if(curIdx.data(FilesTreeModel::FileTypeRole).toInt() == FilesTreeModel::Movie){ QModelIndex seriesIdx = mSeriesModel->findRecursive(curIdx.data(FilesTreeModel::SeriesPartIdRole), SeriesTreeModel::SeriesPartId, mSeriesModel->index(0, 0, QModelIndex())); if(seriesIdx.isValid()){ QModelIndex isLocalIdx = mSeriesModel->index(seriesIdx.row(), SeriesTreeModel::IsLocal, seriesIdx.parent()); bool isLocal = (value > -1) ? false : true; mSeriesModel->setData(isLocalIdx, isLocal, Qt::EditRole); } } } mModel->setData(curIdx, value, Qt::EditRole); } } } void FilesTreeWidget::suggest(){ QModelIndexList currentSel = mView->selectionModel()->selectedRows(); if(currentSel.isEmpty()){ return; } qint64 size = 0; foreach(QModelIndex curIdx, currentSel){ qint64 curSize = curIdx.data(FilesTreeModel::SizeRole).value(); size += curSize; } qint64 dvdSize = SmGlobals::instance()->dvdSize(); if(size > dvdSize){ QMessageBox::critical(this, tr("Error"), tr("File too big for a single DVD")); return; } qint64 neededSize = dvdSize - size; QModelIndexList possibleFiles = mModel->fileSizeLessThan(neededSize); if(possibleFiles.isEmpty()){ QLocale l; float mb = size / 1024 / 1024; QString message = QString(tr("No file smaller than %1 MiB found")).arg(l.toString(mb, 'f', 2)); QMessageBox::critical(this, tr("Error"), message); return; } QModelIndex last; QStringList selectedFiles; foreach(QModelIndex sIdx, possibleFiles){ QModelIndex vIdx = mProxy->mapFromSource(sIdx); last = vIdx; mView->selectionModel()->select(vIdx, QItemSelectionModel::Select | QItemSelectionModel::Rows); selectedFiles << sIdx.data(FilesTreeModel::FileNameRole).toString(); } QString message = QString(tr("Selected %1 items: %2")).arg(QString::number(selectedFiles.count())).arg(selectedFiles.join(", ")); emit statusMessage(message); mView->scrollTo(last, QAbstractItemView::PositionAtCenter); } void FilesTreeWidget::fileSelectionChanged(){ QModelIndexList selected = mView->selectionModel()->selectedRows(); qint64 selSize = 0; QStringList selectedSeries; foreach(QModelIndex idx, selected){ selSize += idx.data(FilesTreeModel::SizeRole).toLongLong(); int seriesPartId = idx.data(FilesTreeModel::SeriesPartIdRole).toInt(); int seriesId = mSeriesModel->seriesIdByPartId(seriesPartId); QModelIndex seriesIdx = mSeriesModel->findValue(seriesId, QModelIndex(), SeriesTreeModel::SeriesId); if(seriesIdx.isValid()){ QModelIndex seriesPartIdx = mSeriesModel->findValue(seriesPartId, seriesIdx, SeriesTreeModel::SeriesPartId); QString seriesNumber = QString::number(seriesPartIdx.data(SeriesTreeModel::SeriesPartRole).toInt()); QString seriesString = QString("%1 %2").arg(seriesIdx.data(SeriesTreeModel::NameRole).toString()).arg(seriesNumber); if(!selectedSeries.contains(seriesString)){ selectedSeries << seriesString; } } } emit selectedSize(selSize); emit numSelected(selected.size()); emit statusMessage(QString(tr("Series: %1")).arg(selectedSeries.join(","))); } void FilesTreeWidget::itemDoubleClicked(const QModelIndex &index){ QString file = index.data(FilesTreeModel::FullPathRole).toString(); QFileInfo fi(file); if(fi.exists()){ QString mimeType = Helper::mimeType(file); if(mimeType.startsWith("image")){ mPictureViewer->setFile(file); return; }else{ int dvdNo = index.data(FilesTreeModel::DvdNoRole).toInt(); if(dvdNo != -1){ QString msg = QString(tr("%1 is archived on DVD %2.")).arg(index.data(FilesTreeModel::FileNameRole).toString()).arg(QString::number(index.data(FilesTreeModel::DvdNoRole).toInt())); QMessageBox::critical(this, tr("Error"), msg); return; } QPair data = Helper::programData("movieviewer", QString()); if(data.first.isEmpty()){ QMessageBox::critical(this, tr("Error"), tr("No viedeo viewer configured.")); return; } QString program = data.first; QStringList args = data.second; args << file; QProcess::startDetached(program, args); } } } FilesTreeView::FilesTreeView(QWidget *parent) : QTreeView(parent), mHoverPics(false){ setAttribute(Qt::WA_Hover); mHoverWin = new HoverWindow(this); } void FilesTreeView::setModel(QAbstractItemModel *model){ QTreeView::setModel(model); for(int i = 0; i < header()->count(); ++i){ header()->setSectionHidden(i, true); } readHeaderConfig(); } void FilesTreeView::readSettings(){ QSettings s; mHoverPics = s.value("ui/hoverpics", true).toBool(); mHoverWin->setWindowOpacity(s.value("ui/hoveropacity", 10).toFloat() / 10.0); mHoverMovies = s.value("ui/hovermovies", true).toBool(); mCursorOffest = s.value("ui/cursoroffset").toInt(); } void FilesTreeView::readHeaderConfig(){ QSettings s; QByteArray headerPos = s.value("ui/headerpos").toByteArray(); if(!headerPos.isEmpty()){ header()->restoreState(headerPos); } } void FilesTreeView::writeHeaderConfig(){ QSettings s; s.setValue("ui/headerpos", header()->saveState()); } void FilesTreeView::toggleHeader(QObject *action){ QAction *a = qobject_cast(action); Q_ASSERT(a); int logicalIndex = a->data().toInt(); QHeaderView *hv = header(); hv->setSectionHidden(logicalIndex, !a->isChecked()); } void FilesTreeView::contextMenuEvent(QContextMenuEvent *event){ mHoverWin->hide(); QMenu ctxMenu; foreach(QAction *a, actions()){ ctxMenu.addAction(a); } ctxMenu.exec(event->globalPos()); } bool FilesTreeView::event(QEvent *e){ QHoverEvent *hEvent = static_cast(e); if(!hEvent){ return QTreeView::event(e); } QPoint hotSpot(hEvent->pos().x(), hEvent->pos().y() + mCursorOffest); QModelIndex curIdx = indexAt(hotSpot); /* The whole point of this if/then/else mess is to exit as early as possible for performance reasons. We don't want the cpu to spin at 100% only because something _could_ happen... */ if((e->type() == QEvent::HoverEnter) || (e->type() == QEvent::HoverMove)){ if(!curIdx.isValid() || (!curIdx.column() == 0)){ return exitHover(); } bool toInt; int fileType = curIdx.data(FilesTreeModel::FileTypeRole).toInt(&toInt); if(!toInt){ return exitHover(); } if(fileType == FilesTreeModel::Movie){ if(!mHoverMovies){ return exitHover(); } }else{ if(!mHoverPics){ return exitHover(); } } } if(e->type() == QEvent::HoverEnter){ doHover(curIdx); return true; } if(e->type() == QEvent::HoverMove){ if(curIdx != mCurHover){ doHover(curIdx); return true; }else{ mHoverWin->setPos(); return true; } } if(e->type() == QEvent::HoverLeave){ return exitHover(); } return QTreeView::event(e); } bool FilesTreeView::exitHover(bool exitVal){ mHoverWin->setVisible(false); mCurHover = QModelIndex(); return exitVal; } void FilesTreeView::doHover(const QModelIndex &idx){ QPixmap pm; bool scale = true; mCurHover = idx; if(idx.data(FilesTreeModel::FileTypeRole).toInt() == FilesTreeModel::Movie){ pm = SmGlobals::instance()->frameCache()->entry(idx.data(FilesTreeModel::FullPathRole).toString()); if(pm.isNull()){ return; } scale = false; FilesTreeModel *filesModel = qobject_cast(SmGlobals::instance()->model("FilesModel")); if(filesModel->mode() == SeriesTreeModel::Local){ QString filesData = fileNameText(idx); pm = annotateHover(pm, filesData); } QString metaData = metaDataText(idx); if(!metaData.isEmpty()){ pm = annotateHover(pm, metaData); } scale = false; }else{ if(!pm.load(idx.data(FilesTreeModel::FullPathRole).toString())){ return; } } mHoverWin->setPixmap(pm, scale); mHoverWin->setCaption(idx.data(FilesTreeModel::FileNameRole).toString()); mHoverWin->setPos(); mHoverWin->setVisible(true); } const QString FilesTreeView::fileNameText(const QModelIndex &idx) const{ SeriesTreeModel *seriesModel = qobject_cast(SmGlobals::instance()->model("SeriesModel")); int seriesId = seriesModel->seriesIdByPartId(idx.data(FilesTreeModel::SeriesPartIdRole).toInt()); if(seriesId == -1){ return QString(); } QModelIndex seriesIdx = seriesModel->findValue(seriesId, QModelIndex(), SeriesTreeModel::SeriesId); if(!seriesIdx.isValid()){ return QString(); } FilesTreeModel *filesModel = qobject_cast(SmGlobals::instance()->model("FilesModel")); QHash files = filesModel->filesBySeriesPartId(idx.data(FilesTreeModel::SeriesPartIdRole).toInt()); QModelIndex seriesPartIdx = seriesModel->findValue(idx.data(FilesTreeModel::SeriesPartIdRole), seriesIdx, SeriesTreeModel::SeriesPartId); QString retval = QString(tr("

%1 %2

")).arg(seriesPartIdx.data(SeriesTreeModel::NameRole).toString()).arg(QString::number(seriesPartIdx.data(SeriesTreeModel::SeriesPartRole).toInt())); retval.append(QString(tr("

Files:

"))); retval.append(tr("
    ")); QHash::const_iterator it = files.constBegin(); while(it != files.constEnd()){ retval.append(QString("
  • %1
  • ").arg(it.key())); ++it; } retval.append(tr("
")); return retval; } const QString FilesTreeView::metaDataText(const QModelIndex &idx) const{ if(!idx.isValid()){ return QString(); } SeriesMetadataModel *metaDataModel = qobject_cast(SmGlobals::instance()->model("SeriesMetadata")); int seriesPartId = idx.data(FilesTreeModel::SeriesPartIdRole).toInt(); if(!metaDataModel->hasRecord(seriesPartId)){ return QString(); } metaDataModel->populate(seriesPartId); QModelIndex mdIdx = metaDataModel->index(0, 0, QModelIndex()); QList metaData = metaDataModel->dataList(mdIdx); QString retval("

Metadata

"); retval.append(QString(tr("")).arg(QString::number(metaData.at(SeriesMetadataModel::ReleaseYear).toInt()))); retval.append(QString(tr("")).arg(QString::number(metaData.at(SeriesMetadataModel::Passes).toInt()))); retval.append(QString(tr("")).arg(metaData.at(SeriesMetadataModel::ReleaseGroup).toString())); retval.append(QString(tr("")).arg(metaData.at(SeriesMetadataModel::SourceMedium).toString())); retval.append(QString(tr("")).arg(metaData.at(SeriesMetadataModel::Subject).toString())); retval.append(QString(tr("")).arg(metaData.at(SeriesMetadataModel::EncoderOpts).toString())); QDate dateAdded = metaData.at(SeriesMetadataModel::Added).toDate(); retval.append(QString(tr("")).arg(dateAdded.toString(Qt::ISODate))); retval.append(QString(tr("")).arg(metaData.at(SeriesMetadataModel::Comment).toString())); retval.append("
Release Year%1
Encoding Passes%1
Release Group%1
Source Medium%1
Usenet Subject%1
Encoder Options%1
Added%1
Comment%1

"); return retval; } const QPixmap FilesTreeView::annotateHover(const QPixmap &hoverImage, const QString &text) const{ QTextDocument doc; doc.setHtml(text); doc.setTextWidth(hoverImage.width()); QSize newPicSize(hoverImage.width(), hoverImage.height() + doc.size().height()); QImage retImg(newPicSize, QImage::Format_ARGB32_Premultiplied); retImg.fill(0); QPainter p(&retImg); p.drawPixmap(0, 0, hoverImage); p.setRenderHint(QPainter::Antialiasing, false); p.setRenderHint(QPainter::TextAntialiasing, true); QColor bgColor(Qt::white); bgColor.setAlpha(70); QBrush brush(bgColor); p.setPen(QPen(Qt::NoPen)); p.setBrush(brush); QRect annotateRect(0, hoverImage.height(), hoverImage.width(), doc.size().height()); p.drawRect(annotateRect); p.setPen(QPen(Qt::black)); p.translate(0, hoverImage.height()); doc.drawContents(&p); return QPixmap::fromImage(retImg); } FilesTreeSortModel::FilesTreeSortModel(QObject *parent) : QSortFilterProxyModel(parent) {} // left + right are from the sourceModel() !!!! bool FilesTreeSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const{ if(left.parent() == QModelIndex()){ return false; } if(left.column() == FilesTreeModel::SizeDisplay){ return left.data(FilesTreeModel::SizeRole).toLongLong() < right.data(FilesTreeModel::SizeRole).toLongLong(); } if(left.column() == FilesTreeModel::DvdNoRole){ return left.data(FilesTreeModel::DvdNoRole).toInt() < right.data(FilesTreeModel::DvdNoRole).toInt(); } if(left.column() == FilesTreeModel::DisplayName){ if(left.data(FilesTreeModel::SeriesNameRole) == right.data(FilesTreeModel::SeriesNameRole)){ return left.data(FilesTreeModel::SeriesPartRole).toInt() < right.data(FilesTreeModel::SeriesPartRole).toInt(); } } return QSortFilterProxyModel::lessThan(left, right); }