#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fswidget.h" #include "helper.h" #include "newmoviewizard.h" #include "newpicsdialog.h" #include "fsproxy.h" #include "fsview.h" #include "viewer.h" FSWidget::FSWidget(QWidget *parent) : QWidget(parent) { mMovieWizard = new NewMovieWizard(this); mMovieWizard->setMinimumWidth(1024); mViewer = new Viewer; mNewPicsDlg = new NewPicsDialog; setPalette(qApp->palette()); setupWidget(); } void FSWidget::setupWidget(){ QToolBar *toolbar = new QToolBar; QPixmap buttplug(":/butt_plug.png"); QMatrix rotatematrix; rotatematrix.rotate(90); QIcon buttplugRight(buttplug.transformed(rotatematrix)); rotatematrix.rotate(-180); QIcon buttplugLeft(buttplug.transformed(rotatematrix)); QAction *backA = new QAction(buttplugLeft, tr("Prev. dir"), this); connect(backA, &QAction::triggered, [=] { advanceDir(-1); }); toolbar->addAction(backA); QAction *forwardA = new QAction(buttplugRight, tr("Next dir"), this); connect(forwardA, &QAction::triggered, [=] { advanceDir(1); }); toolbar->addAction(forwardA); QAction *refreshA = new QAction(QIcon(":/refresh.png"), tr("Refresh"), this); connect(refreshA, &QAction::triggered, this, &FSWidget::refresh); toolbar->addSeparator(); toolbar->addAction(refreshA); QAction *deleteFilesA = new QAction(QIcon(":/delete.png"), tr("Delete"), this); connect(deleteFilesA, &QAction::triggered, this, &FSWidget::deleteFiles); toolbar->addAction(deleteFilesA); toolbar->addSeparator(); QAction *archiveMovieA = new QAction(QIcon(":/huge_bra.png"), tr("Archive movies..."), this); connect(archiveMovieA, &QAction::triggered, this, &FSWidget::archiveMovie); connect(mMovieWizard, &NewMovieWizard::accepted, this, &FSWidget::refresh); toolbar->addAction(archiveMovieA); QAction *archivePicsA = new QAction(QIcon(":/bald_pussy.png"), tr("Archive pics..."), this); connect(archivePicsA, &QAction::triggered, this, &FSWidget::archivePics); connect(mNewPicsDlg, &NewPicsDialog::accepted, this, &FSWidget::refresh); toolbar->addAction(archivePicsA); QAction *unpackA = new QAction(QIcon(":/clitoris.png"), tr("Unpack"), this); connect(unpackA, &QAction::triggered, this, &FSWidget::unpack); toolbar->addAction(unpackA); QAction *previewA = new QAction(QIcon(":/snapshot.png"), tr("Preview..."), this); connect(previewA, &QAction::triggered, this, &FSWidget::preview); toolbar->addAction(previewA); QAction *playSelectedA = new QAction(QIcon(":/spreadingpants.png"), tr("Play selected"), this); connect(playSelectedA, &QAction::triggered, [=] { playSelected(1); }); QMenu *repeatMenu = new QMenu; for(int i = 2; i < 6; ++i){ QString actionStr = QString(tr("Play %1 times")).arg(QString::number(i)); QAction *a = new QAction(actionStr, this); repeatMenu->addAction(a); connect(a, &QAction::triggered, [=] { playSelected(i); }); } QAction *playRepeatMA = new QAction(tr("Play repeat"), this); playRepeatMA->setMenu(repeatMenu); QAction *selectFilterA = new QAction(tr("Select by filter..."), this); connect(selectFilterA, &QAction::triggered, this, &FSWidget::selectFilter); selectFilterA->setShortcut(tr("CTRL+j")); selectFilterA->setData(FSView::InvisibleAction); QAction *unselectAllA = new QAction(tr("unselect all..."), this); connect(unselectAllA, &QAction::triggered, [=] { mFileView->selectionModel()->clear(); }); unselectAllA->setShortcut(tr("CTRL+k")); unselectAllA->setData(FSView::InvisibleAction); QIcon plusIcon = Helper::icon(QColor(255,85,255), '+'); QIcon minusIcon = Helper::icon(QColor(255,85,255), '-'); QLabel *dirL = new QLabel(tr("Dir")); mDirCB = new QComboBox; mDirCB->setSizeAdjustPolicy(QComboBox::AdjustToContents); connect(mDirCB, &QComboBox::currentTextChanged, this, &FSWidget::gatherData); QAction *addDirA = new QAction(plusIcon, tr("Add dir..."), this); connect(addDirA, &QAction::triggered, [=] { QString startDir = QDir::homePath(); QDir cDir(mDirCB->currentText()); if(cDir.cdUp()){ startDir = cDir.absolutePath(); } QString newDir = QFileDialog::getExistingDirectory(this, tr("Select directory"), startDir); insertItem(mDirCB, newDir); } ); QAction *removeDirA = new QAction(minusIcon, tr("Remove dir."), this); connect(removeDirA, &QAction::triggered, [=] { removeItem(mDirCB);} ); QToolBar *dirTB = new QToolBar; dirTB->addAction(addDirA); dirTB->addAction(removeDirA); QLabel *filterL = new QLabel(tr("Filter")); mFilterCB = new QComboBox; mFilterCB->setInsertPolicy(QComboBox::InsertAlphabetically); mFilterCB->setSizeAdjustPolicy(QComboBox::AdjustToContents); connect(mFilterCB, &QComboBox::currentTextChanged, this, &FSWidget::filterMime); QAction *addFilterA = new QAction(plusIcon, tr("Add filter..."), this); connect(addFilterA, &QAction::triggered, [=] { QString filter = QInputDialog::getText(this, tr("Mime filter"), tr("Filter")); insertItem(mFilterCB, filter); } ); QAction *removeFilterA = new QAction(minusIcon, tr("Remove filter"), this); connect(removeFilterA, &QAction::triggered, [=] { removeItem(mFilterCB); }); QToolBar *filterTB = new QToolBar; filterTB->addAction(addFilterA); filterTB->addAction(removeFilterA); QHBoxLayout *topWL = new QHBoxLayout; topWL->addWidget(dirL); topWL->addWidget(mDirCB); topWL->addWidget(dirTB); topWL->addStretch(); topWL->addWidget(toolbar); topWL->addStretch(); topWL->addWidget(filterL); topWL->addWidget(mFilterCB); topWL->addWidget(filterTB); mFileView = new FSView; mFileView->setAlternatingRowColors(true); mFileView->setSortingEnabled(true); mFileView->setUniformRowHeights(true); mFileView->setSelectionBehavior(QAbstractItemView::SelectRows); mFileView->setSelectionMode(QAbstractItemView::ExtendedSelection); connect(mFileView, &QTreeView::doubleClicked, this, &FSWidget::doubleClicked); mModel = new QStandardItemModel; mProxy = new FSProxy; mProxy->setSourceModel(mModel); mFileView->setModel(mProxy); mFileView->sortByColumn(0, Qt::AscendingOrder); mFileView->addActions(QList() << playSelectedA << playRepeatMA << Helper::createSeparator(this) << backA << forwardA << Helper::createSeparator(this) << refreshA << deleteFilesA << Helper::createSeparator(this) << archiveMovieA << archivePicsA << unpackA << previewA << selectFilterA << unselectAllA); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(topWL); mainLayout->addWidget(mFileView); setLayout(mainLayout); readSettings(); } FSWidget::~FSWidget(){ writeSettings(); } void FSWidget::readSettings(){ QSettings s; QStringList dirs = s.value("fs/dirs").toStringList(); mDirCB->addItems(dirs); QString cDir = s.value("fs/curdir").toString(); mDirCB->setCurrentText(cDir); QStringList filters = s.value("fs/filters").toStringList(); mFilterCB->addItems(filters); QString cFilter = s.value("fs/curfilter").toString(); mFilterCB->setCurrentText(cFilter); } void FSWidget::writeSettings(){ QSettings s; QString curDir = mDirCB->currentText(); QStringList dirs; for(int i = 0 ; i < mDirCB->count(); ++i){ dirs << mDirCB->itemText(i); } s.setValue("fs/dirs", dirs); s.setValue("fs/curdir", curDir); QString curFilter = mFilterCB->currentText(); QStringList filters; for(int i = 0; i < mFilterCB->count(); ++i){ filters << mFilterCB->itemText(i); } s.setValue("fs/filters", filters); s.setValue("fs/curfilter", curFilter); } void FSWidget::insertItem(QComboBox *cb, const QString &text){ if(text.isEmpty()){ return; } int idx = 0; while(idx != cb->count()){ QString curT = cb->itemText(idx); if(text == curT){ return; } if(cb->itemText(idx) > text){ break; } ++idx; } cb->insertItem(idx, text); } void FSWidget::refresh(){ gatherData(mDirCB->currentText()); } void FSWidget::removeItem(QComboBox *cb){ int toRemove = cb->findText(cb->currentText()); if(toRemove != -1){ cb->removeItem(toRemove); } } void FSWidget::gatherData(const QString &curDir){ //setup view qApp->setOverrideCursor(Qt::BusyCursor); int sc = mProxy->sortColumn(); Qt::SortOrder so = mProxy->sortOrder(); mQueryCount = 0; int fileCount = 0; QStringList dups; QElapsedTimer timer; timer.start(); emit message(tr("Gathering data...")); mFileView->setSortingEnabled(false); mFileView->setRootIsDecorated(false); mModel->clear(); QStandardItem *root = mModel->invisibleRootItem(); QMimeDatabase mimedb; mModel->setHorizontalHeaderLabels(QStringList() << tr("Name") << tr("MIME") << tr("Size") << tr("Duration") << tr("MD5") << tr("Presence")); QIcon videoIcon = Helper::icon(QColor(255,85,255), 'M'); QIcon imageIcon = Helper::icon(QColor(255,85,255), 'P'); QIcon otherIcon = Helper::icon(QColor(255,85,255), 'O'); QBrush blackBrush(Qt::black); QBrush greenBrush(Qt::darkGreen); QBrush blueBrush(Qt::darkBlue); //setup database QSqlDatabase db = QSqlDatabase::database("treedb"); QSqlQuery filesMd5Q(db); filesMd5Q.prepare("SELECT COUNT(*) FROM files WHERE cmd5sum = :arg"); QSqlQuery originMd5Q(db); originMd5Q.prepare("SELECT COUNT(*) FROM files_origin WHERE cmd5sum = :arg"); QSqlQuery filesNameQ(db); filesNameQ.prepare("SELECT COUNT(*) FROM files WHERE tfilename = :arg"); QSqlQuery originNameQ(db); originNameQ.prepare("SELECT COUNT(*) FROM files WHERE tfilename LIKE :arg"); QSqlQuery picsNameQ(db); picsNameQ.prepare("SELECT tfilename, cmd5sum FROM pics WHERE tfilename = :arg"); QSqlQuery picsMd5Q(db); picsMd5Q.prepare("SELECT COUNT(*) FROM pics WHERE cmd5sum = :arg"); QDirIterator it(curDir, QDir::Files); mFileView->setUpdatesEnabled(false); while(it.hasNext()){ QBrush currentBrush = blackBrush; QFileInfo fi = it.next(); ++fileCount; QMimeType mimeType = mimedb.mimeTypeForFile(fi); QString mimeName = mimeType.name(); qint64 seconds = 0; Helper::Duration dur; QString durStr(tr("n/a")); QLocale l; QString size = l.toString(fi.size()); QString md5 = Helper::md5Sum(fi.absoluteFilePath()); if(mimeName.startsWith("video")){ QJsonDocument jDoc = Helper::streamData(fi.absoluteFilePath()); QJsonObject jObj = jDoc.object(); QJsonValue durationV = jObj["format"].toObject()["duration"]; seconds = durationV.toVariant().toDouble(); dur = Helper::Duration(seconds); durStr = dur.toString(); } QList items; QFont f("courier new"); QString presenceStr = tr("None"); QString likeArg = QString("%1%%").arg(fi.completeBaseName()); //do this goto dance to execute as few queries as possible if(mimeName.startsWith("video")){ if(queryCount(filesMd5Q, md5) > 0){ currentBrush = greenBrush; presenceStr = tr("Files: MD5"); goto ci; } if(queryCount(originMd5Q, md5)){ currentBrush = blueBrush; presenceStr = tr("Origin: MD5"); goto ci; } if(queryCount(filesNameQ, fi.fileName())){ currentBrush = greenBrush; presenceStr = tr("Files: Name"); goto ci; } if(queryCount(originNameQ, likeArg)){ currentBrush = blueBrush; presenceStr = tr("Origin: Name"); goto ci; } }else if(mimeName.startsWith("image")){ if(queryCount(picsMd5Q, md5) > 0){ currentBrush = greenBrush; presenceStr = tr("Pics: MD5"); goto ci; } picsNameQ.bindValue(":arg", fi.fileName()); picsNameQ.exec(); while(picsNameQ.next()){ dups << picsNameQ.value(1).toString(); } if(!dups.isEmpty()){ currentBrush = blueBrush; presenceStr = QString(tr("Pics: Name")).arg(QString::number(dups.count())); } } ci: for(int i = 0; i < 6; ++i){ QStandardItem *item = new QStandardItem; item->setFont(f); item->setEditable(false); item->setForeground(currentBrush); item->setData(fi.absoluteFilePath(), FullPathRole); item->setData(fi.size(), SizeRole); item->setData(seconds, DurationRole); item->setData(mimeType.name(), MimeRole); item->setData(dups, DupDataRole); items << item; } items[0]->setText(fi.fileName()); if(mimeName.startsWith("video")){ items[0]->setIcon(videoIcon); }else if(mimeName.startsWith("image")){ items[0]->setIcon(imageIcon); }else{ items[0]->setIcon(otherIcon); } items[1]->setText(mimeType.name()); items[2]->setText(size); items[2]->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); items[3]->setText(durStr); items[4]->setText(md5); items[5]->setText(presenceStr); root->appendRow(items); for(int i = 0; i < 6; ++i){ mFileView->resizeColumnToContents(i); } } mFileView->setUpdatesEnabled(true); mFileView->setSortingEnabled(true); mFileView->sortByColumn(sc, so); qApp->restoreOverrideCursor(); QString msg = QString(tr("Analyzed %1 files in %2 ms using %3 queries").arg(QString::number(fileCount)).arg(QString::number(timer.elapsed())).arg(QString::number(mQueryCount))); emit message(msg); } void FSWidget::deleteFiles(){ QModelIndexList selected = mFileView->selectionModel()->selectedRows(); if(selected.isEmpty()){ return; } QString question = QString(tr("Really delete %1 file(s)?")).arg(QString::number(selected.count())); int answer = QMessageBox::question(this, tr("Delete"), question); if(answer == QMessageBox::Yes){ int count = 0; for(const QModelIndex &idx : selected){ if(QFile::remove(idx.data(FullPathRole).toString())){ ++count; } } refresh(); QString msg = QString(tr("Deleted %1 file(s) successfully!")).arg(QString::number(count)); emit message(msg); } } void FSWidget::archiveMovie(){ QModelIndexList selected = mFileView->selectionModel()->selectedRows(); if(selected.isEmpty()){ return; } mMovieWizard->restart(); mMovieWizard->infoPage()->setCurrentDir(mDirCB->currentText()); for(const QModelIndex &idx : selected){ QString path = idx.data(FullPathRole).toString(); mMovieWizard->infoPage()->addFile(path); mMovieWizard->infoPage()->guessOld(path); } QSettings s; bool autoAddCovers = s.value("ui/autoaddcovers", false).toBool(); QString coverPath = s.value("paths/coverpath").toString(); if(autoAddCovers && !coverPath.isEmpty()){ QDir coverDir(coverPath); for(const QFileInfo &fi : coverDir.entryInfoList(QDir::Files)){ mMovieWizard->infoPage()->addFile(fi.absoluteFilePath()); } } mMovieWizard->infoPage()->selectFirst(); mMovieWizard->show(); } void FSWidget::archivePics(){ QModelIndexList selected = mFileView->selectionModel()->selectedRows(); if(selected.isEmpty()){ return; } QStringList files; for(const QModelIndex &idx : selected){ files << idx.data(FullPathRole).toString(); } mNewPicsDlg->clearFiles(); mNewPicsDlg->addFiles(files); mNewPicsDlg->show(); } void FSWidget::advanceDir(int by){ int dirCount = mDirCB->count(); int nextIdx = mDirCB->currentIndex() + by; if(nextIdx >= dirCount){ nextIdx = 0; } if(nextIdx < 0){ nextIdx = dirCount - 1; } mDirCB->setCurrentIndex(nextIdx); } int FSWidget::queryCount(QSqlQuery &q, const QString &arg){ int retval = -1; q.bindValue(":arg", arg); q.exec(); while(q.next()){ retval = q.value(0).toInt(); } ++mQueryCount; return retval; } void FSWidget::filterMime(const QString &mime){ QString filter = mime; if(filter == ""){ filter = QString(); } mProxy->setFilterKeyColumn(1); mProxy->setFilterRegExp(filter); mProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); } void FSWidget::unpack(){ QModelIndexList selected = mFileView->selectionModel()->selectedRows(); if(selected.isEmpty()){ return; } for(const QModelIndex &idx : selected){ QString file = idx.data(FullPathRole).toString(); QFileInfo fi(file); QString outDir = QString("-o%1").arg(fi.absolutePath()); QStringList args = QStringList() << "x" << outDir << file; int retval = QProcess::execute("7z", args); if(retval == 0){ QFile::remove(file); refresh(); } } } void FSWidget::doubleClicked(const QModelIndex &idx){ QString mime = idx.data(MimeRole).toString(); if(mime.startsWith("image")){ mViewer->setFile(idx.data(FullPathRole).toString()); mViewer->showMaximized(); }else if(mime.startsWith("video")){ QPair playerData = Helper::programData("movieviewer"); QStringList args = playerData.second; args << idx.data(FullPathRole).toString(); QProcess::startDetached(playerData.first, args); } } void FSWidget::preview(){ QModelIndexList selected = mFileView->selectionModel()->selectedRows(); if(selected.isEmpty()){ return; } QString mime = selected.first().data(MimeRole).toString(); if(mime.startsWith("video")){ mViewer->preview(selected.first().data(FullPathRole).toString()); mViewer->showMaximized(); } } void FSWidget::playSelected(int count){ QModelIndexList selected = mFileView->selectionModel()->selectedRows(); if(selected.isEmpty()){ return; } QStringList paths; for(const QModelIndex &idx : selected){ paths << idx.data(FullPathRole).toString(); } QPair playerData = Helper::programData("movieviewer"); QStringList args = playerData.second; for(int i = 0; i < count; ++i){ args << paths; } QProcess::startDetached(playerData.first, args); } void FSWidget::selectFilter(){ bool ok; QString retval = QInputDialog::getText(this, tr("File selection by regex!"), tr("Select"), QLineEdit::Normal, "mkv$", &ok); if(!ok){ return; } mFileView->selectionModel()->clearSelection(); QRegExp re(retval); for(int i = 0; i < mProxy->rowCount(); ++i){ QModelIndex nameIdx = mProxy->index(i, 0); if(re.indexIn(nameIdx.data().toString()) != -1){ mFileView->selectionModel()->select(nameIdx, QItemSelectionModel::Select | QItemSelectionModel::Rows); } } }