/* 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 "filestreewidget.h" #include "smglobals.h" #include "filestreemodel.h" #include "seriestreemodel.h" #include "helper.h" #include "pictureviewer.h" #include "filepropertiesdialog.h" #include "hoverwindow.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(currentChanged(QModelIndex,QModelIndex)), this, SLOT(fileSelectionChanged(QModelIndex,QModelIndex))); connect(selModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(fileSelectionChanged(QItemSelection,QItemSelection))); 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 sizeChanged(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(this); dlg.setFileName(real.data(FilesTreeModel::FileNameRole).toString()); if(mimeType.startsWith("video")){ QList > fileData = mModel->streamInfo(real); 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); } } 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; } QModelIndexList 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){ SeriesTreeModel *seriesModel = static_cast(SmGlobals::instance()->model("SeriesModel")); int nextDvdNo = seriesModel->findNextDvdNo(); value = QInputDialog::getInt(this, msg, msg, nextDvdNo, -1, 2147483647, 1, &dialogOk); } if(dialogOk){ foreach(QModelIndex curIdx, sIdxes){ mModel->setData(curIdx, value, Qt::EditRole); } } } void FilesTreeWidget::fileSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous){ Q_UNUSED(previous); int seriesPartId = current.data(FilesTreeModel::SeriesPartIdRole).toInt(); int seriesId = mSeriesModel->seriesIdByPartId(seriesPartId); int filePart = current.data(FilesTreeModel::PartNoRole).toInt(); 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 msg; if(filePart > 0){ msg = QString(tr("%1 %2 (%3)")).arg(seriesIdx.data(SeriesTreeModel::NameRole).toString()).arg(seriesNumber).arg(filePart); }else{ msg = QString(tr("%1 %2")).arg(seriesIdx.data(SeriesTreeModel::NameRole).toString()).arg(seriesNumber); } emit statusMessage(msg); } } 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->showPic(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); } } void FilesTreeWidget::fileSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected){ QModelIndexList sel = selected.indexes(); QModelIndexList desel = deselected.indexes(); foreach(QModelIndex i, sel){ if(i.column() == 0){ mSelectedSize += i.data(FilesTreeModel::SizeRole).toLongLong(); } } foreach(QModelIndex i, desel){ if(i.column() == 0){ mSelectedSize -= i.data(FilesTreeModel::SizeRole).toLongLong(); } } emit sizeChanged(mSelectedSize); } FilesTreeView::FilesTreeView(QWidget *parent) : QTreeView(parent), mHoverWin(new HoverWindow), mHoverPics(false){ setAttribute(Qt::WA_Hover); } void FilesTreeView::setModel(QAbstractItemModel *model){ QTreeView::setModel(model); for(int i = 0; i < header()->count(); ++i){ header()->setSectionHidden(i, true); } readHeaderConfig(); } void FilesTreeView::readConfig(){ 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(); } 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){ 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() - SmGlobals::instance()->cursorSize().height()); 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(!QFileInfo(curIdx.data(FilesTreeModel::FullPathRole).toString()).exists()){ 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; mCurHover = idx; if(idx.data(FilesTreeModel::FileTypeRole).toInt() == FilesTreeModel::Movie){ pm = Helper::grabFrame(idx.data(FilesTreeModel::FullPathRole).toString()); if(pm.isNull()){ return; } }else{ if(!pm.load(idx.data(FilesTreeModel::FullPathRole).toString())){ return; } } mHoverWin->setPixmap(pm); mHoverWin->setPos(); mHoverWin->setVisible(true); } 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); }