#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 #include #include #include #include #include #include #include #include #include #include #include #include "filewidget.h" #include "filesorter.h" #include "filedisplay.h" #include "filecopier.h" #include "cachedfiledata.h" #include "origindialog.h" #include "helper.h" #include "globals.h" #include "viewer.h" #include "searchdialog.h" FileWidget::FileWidget(QWidget *parent) : QWidget(parent), mCopyToMenu(0) { mFileCopier = new FileCopier(this); mFileCache.setMaxCost(500); readCache(); connect(mFileCopier, &FileCopier::message, this, &FileWidget::statusMessage); setupGui(); } FileWidget::~FileWidget(){ writeHeaderData(); writeSettings(); writeCache(); } void FileWidget::setupGui(){ mDir = new QLineEdit; QCompleter *fsCompleter = new QCompleter(this); QDirModel *dirModel = new QDirModel(this); fsCompleter->setModel(dirModel); fsCompleter->setCompletionMode(QCompleter::PopupCompletion); mDir->setCompleter(fsCompleter); connect(mDir, &QLineEdit::returnPressed, this, &FileWidget::gatherData); QPushButton *selDirBtn = new QPushButton(tr("&Browse...")); connect(selDirBtn, &QPushButton::clicked, this, &FileWidget::selectDir); mFavDirs = new QComboBox; populateFavDirs(); connect(mFavDirs, QOverload::of(&QComboBox::activated), [=](const QString &text) { cdAbsolute(text); }); mFilter = new QLineEdit; connect(mFilter, &QLineEdit::returnPressed, [this] { filter(NameColumn, mFilter->text()); }); QPushButton *searchBtn = new QPushButton(tr("F&ilter")); connect(searchBtn, &QPushButton::clicked, [this] { filter(NameColumn, mFilter->text()); }); mMimeFilters = new QComboBox; populateMimeFilters(); connect(mMimeFilters, QOverload::of(&QComboBox::activated), [this](const QString &text) { filter(MimeColumn, text); }); connect(mMimeFilters, &QComboBox::currentTextChanged, [this](const QString &text) { filter(MimeColumn, text); }); QGridLayout *topL = new QGridLayout; topL->addWidget(new QLabel(tr("Directory")), 0, 0); topL->addWidget(mDir, 0, 1); topL->addWidget(selDirBtn, 0, 2); topL->addWidget(mFavDirs, 0, 3); topL->addWidget(new QLabel(tr("Filter")), 1, 0); topL->addWidget(mFilter, 1, 1); topL->addWidget(searchBtn, 1, 2); topL->addWidget(mMimeFilters, 1, 3); mFileView = new QTreeView; Globals::instance()->addView(mFileView); mFileView->setSortingEnabled(true); mFileView->setUniformRowHeights(true); mFileView->setSelectionBehavior(QAbstractItemView::SelectRows); mFileView->setSelectionMode(QAbstractItemView::ExtendedSelection); connect(mFileView, &QTreeView::doubleClicked, this, QOverload::of(&FileWidget::properties)); mModel = new QStandardItemModel; mProxy = new VideoSorter; mProxy->setSourceModel(mModel); mFileView->setModel(mProxy); readSettings(); QSqlDatabase mDb = QSqlDatabase::database("shemovdb"); if(!mDb.isOpen()){ mDb.open(); } mQOrigin = QSqlQuery(mDb); mQOrigin.prepare("SELECT COUNT(*) FROM files_origin WHERE cmd5sum = :md5"); mQFiles = QSqlQuery(mDb); mQFiles.prepare("SELECT COUNT(*) FROM files WHERE cmd5sum = :md5"); mFileDisplay = new FileDisplay(this); mOrignDlg = new OriginDialog(this); connect(mOrignDlg, &OriginDialog::statusMessage, this, &FileWidget::statusMessage); connect(mOrignDlg, &OriginDialog::refreshNeeded, this, &FileWidget::gatherData); mSearchDlg = new SearchDialog(this, 0); mMenuBar = new QMenuBar; mToolBar = new QToolBar; mToolBar->setIconSize(QSize(16, 16)); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(mMenuBar); mainLayout->addWidget(mToolBar); mainLayout->addLayout(topL); mainLayout->addWidget(new QLabel(tr("Files"))); mainLayout->addWidget(mFileView); setLayout(mainLayout); connect(mFileView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FileWidget::fileSelectionChanged); createActions(); mMimeData = new QMimeData; mFileView->setFocus(); QSettings s; QString startMimeFilter = s.value("currentmimefilter").toString(); mMimeFilters->setCurrentText(startMimeFilter); } void FileWidget::createActions(){ mCopyToMenu = new QMenu; mSelDirA = new QAction(QIcon(":/folder.png"), tr("Select Folder..."), this); mSelDirA->setShortcut(tr("CTRL+O")); connect(mSelDirA, &QAction::triggered, this, &FileWidget::selectDir); mRefreshA = new QAction(QIcon(":/refresh.png"), tr("Refresh"), this); mRefreshA->setShortcut(tr("CTRL+R")); connect(mRefreshA, &QAction::triggered, this, &FileWidget::gatherData); mDirUpA = new QAction(QIcon(":/up_dick.png"), tr("Dir up"), this); mDirUpA->setShortcut(Qt::Key_Backspace); connect(mDirUpA, &QAction::triggered, this, &FileWidget::cdUp); mDirBackA = new QAction(QIcon(":/back_dick.png"), tr("Back"), this); mDirBackA->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Left)); connect(mDirBackA, &QAction::triggered, this, &FileWidget::cdLast); mCopyA = new QAction(QIcon(":/edit-copy.png"), tr("Copy"), this); mCopyA->setShortcut(tr("CTRL+C")); connect(mCopyA, &QAction::triggered, this, &FileWidget::copy); mCutA = new QAction(QIcon(":/edit-cut.png"), tr("Cut"), this); mCutA->setShortcut(tr("CTRL+X")); connect(mCutA, &QAction::triggered, this, &FileWidget::cut); mPasteA = new QAction(QIcon(":/edit-paste.png"), tr("Paste"), this); mPasteA->setShortcut(tr("CTRL+V")); connect(mPasteA, &QAction::triggered, this, &FileWidget::paste); mDeleteA = new QAction(QIcon(":/delete.png"), tr("Delete..."), this); mDeleteA->setShortcut(Qt::Key_Delete); connect(mDeleteA, &QAction::triggered, this, &FileWidget::deleteFiles); mPropertiesA = new QAction(QIcon(":/diaper.png"), tr("Properties..."), this); mPropertiesA->setShortcut(Qt::CTRL + Qt::Key_Return); connect(mPropertiesA, &QAction::triggered, this, QOverload<>::of(&FileWidget::properties)); mPreviewA = new QAction(QIcon(":/snapshot.png"), tr("Preview..."), this); connect(mPreviewA, &QAction::triggered, this, &FileWidget::preview); mAddAsOriginA = new QAction(QIcon(":/butt_plug.png"), tr("Add as Origin..."), this); connect(mAddAsOriginA, &QAction::triggered, this, &FileWidget::addAsOrigin); mRemoveFromCacheA = new QAction(QIcon(":/bizarre_amputee.png"), tr("Remove selected"), this); connect(mRemoveFromCacheA, &QAction::triggered, this, &FileWidget::removeFromCache); mClearCacheA = new QAction(QIcon(":/male_chastity_belt.png"), tr("Clear..."), this); connect(mClearCacheA, &QAction::triggered, this, &FileWidget::clearCache); mGuessSubjectA = new QAction(QIcon(":/bizarre_amputee.png"), tr("Guess subject..."), this); connect(mGuessSubjectA, &QAction::triggered, this, &FileWidget::guessSubject); QAction *searchGoogleA = new QAction(QIcon(":/web-browser.png"), tr("Search Google..."), this); connect(searchGoogleA, &QAction::triggered, this, &FileWidget::searchGoogle); QAction *searchDlgA = new QAction(QIcon(":/system-search.png"), tr("Search dialog..."), this); connect(searchDlgA, &QAction::triggered, mSearchDlg, &SearchDialog::show); mCancelCopyA = new QAction(QIcon(":/chastity_belt.png"), tr("Cancel copy"), this); connect(mCancelCopyA, &QAction::triggered, mFileCopier, &FileCopier::cancel); QMenu *fileMenu = new QMenu(tr("&File")); fileMenu->addAction(mSelDirA); fileMenu->addAction(mRefreshA); fileMenu->addSeparator(); QMenu *cacheMenu = new QMenu(tr("Cache")); cacheMenu->addAction(mRemoveFromCacheA); cacheMenu->addAction(mClearCacheA); fileMenu->addMenu(cacheMenu); fileMenu->addSeparator(); fileMenu->addAction(Globals::instance()->action(Globals::QuitAction)); QMenu *editMenu = new QMenu(tr("&Edit")); editMenu->addAction(mDirUpA); editMenu->addAction(mDirBackA); editMenu->addSeparator(); editMenu->addAction(mGuessSubjectA); editMenu->addAction(searchGoogleA); editMenu->addAction(searchDlgA); editMenu->addSeparator(); editMenu->addAction(mCopyA); editMenu->addAction(mCutA); editMenu->addAction(mPasteA); editMenu->addAction(mDeleteA); editMenu->addSeparator(); editMenu->addAction(mPropertiesA); editMenu->addAction(mPreviewA); editMenu->addAction(mAddAsOriginA); editMenu->addSeparator(); editMenu->addAction(Globals::instance()->action(Globals::ConfigAction)); mMenuBar->addMenu(fileMenu); mMenuBar->addMenu(editMenu); mToolBar->addAction(mRefreshA); mToolBar->addAction(mSelDirA); mToolBar->addAction(createSeparator()); mToolBar->addAction(mDirUpA); mToolBar->addAction(mDirBackA); mToolBar->addAction(createSeparator()); mToolBar->addAction(mCopyA); mToolBar->addAction(mCutA); mToolBar->addAction(mPasteA); mToolBar->addAction(mDeleteA); mToolBar->addAction(createSeparator()); mToolBar->addAction(mPropertiesA); mToolBar->addAction(mPreviewA); mToolBar->addAction(createSeparator()); mToolBar->addAction(mAddAsOriginA); mToolBar->addAction(mGuessSubjectA); mToolBar->addAction(searchGoogleA); mToolBar->addAction(searchDlgA); mToolBar->addAction(createSeparator()); mToolBar->addAction(Globals::instance()->action(Globals::ConfigAction)); addActions(QList() << mGuessSubjectA << searchGoogleA << searchDlgA << createSeparator() << mRefreshA << mSelDirA << createSeparator() << mCopyA << mCutA << mPasteA << mDeleteA << createSeparator() << mAddAsOriginA << createSeparator() << mPreviewA << createSeparator() << mPropertiesA); } void FileWidget::fileData(const QString &fullPath, const QString &md5){ mFileDisplay->setFileData(fullPath, md5); mFileDisplay->show(); } void FileWidget::selectDir(){ QString dir = QFileDialog::getExistingDirectory(this, tr("Select directory"), QDir::homePath()); if(!dir.isEmpty()){ mDir->setText(QDir::toNativeSeparators(dir)); gatherData(); } } void FileWidget::gatherData(){ if(isVisible()){ writeHeaderData(); } QSettings s; QStringList copyDirs = s.value("copydirs").toStringList(); QStringList copyDirFiles; for(const QString &d : copyDirs){ QDirIterator it(d, QDir::Files | QDir::NoDotAndDotDot); while(it.hasNext()){ it.next(); copyDirFiles << it.fileName(); } } mFileView->setSortingEnabled(false); mModel->clear(); QStandardItem *root = mModel->invisibleRootItem(); mModel->setHorizontalHeaderLabels(QStringList() << QChar(0x26A7) << tr("Name") << QChar(0x26A5) << tr("MIME") << tr("Duration") << tr("Size") << tr("MD5")); QMimeDatabase db; qApp->setOverrideCursor(Qt::BusyCursor); QString gatherMsg(tr("Gathering data: %1 (%2)")); int count = 1; qint64 totalSize = 0; qApp->processEvents(); QDirIterator it(mDir->text(), QDir::Files | QDir::Dirs | QDir::NoDot); while(it.hasNext()){ QFileInfo fi = it.next(); emit statusMessage(gatherMsg.arg(fi.fileName()).arg(QString::number(count))); CachedFileData *fd = mFileCache[fi.absoluteFilePath()]; if(!fd){ bool insert = true; fd = new CachedFileData; QMimeType mime = db.mimeTypeForFile(fi); fd->attr = NotPresent; Helper::Duration dur; QString md5(tr("n/a")); QString duration(tr("n/a")); qint64 seconds = 0; if(mime.name().startsWith("video")){ md5 = Helper::md5Sum(fi.absoluteFilePath()); fd->md5Sum = md5; int haveFile = md5Count(md5); if(haveFile){ fd->attr = Present; } QJsonDocument jDoc = Helper::ffpmegData(fi.absoluteFilePath()); QJsonObject jObj = jDoc.object(); QJsonValue durationV = jObj["format"].toObject()["duration"]; seconds = durationV.toVariant().toDouble(); dur = Helper::Duration(seconds); duration = dur.toString(); }else if(fi.isDir()){ if(fi.fileName() == ".."){ fd->attr = ParentDir; insert = false; }else{ fd->attr = Directory; insert = false; } }else{ fd->attr = NoVideo; insert = false; } if(fi.isFile()){ qint64 sizemb = fi.size(); fd->size = sizemb; if(copyDirFiles.contains(fi.fileName())){ fd->copied = true; }else{ fd->copied = false; } ++count; totalSize += sizemb; } fd->duration = duration; fd->md5Sum = md5; fd->mimeType = mime.name(); fd->name = fi.fileName(); fd->fullPath = fi.absoluteFilePath(); fd->seconds = seconds; if(insert){ mFileCache.insert(fi.absoluteFilePath(), fd); } }else{ int haveFile = md5Count(fd->md5Sum); if(haveFile){ fd->attr = Present; } if(copyDirFiles.contains(fi.fileName())){ fd->copied = true; } } root->appendRow(constructRow(fd)); } mFileView->setSortingEnabled(true); readHeaderData(); readDescriptION(); qApp->restoreOverrideCursor(); selectFirst(); double totalSizeGB = totalSize / 1024 / 1024 / 1024.0; emit statusMessage(QString(tr("Analyzed %1 file(s) / (%2 GB)").arg(QString::number(count)).arg(QString::number(totalSizeGB, 'f', 2)))); emit freeSpaceChanged(mDir->text()); } void FileWidget::readDescriptION(){ mDescript.clear(); QString fileName = QString("%1/descript.ion").arg(mDir->text()); QFile descFile(fileName); if(descFile.open(QFile::ReadOnly)){ QTextStream in(&descFile); QString line; while(in.readLineInto(&line)){ if(line.contains("yEnc,")){ QString part = line.left(line.indexOf("yEnc,") + 4); mDescript << part; }else{ QStringList parts = line.split(','); if(!parts.isEmpty()){ mDescript << parts.at(0); } } } } std::sort(mDescript.begin(), mDescript.end()); } void FileWidget::guessSubject(){ readDescriptION(); QModelIndexList sel = mFileView->selectionModel()->selectedRows(NameColumn); if(!sel.isEmpty()){ QFileInfo fi(sel.first().data().toString()); QString fRe = QString(".*%1.*").arg(fi.completeBaseName()); const QRegExp fnRe(fRe); int index = mDescript.indexOf(fnRe); if(index > -1){ QString subject(mDescript.at(index)); if(subject.startsWith('"')){ int nextQuot = subject.indexOf('"', 1); if(nextQuot > -1){ subject = subject.remove(0, nextQuot + 1).trimmed(); } } qApp->clipboard()->setText(subject); emit statusMessage(subject); }else{ emit statusMessage(tr("No subject found!")); } } } void FileWidget::searchGoogle(){ QModelIndexList sel = mFileView->selectionModel()->selectedRows(NameColumn); if(!sel.isEmpty()){ QString fn = sel.first().data().toString().replace('.', ' '); QString url = QString("https://www.google.com/search?q=%1").arg(fn); QDesktopServices::openUrl(url); } } int FileWidget::md5Count(const QString &md5) { mQOrigin.bindValue(":md5", md5); mQOrigin.exec(); int retval = 0; while(mQOrigin.next()){ retval = mQOrigin.value(0).toInt(); } if(!retval){ mQFiles.bindValue(":md5", md5); mQFiles.exec(); while(mQFiles.next()){ retval = mQFiles.value(0).toInt(); } } return retval; } void FileWidget::readHeaderData(){ QSettings s; QByteArray headers = s.value("video/fileheaders").toByteArray(); if(!headers.isEmpty()){ mFileView->header()->restoreState(headers); } } void FileWidget::writeHeaderData(){ QSettings s; s.setValue("video/fileheaders", mFileView->header()->saveState()); } void FileWidget::readSettings(){ QSettings s; QString dir = s.value("video/searchdir", QDir::toNativeSeparators(QDir::homePath())).toString(); mDir->setText(dir); populateFavDirs(); populateMimeFilters(); } void FileWidget::writeSettings(){ QSettings s; s.setValue("video/searchdir", mDir->text()); s.setValue("currentmimefilter", mMimeFilters->currentText()); } void FileWidget::readCache(){ QString cacheDir = Helper::appDataDir(); if(!cacheDir.isEmpty()){ QString cacheFile = QString("%1/%2").arg(cacheDir).arg("cfd.dat"); QFile f(cacheFile); if(f.open(QIODevice::ReadOnly)){ QDataStream in(&f); while(!in.atEnd()){ CachedFileData *fd = new CachedFileData; in >> *fd; mFileCache.insert(fd->fullPath, fd); } } } } void FileWidget::writeCache(){ QString cacheDir = Helper::appDataDir(); if(!cacheDir.isEmpty()){ QString cacheFile = QString("%1/%2").arg(cacheDir).arg("cfd.dat"); QFile f(cacheFile); if(f.open(QIODevice::WriteOnly)){ QDataStream out(&f); QList entries = mFileCache.keys(); for(const QString &e : entries){ CachedFileData *entry = mFileCache[e]; out << *entry; } } } } QAction *FileWidget::createSeparator(){ QAction *retval = new QAction(this); retval->setSeparator(true); return retval; } void FileWidget::filter(int column, QString regexp){ if(column == NameColumn){ if(regexp.isEmpty()){ column = MimeColumn; regexp = mMimeFilters->currentText(); } }else if(column == MimeColumn){ if(regexp == ""){ regexp = QString(); } } mProxy->setFilterRegExp(regexp); mProxy->setFilterKeyColumn(column); mProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); } void FileWidget::copy(){ mFileAction = Copy; cutOrCopy(); } void FileWidget::cut(){ mFileAction = Cut; cutOrCopy(); } void FileWidget::cutOrCopy(){ mMimeData->clear(); QModelIndexList idxL = mFileView->selectionModel()->selectedRows(NameColumn); QList files; for(const QModelIndex &i : idxL){ QUrl url = QUrl::fromLocalFile(i.data(FullPathRole).toString()); files << url; } mMimeData->setUrls(files); QClipboard *clip = qApp->clipboard(); clip->setMimeData(mMimeData); } void FileWidget::populateFavDirs(){ QSettings s; QStringList favDirs = s.value("favdirs").toStringList(); mFavDirs->clear(); mFavDirs->addItems(favDirs); int cbidx = mFavDirs->findText(mDir->text()); if(cbidx > -1){ mFavDirs->blockSignals(true); mFavDirs->setCurrentIndex(cbidx); mFavDirs->blockSignals(false); } } void FileWidget::populateMimeFilters(){ QSettings s; QString current = mMimeFilters->currentText(); QStringList mimeFilters = s.value("mimefilters").toStringList(); mimeFilters.sort(); mimeFilters.prepend(tr("")); mMimeFilters->clear(); mMimeFilters->addItems(mimeFilters); int cbidx = mMimeFilters->findText(current); if(cbidx > -1){ mMimeFilters->setCurrentIndex(cbidx); } } QList FileWidget::constructRow(CachedFileData *fd) const{ QList fData; QBrush redBrush(Qt::red); QBrush greenBrush(Qt::darkGreen); QBrush blueBrush(Qt::darkBlue); for(int i = 0; i < ColumnCount; ++i){ QStandardItem *item = new QStandardItem; item->setEditable(false); if(i == Md5Column){ item->setFont(QFont("courier new")); item->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); } if(i == SizeColumn){ item->setFont(QFont("courier new")); item->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); } fData << item; } fData[NameColumn]->setText(fd->name); fData[Md5Column]->setText(fd->md5Sum); int attr = fd->attr; if(attr == Present){ fData[IconColumn]->setIcon(Helper::icon(QColor(255,85,255), 'O')); fData[NameColumn]->setForeground(greenBrush); }else if(attr == NotPresent){ fData[IconColumn]->setIcon(Helper::icon(QColor(255,85,255), 'X')); fData[NameColumn]->setForeground(redBrush); }else if(attr == ParentDir){ fData[IconColumn]->setIcon(Helper::icon(QColor(255,85,255), QChar(0x21B0))); }else if(attr == Directory){ fData[IconColumn]->setIcon(Helper::icon(QColor(255,85,255), 'F')); }else if(attr == NoVideo){ fData[IconColumn]->setIcon(Helper::icon(QColor(255,85,255), 'Y')); } qint64 size = fd->size; QString sizeStr; if(size){ QLocale l; sizeStr = QString("%1").arg(l.toString(size)); } fData[SizeColumn]->setText(sizeStr); fData[SizeColumn]->setData(size, SizeRole); bool copied = fd->copied; if(copied){ fData[CopiedColumn]->setText(QChar(0x2642)); fData[CopiedColumn]->setForeground(blueBrush); }else{ fData[CopiedColumn]->setText(QChar(0x2640)); fData[CopiedColumn]->setForeground(redBrush); } fData[DurationColumn]->setText(fd->duration); fData[DurationColumn]->setData(fd->seconds, SecondsRole); QString mimeType = fd->mimeType; if(mimeType == "inode/directory"){ fData[MimeColumn]->setText("directory"); }else{ fData[MimeColumn]->setText(mimeType); } for(int i = 0; i < ColumnCount; ++i){ fData[i]->setData(fd->attr, AttrsRole); fData[i]->setData(fd->fullPath, FullPathRole); fData[i]->setData(fd->md5Sum, MD5SumRole); } return fData; } void FileWidget::paste(){ QClipboard *clip = qApp->clipboard(); const QMimeData *md = clip->mimeData(); if(md->hasUrls()){ qApp->setOverrideCursor(Qt::WaitCursor); QList urls = md->urls(); int count = 0; mProgressBar->reset(); mProgressBar->setMaximum(urls.count() - 1); for(QUrl u : urls){ QString path = u.toLocalFile(); QString newDir = mDir->text(); QFileInfo fi(path); QString newFile = QString("%1/%2").arg(newDir).arg(fi.fileName()); mProgressBar->setValue(++count); CachedFileData *fd = mFileCache[path]; if(mFileAction == Copy){ QString msg = QString(tr("Copying %1").arg(path)); emit statusMessage(msg); qApp->processEvents(); if(QFile::copy(path, newFile)){ CachedFileData *fdNew = new CachedFileData(*fd); fdNew->fullPath = newFile; mFileCache.insert(newFile, fdNew); } }else if(mFileAction == Cut){ QString msg = QString(tr("Moving %1").arg(path)); emit statusMessage(msg); qApp->processEvents(); if(QFile::rename(path, newFile)){ CachedFileData *fd = mFileCache.take(path); if(fd){ mFileCache.remove(path); mFileCache.insert(newFile, fd); } } } emit statusMessage("Finished Paste!"); } gatherData(); qApp->restoreOverrideCursor(); } } void FileWidget::selectFirst(){ if(mModel->rowCount() > 0){ QModelIndex idx = mModel->index(0, 0); mFileView->setCurrentIndex(idx); mFileView->selectionModel()->select(idx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } } void FileWidget::addAsOrigin(){ mOrignDlg->clear(); QModelIndex idx = mFileView->selectionModel()->selectedRows().first(); QString fp = idx.data(FullPathRole).toString(); mOrignDlg->setSrctData(fp); mOrignDlg->setDstData(fp); mOrignDlg->show(); } void FileWidget::copyFiles(QString destDir){ QModelIndexList files = mFileView->selectionModel()->selectedRows(); if(!files.isEmpty()){ for(const QModelIndex &idx : files){ QString fp = idx.data(FullPathRole).toString(); QFileInfo fi(fp); if(!fi.exists() || fi.isDir()){ continue; } QString destFile = QString("%1%2%3").arg(destDir).arg(QDir::separator()).arg(fi.fileName()); mFileCopier->addJob(fp, destFile); } if(!mFileCopier->isRunning()){ mFileCopier->start(); } } } void FileWidget::copySuccess(bool success, QString source){ if(success){ QFileInfo fi(source); auto sourceFiles = mModel->findItems(fi.fileName(), Qt::MatchExactly, NameColumn); if(!sourceFiles.isEmpty()){ QModelIndex idx = mModel->indexFromItem(sourceFiles.first()); if(idx.isValid()){ QModelIndex copyIdx = idx.sibling(idx.row(), CopiedColumn); mModel->setData(copyIdx, QColor(Qt::blue), Qt::ForegroundRole); mModel->setData(copyIdx, QChar(0x2642)); } } CachedFileData *fd = mFileCache[source]; if(fd){ fd->copied = true; } } } void FileWidget::removeFromCache(){ QModelIndexList curSel = mFileView->selectionModel()->selectedRows(); int count = 0; for(const QModelIndex &i : curSel){ QString fullPath = i.data(FullPathRole).toString(); if(mFileCache.remove(fullPath)){ ++count; } } QString msg = QString(tr("Removed %1 files from cache")).arg(QString::number(count)); gatherData(); emit statusMessage(msg); } void FileWidget::clearCache(){ int retval = QMessageBox::question(this, tr("Clear cache"), tr("Really empty the cache?")); if(retval == QMessageBox::Yes){ mFileCache.clear(); gatherData(); emit statusMessage(tr("Cache cleared!")); } } void FileWidget::fileSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected){ Q_UNUSED(selected); Q_UNUSED(deselected); int count = mFileView->selectionModel()->selectedRows().count(); int total = mModel->rowCount(); QString msg = QString("%1/%2").arg(QString::number(count)).arg(QString::number(total)); emit selectionCountChanged(msg); QModelIndexList idxl = mFileView->selectionModel()->selectedRows(DurationColumn); qint64 seconds = 0; for(const QModelIndex &idx : idxl){ seconds += idx.data(SecondsRole).toLongLong(); } Helper::Duration dur(seconds); emit durationChanged(dur.toString()); } void FileWidget::properties(){ QModelIndexList idxs = mFileView->selectionModel()->selectedRows(); if(!idxs.isEmpty()){ QModelIndex cur = idxs.first(); int attr = cur.data(AttrsRole).toInt(); if(attr != ParentDir && attr != Directory){ mFileView->selectionModel()->select(cur, QItemSelectionModel::Rows); properties(cur); } } } void FileWidget::properties(const QModelIndex &idx){ QModelIndex name = idx; QString mime = idx.sibling(idx.row(), MimeColumn).data().toString(); if(name.column() != NameColumn){ name = idx.sibling(idx.row(), NameColumn); } int attr = name.data(AttrsRole).toInt(); if(attr == ParentDir || attr == Directory){ cd(name.data().toString()); return; } if(mime.startsWith("video")){ fileData(name.data(FullPathRole).toString(), name.data(MD5SumRole).toString()); } if(mime.startsWith("image")){ Viewer *v = Globals::instance()->viewer(); v->setFile(idx.data(FullPathRole).toString()); v->show(); } } void FileWidget::preview(){ QModelIndex idx = mFileView->selectionModel()->currentIndex(); if(!idx.isValid()){ return; } QString mime = idx.sibling(idx.row(), MimeColumn).data().toString(); QString file = idx.data(FullPathRole).toString(); qApp->setOverrideCursor(Qt::WaitCursor); Viewer *v = Globals::instance()->viewer(); if(mime.startsWith("video")){ v->preview(file); }else if(mime.startsWith("image")){ v->setFile(file, false); } qApp->restoreOverrideCursor(); v->show(); } void FileWidget::cd(const QString &to){ QString ld = mDir->text(); QDir cur(mDir->text()); if(cur.cd(to)){ QString newDir = cur.absolutePath(); mDir->setText(QDir::toNativeSeparators(newDir)); mLastDir = ld; gatherData(); }else{ QString msg = QString(tr("Could not cd to %1")).arg(to); emit statusMessage(msg); } } void FileWidget::cdAbsolute(const QString &to){ QString ld = mDir->text(); mLastDir = ld; mDir->setText(QDir::toNativeSeparators(to)); gatherData(); } void FileWidget::cdUp(){ cd(".."); } void FileWidget::cdLast(){ cd(mLastDir); } void FileWidget::deleteFiles(){ QModelIndexList selFiles = mFileView->selectionModel()->selectedRows(NameColumn); if(selFiles.isEmpty()){ return; } const QString msg = QString(tr("Really delete %1 file(s)?")).arg(QString::number(selFiles.count())); int retval = QMessageBox::question(this, tr("Delete files..."), msg); if(retval == QMessageBox::Yes){ QStringList removed; for(const QModelIndex& idx : selFiles){ QString path = idx.data(FullPathRole).toString(); QFileInfo fi(path); bool success = false; if(fi.isDir()){ QDir d(fi.absoluteFilePath()); success = d.removeRecursively(); }else{ success = QFile::remove(path); if(!success){ QFile f(path); f.setPermissions(QFile::ReadOther | QFile::WriteOther); success = f.remove(); } } if(success){ mFileCache.remove(fi.absoluteFilePath()); removed << idx.data().toString(); } } for(const QString& f : removed){ QList res = mModel->findItems(f, Qt::MatchExactly, NameColumn); if(!res.isEmpty()){ mModel->removeRow(res.at(0)->row()); } } } } void FileWidget::keyPressEvent(QKeyEvent *e){ if(e->key() == Qt::Key_S && (e->modifiers() & Qt::ControlModifier)){ mSearchDlg->show(); mSearchDlg->raise(); mSearchDlg->setFocus(); goto exit; } if(e->key() == Qt::Key_D && (e->modifiers() & Qt::ControlModifier)){ mDir->setFocus(); mDir->selectAll(); goto exit; } if(e->key() == Qt::Key_F && (e->modifiers() & Qt::ControlModifier)){ mFileView->setFocus(); goto exit; } if(mFileView->hasFocus()){ if(e->key() == Qt::Key_Return){ QModelIndex cur = mFileView->selectionModel()->currentIndex(); QString mimeType = cur.sibling(cur.row(), MimeColumn).data().toString(); if(mimeType == "directory"){ cd(cur.sibling(cur.row(), NameColumn).data().toString()); }else{ properties(); } } } return QWidget::keyPressEvent(e); exit: e->accept(); } void FileWidget::contextMenuEvent(QContextMenuEvent *e){ QMenu contextMenu(this); QSettings s; mCopyToMenu->clear(); mCopyToMenu->setTitle(tr("Copy Files to...")); mCopyToMenu->setIcon(QIcon(":/hourglass_figure.png")); QStringList copyToDirs = s.value("copydirs").toStringList(); std::sort(copyToDirs.begin(), copyToDirs.end()); for(QString &d : copyToDirs){ QFileInfo di(d); if(di.exists()){ QAction *copyA = new QAction(di.fileName(), mCopyToMenu); connect(copyA, &QAction::triggered, [=] { copyFiles(di.absoluteFilePath()); }); mCopyToMenu->addAction(copyA); } } contextMenu.addMenu(mCopyToMenu); if(mFileCopier->isRunning()){ contextMenu.addAction(mCancelCopyA); } contextMenu.addAction(createSeparator()); contextMenu.addActions(actions()); contextMenu.exec(e->globalPos()); }