/*
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 "filepropertiesdialog.h"
#include "smglobals.h"
#include "filestreemodel.h"
#include "seriestreewidget.h"
#include "seriestreemodel.h"
#include "helper.h"
#include "pictureviewer2.h"
#include "filepropertiesdialog.h"
#include "hoverwindow.h"
#include "seriestreemodel.h"
#include "seriesmetadatamodel.h"
#include "framecache.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);
mPropDlg = new FilePropertiesDialog(this);
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 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()){
//create target dir acc. to seriesName
QString seriesName = seriesIdx.data(SeriesTreeModel::NameRole).toString();
QString dirName = seriesName.replace(' ', ".");
QDir target(burnDir);
target.mkdir(dirName);
//move selected file
QString selSource = i.data(FilesTreeModel::FullPathRole).toString();
QString selTarget = QString("%1/%2/%3").arg(burnDir).arg(dirName).arg(i.data(FilesTreeModel::FileNameRole).toString());
QFile::rename(selSource, selTarget);
//copy covers
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));
switch(fileType){
case FilesTreeModel::FrontCover:
case FilesTreeModel::BackCover:
case FilesTreeModel::GeneralCover:
QFile::copy(sourceFile, targetFile);
break;
default:
;
}
}
}
}
}
}
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){
QList 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);
mPropDlg->metaWidget()->setSeriesPartId(real.data(FilesTreeModel::SeriesPartIdRole).toInt());
if(mimeType.startsWith("video")){
QList > fileData = mModel->streamInfo(real);
mPropDlg->setFileName(real.data(FilesTreeModel::FileNameRole).toString());
mPropDlg->setStreamData(fileData);
}else if(mimeType.startsWith("image")){
QMap imageData = mModel->pictureInfo(real);
mPropDlg->addData("Image data", imageData);
QMap textData = mModel->pictureMetaInfo(real);
if(!textData.isEmpty()){
mPropDlg->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);
mPropDlg->setFileName(label);
}
mPropDlg->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){
mModel->setData(curIdx, value, Qt::EditRole);
}
mModel->refresh();
mView->expandAll();
}
}
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;
QModelIndex candidate = mModel->fileSizeLessThan(neededSize);
QModelIndex real = mProxy->mapFromSource(candidate);
if(!real.isValid() || mView->selectionModel()->isSelected(real)){
QMessageBox::critical(this, tr("Error"), tr("Out of suggestions!"));
return;
}
mView->selectionModel()->select(real, QItemSelectionModel::Select | QItemSelectionModel::Rows);
mView->scrollTo(real, QAbstractItemView::PositionAtCenter);
}
void FilesTreeWidget::playSelected(){
QStringList fullPaths;
QModelIndexList selected = mView->selectionModel()->selectedRows();
if(selected.isEmpty()){
return;
}
foreach(QModelIndex idx, selected){
fullPaths << idx.data(FilesTreeModel::FullPathRole).toString();
}
playItems(fullPaths);
}
void FilesTreeWidget::moveToSeries(){
SeriesTreeWidget *vWidget = SmGlobals::instance()->seriesTreeWidget();
QModelIndex sel = vWidget->seriesTree()->selectionModel()->currentIndex();
QModelIndex real = vWidget->seriesProxy()->mapToSource(sel);
QModelIndexList nfiles = mView->selectionModel()->selectedRows();
QList files;
foreach (QModelIndex i, nfiles){
files << i;
}
FileMoveDialog fmd(real, files, 0, 0);
fmd.exec();
}
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, SeriesTreeModel::Name);
QString seriesString = seriesPartIdx.data().toString();
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);
mPictureViewer->show();
return;
}else if(mimeType.startsWith("video")){
playItems(QStringList() << file);
}
}else{
QString msg = QString(tr("File %1 not found!")).arg(file);
QMessageBox::critical(this, tr("Error"), msg);
}
}
void FilesTreeWidget::playItems(const QStringList &paths){
if(paths.isEmpty()){
return;
}
QStringList existing;
foreach(QString p, paths){
QFileInfo fi(p);
if(fi.exists()){
existing << p;
}
}
if(existing.isEmpty()){
return;
}
QPair pData = Helper::programData("movieviewer", QString());
QString prog = pData.first;
QStringList args = pData.second;
args << existing;
QProcess::startDetached(prog, args);
}
FilesTreeView::FilesTreeView(QWidget *parent) : SmTreeView("ui/headerpos", parent), mHoverPics(false){
setAttribute(Qt::WA_Hover);
mHoverWin = new HoverWindow(this);
}
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();
readHeaderConfig();
}
void FilesTreeView::writeSettings(){
writeHeaderConfig();
}
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 SmTreeView::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 SmTreeView::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->displayMode() == FilesTreeModel::Burn){
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, SeriesTreeModel::Name);
QString retval = QString(tr("%1
")).arg(seriesPartIdx.data().toString());
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("Release Year | %1 |
")).arg(QString::number(metaData.at(SeriesMetadataModel::ReleaseYear).toInt())));
retval.append(QString(tr("Encoding Passes | %1 |
")).arg(QString::number(metaData.at(SeriesMetadataModel::Passes).toInt())));
retval.append(QString(tr("Release Group | %1 |
")).arg(metaData.at(SeriesMetadataModel::ReleaseGroup).toString()));
retval.append(QString(tr("Source Medium | %1 |
")).arg(metaData.at(SeriesMetadataModel::SourceMedium).toString()));
retval.append(QString(tr("Usenet Subject | %1 |
")).arg(metaData.at(SeriesMetadataModel::Subject).toString()));
retval.append(QString(tr("Encoder Options | %1 |
")).arg(metaData.at(SeriesMetadataModel::EncoderOpts).toString()));
QDate dateAdded = metaData.at(SeriesMetadataModel::Added).toDate();
retval.append(QString(tr("Added | %1 |
")).arg(dateAdded.toString(Qt::ISODate)));
retval.append(QString(tr("Comment | %1 |
")).arg(metaData.at(SeriesMetadataModel::Comment).toString()));
retval.append("
");
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);
}
FileMoveDialog::FileMoveDialog(QPersistentModelIndex selected, QList files, QWidget *parent, Qt::WindowFlags f) : QDialog(parent, f), mFiles(files){
QLabel *l1 = new QLabel(tr("Select destination"));
QVBoxLayout *selectionLayout = new QVBoxLayout;
selectionLayout->addWidget(l1);
mTree = new SmTreeView;
mModel = static_cast(SmGlobals::instance()->model("SeriesModel"));
mTree->setModel(mModel);
selectionLayout->addWidget(mTree);
for(int i = 1; i < SeriesTreeModel::NumFields; ++i){
mTree->setColumnHidden(i, true);
}
mTree->resizeColumnToContents(0);
mTree->setAlternatingRowColors(true);
mTree->expand(selected);
mTree->scrollTo(selected, QAbstractItemView::PositionAtTop);
QHBoxLayout *buttonLayout = new QHBoxLayout;
mOk = new QPushButton(tr("Ok"));
connect(mOk, SIGNAL(clicked()), this, SLOT(accept()));
mCancel = new QPushButton(tr("Cancel"));
connect(mCancel, SIGNAL(clicked()), this, SLOT(reject()));
buttonLayout->setAlignment(Qt::AlignRight);
buttonLayout->addWidget(mOk);
buttonLayout->addWidget(mCancel);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(selectionLayout);
mainLayout->addLayout(buttonLayout);
setLayout(mainLayout);
}
void FileMoveDialog::accept(){
if(mFiles.isEmpty()){
return;
}
FilesTreeModel *filesModel = static_cast(SmGlobals::instance()->model("FilesModel"));
int newSeriesIdx = mTree->selectionModel()->currentIndex().data(SeriesTreeModel::SeriesPartIdRole).toInt();
filesModel->updateSeries(newSeriesIdx, mFiles);
QDialog::accept();
}