/* 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 "filesystemwidget.h" #include "filesystemdirproxy.h" #include "fileview.h" #include "shemoviconprovider.h" #include "filesystemfileproxy.h" #include "helper.h" #include "pictureviewer2.h" #include "smglobals.h" FilesystemWidget::FilesystemWidget(QWidget *parent) : QWidget(parent), mClipboardMode(None) { mModel = new FileSystemModel(this); mModel->setRootPath("/"); mModel->setFilter(QDir::AllEntries | QDir::NoDot); mModel->setReadOnly(false); mIconProvider = new SheMovIconProvider; mModel->setIconProvider(mIconProvider); mDirProxy = new FilesystemDirProxy; mDirProxy->setSourceModel(mModel); mDirView = new QTreeView; mDirView->setModel(mDirProxy); mDirView->setColumnHidden(1, true); mDirView->setColumnHidden(2, true); mDirView->setColumnHidden(3, true); mDirView->setRootIsDecorated(false); mDirView->setSelectionMode(QAbstractItemView::SingleSelection); mDirView->setEditTriggers(QAbstractItemView::NoEditTriggers); mDirView->setSortingEnabled(true); mDirView->sortByColumn(0, Qt::AscendingOrder); mDirView->setAlternatingRowColors(true); mFileView = new FileView; mFileProxy = new FilesystemFileProxy; mFileProxy->setSourceModel(mModel); mFileView->setModel(mFileProxy); mFileView->setSortingEnabled(true); mFileView->sortByColumn(0, Qt::AscendingOrder); mFileView->setItemsExpandable(false); mFileView->setSelectionMode(QAbstractItemView::ExtendedSelection); mFileView->setEditTriggers(QAbstractItemView::NoEditTriggers); mFileView->setAlternatingRowColors(true); connect(mFileView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), mFileView, SLOT(selectedFilesChanged())); mPicViewer = SmGlobals::instance()->pictureViewer(); QWidget *fileWidget = new QWidget; QHBoxLayout *directoryEdit = new QHBoxLayout; QLabel *dirLabel = new QLabel(tr("&Directory")); mDirEdit = new QLineEdit; QCompleter *completer = new QCompleter(this); completer->setModel(mModel); completer->setCompletionMode(QCompleter::PopupCompletion); mDirEdit->setCompleter(completer); dirLabel->setBuddy(mDirEdit); directoryEdit->addWidget(dirLabel); directoryEdit->addWidget(mDirEdit); QVBoxLayout *fwLayout = new QVBoxLayout; fwLayout->addLayout(directoryEdit); fwLayout->addWidget(mFileView); fileWidget->setLayout(fwLayout); connect(mDirView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(directoryChanged(const QModelIndex &, const QModelIndex &))); connect(mDirView, SIGNAL(expanded(QModelIndex)), this, SLOT(dirExpanded(QModelIndex))); connect(mDirView, SIGNAL(collapsed(QModelIndex)), this, SLOT(dirCollapsed(QModelIndex))); connect(mFileView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(fileViewActivated(const QModelIndex &))); connect(mFileView, SIGNAL(enterPressed(const QModelIndex &)), this, SLOT(fileViewActivated(const QModelIndex &))); connect(mFileView, SIGNAL(upDir()), this, SLOT(parentDir())); connect(mDirEdit, SIGNAL(returnPressed()), this, SLOT(directoryEdited())); connect(mFileView, SIGNAL(delFiles()), this, SLOT(deleteFiles())); connect(mFileView, SIGNAL(editorClosed(QModelIndex)), this, SLOT(fileEditorClosed(QModelIndex))); mFileView->resizeColumnToContents(0); QVBoxLayout *mainLayout = new QVBoxLayout; QSplitter *splitter = new QSplitter; splitter->addWidget(mDirView); splitter->addWidget(fileWidget); splitter->setStretchFactor(0, 1); splitter->setStretchFactor(1, 2); mainLayout->addWidget(splitter); setLayout(mainLayout); } bool FilesystemWidget::isMounted(){ QSettings s; QString mount = s.value("paths/dvdmount").toString(); if(mount.isEmpty()){ return false; } QFile mounts("/proc/mounts"); if(!mounts.exists() || !mounts.open(QFile::ReadOnly)){ return false; } QTextStream mountStream(&mounts); QString line; do { line = mountStream.readLine(); if(line.contains(mount)){ return true; } } while(!line.isNull()); return false; } void FilesystemWidget::directoryChanged(const QModelIndex &selected, const QModelIndex &deselected){ QModelIndex real = mDirProxy->mapToSource(selected); if(!real.isValid()){ return; } QModelIndex realPrev = mDirProxy->mapToSource(deselected); if(realPrev.isValid()){ mLastDir = realPrev.data(QFileSystemModel::FilePathRole).toString(); } mModel->setRootPath(mModel->filePath(real)); mDirEdit->setText(mModel->filePath(real)); setWindowTitle(mModel->filePath(real)); mFileView->selectionModel()->clear(); mFileView->setRootIndex(mFileProxy->mapFromSource(real)); } void FilesystemWidget::directoryEdited(){ QString path = mDirEdit->text(); if(path.isEmpty()){ return; } QModelIndex index = mModel->index(path); if(index.isValid()){ mDirView->setCurrentIndex(mDirProxy->mapFromSource(index)); } mFileView->setFocus(Qt::ActiveWindowFocusReason); } void FilesystemWidget::fileViewActivated(const QModelIndex &idx){ QModelIndex real = mFileProxy->mapToSource(idx); if(mModel->isDir(real)){ if(idx.data().toString() == ".."){ parentDir(); return; } fileView()->selectionModel()->select(idx, QItemSelectionModel::Deselect); mDirView->setCurrentIndex(mDirProxy->mapFromSource(real)); return; } QString path = mModel->filePath(real); QString mt = Helper::mimeType(path); QStringList programArgs; QString program; if(mt.toLower().startsWith("video")){ QPair data = programData("movieviewer", QString()); if(data.first.isEmpty()){ QMessageBox::critical(this, tr("Error"), tr("No viedeo viewer configured.")); return; } program = data.first; programArgs = data.second; } if(mt.toLower().startsWith("image")){ if(!mPicViewer->isVisible()){ mPicViewer->setVisible(true); } mPicViewer->setFile(path); return; } programArgs << path; QProcess::startDetached(program, programArgs); } void FilesystemWidget::parentDir(){ QModelIndex idx = mDirView->currentIndex(); if(idx.parent().isValid()){ mDirView->setCurrentIndex(idx.parent()); } } void FilesystemWidget::goBack(){ if(mLastDir.isEmpty()){ return; } QModelIndex lastIdx = mModel->index(mLastDir); if(lastIdx.isValid()){ mDirView->selectionModel()->setCurrentIndex(mDirProxy->mapFromSource(lastIdx), QItemSelectionModel::ClearAndSelect); } } void FilesystemWidget::deleteFiles(){ QSortFilterProxyModel *proxy = static_cast(mFileView->model()); QModelIndexList selected = mFileView->selectionModel()->selectedRows(); int count(0); if(!selected.isEmpty()){ count = selected.count(); QString message = QString(tr("Really delete %1 files?")).arg(QString::number(selected.count())); int retval = QMessageBox::question(this, tr("Question"), message, QMessageBox::Yes | QMessageBox::No); if(retval == QMessageBox::Yes){ foreach(QModelIndex idx, selected){ QModelIndex real = proxy->mapToSource(idx); deleteRecursive(mModel->fileInfo(real)); } } mFileView->selectionModel()->clearSelection(); }else{ count = 1; QModelIndex cur = mFileView->currentIndex(); QModelIndex real = proxy->mapToSource(cur); QString message = QString(tr("Really delete %1?")).arg(mModel->fileName(real)); int retval = QMessageBox::question(this, tr("Question"), message, QMessageBox::Yes | QMessageBox::No); if(retval == QMessageBox::Yes){ deleteRecursive(mModel->fileInfo(real)); } } QString message = QString(tr("Deleted %1 file(s)")).arg(count); emit statusbarMessage(message); } void FilesystemWidget::toClipboard(int clipmode){ mClipboardMode = clipmode; QClipboard *clip = qApp->clipboard(); QModelIndexList selected = mFileView->selectionModel()->selectedRows(); clip->clear(); mModel->clearClipboardList(); if(selected.isEmpty()){ return; } QList files; foreach(QModelIndex idx, selected){ if(idx.data(QFileSystemModel::FileNameRole).toString() == ".."){ continue; } files << QUrl::fromLocalFile(idx.data(QFileSystemModel::FilePathRole).toString()); mModel->markForClipboard(mFileProxy->mapToSource(idx)); } QMimeData *mimeData = new QMimeData; mimeData->setUrls(files); clip->setMimeData(mimeData); } void FilesystemWidget::fromClipboard(){ QClipboard *clip = qApp->clipboard(); const QMimeData *mimeData = clip->mimeData(); if(!mimeData->hasUrls()){ return; } QStringList files; foreach(QUrl url, mimeData->urls()){ files << url.toLocalFile(); } const QString destDir = selectedDir(); QFileInfo destDirFi(destDir); if(!destDirFi.isDir()){ return; } if(mClipboardMode == Copy){ mModel->clearClipboardList(); copyFiles(files, destDir); }else if(mClipboardMode == Cut){ mModel->clearClipboardList(); moveFiles(files, destDir); } } void FilesystemWidget::renameFile(){ QModelIndex curIdx = mFileView->currentIndex(); if(curIdx.data().toString() == ".."){ return; } mFileView->edit(curIdx); } void FilesystemWidget::playSelected(const QString &player){ QStringList files = selectedFiles(); if(files.isEmpty()){ statusbarMessage(tr("Nothing selected.")); return; } QPair data = programData("movieviewer", player); if(data.first.isEmpty()){ data = programData("pictureviewer", player); if(data.first.isEmpty()){ QString message = QString(tr("Cannot find program %1.")).arg(player); QMessageBox::critical(this, tr("Error"), message); return; } } QString program = data.first; QStringList programArgs(data.second); programArgs << files; QProcess::startDetached(program, programArgs); } void FilesystemWidget::readSettings(){ QSettings s; QStringList expandedDirs = s.value("paths/expandeddirs").toStringList(); if(expandedDirs.isEmpty()){ expandedDirs << QDir::homePath(); } foreach(QString p, expandedDirs){ QModelIndex idx = mModel->index(p); if(idx.isValid()){ QModelIndex pidx = mDirProxy->mapFromSource(idx); if(pidx.isValid()){ mDirView->setExpanded(pidx, true); } } } QString selectedDir = s.value("paths/selecteddir").toString(); if(!selectedDir.isEmpty()){ QModelIndex diridx = mModel->index(selectedDir); if(diridx.isValid()){ QModelIndex pidx = mDirProxy->mapFromSource(diridx); mDirView->selectionModel()->setCurrentIndex(pidx, QItemSelectionModel::ClearAndSelect); } } QPoint picViewerPos = s.value("windows/picviewer").toPoint(); mPicViewer->move(picViewerPos); } void FilesystemWidget::writeSettings(){ QSettings s; s.setValue("paths/expandeddirs", mExpandedDirs); QModelIndex currentDir = mDirView->selectionModel()->currentIndex(); if(currentDir.isValid()){ QModelIndex real = mDirProxy->mapToSource(currentDir); QString dir = mModel->filePath(real); s.setValue("paths/selecteddir", dir); } s.setValue("windows/picviewer", mPicViewer->pos()); } void FilesystemWidget::configChanged(){ mModel->setIconProvider(mIconProvider); } void FilesystemWidget::dvdMount(){ QSettings s; QString mountDir = s.value("paths/dvdmount").toString(); if(isMounted()){ int retval = QProcess::execute("umount", QStringList() << mountDir); if(retval){ QString message = QString(tr("Could not unmount %1: %2")).arg(mountDir).arg(strerror(retval)); QMessageBox::critical(this, tr("Error"), message); return; } emit mounted(false); }else{ int retval = -1; int ctr = 0; while(retval){ retval = QProcess::execute("mount", QStringList() << mountDir); ++ctr; if(ctr > 4){ break; } } QModelIndex mIdx = mDirProxy->mapFromSource(mModel->index(mountDir)); mDirView->selectionModel()->setCurrentIndex(mIdx, QItemSelectionModel::ClearAndSelect); if(!isMounted()){ QString message = QString(tr("Could not mount %1: %2")).arg(mountDir).arg(strerror(retval)); QMessageBox::critical(this, tr("Error"), message); emit mounted(isMounted()); return; } //ugly hack to update QFileSytemModel QString tDirPath = mLastDir.isEmpty() ? QDir::homePath() : mLastDir; QModelIndex tIdx = mDirProxy->mapFromSource(mModel->index(tDirPath)); mDirView->selectionModel()->setCurrentIndex(tIdx, QItemSelectionModel::ClearAndSelect); mDirView->selectionModel()->setCurrentIndex(mIdx, QItemSelectionModel::ClearAndSelect); mDirView->selectionModel()->setCurrentIndex(tIdx, QItemSelectionModel::ClearAndSelect); mDirView->selectionModel()->setCurrentIndex(mIdx, QItemSelectionModel::ClearAndSelect); mDirView->expand(mIdx); emit mounted(isMounted()); } } void FilesystemWidget::markSeen(){ QStringList selected = selectedFiles(); if(selected.isEmpty()){ return; } foreach(QString p, selected){ mModel->markAsSeen(p, !mModel->isSeen(p)); } } void FilesystemWidget::selectAllPV(){ QModelIndex idx = mDirView->currentIndex(); if(idx.isValid()){ QModelIndex real = mDirProxy->mapToSource(idx); QString filePath = real.data(QFileSystemModel::FilePathRole).toString(); mPicViewer->addFiles(filePath, true); } } void FilesystemWidget::setWindowTitle(const QString &dir){ mWindowTitle = QString("%1 - %2").arg(qApp->applicationName()).arg(dir); emit windowTitle(mWindowTitle); } void FilesystemWidget::deleteRecursive(const QFileInfo &start){ if(start.isDir()){ QDir curDir = QDir(start.absoluteFilePath());; foreach(QFileInfo info, curDir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)){ if(info.isDir()){ deleteRecursive(info); }else{ QFile::remove(info.absoluteFilePath()); } } QDir dir = start.absoluteDir(); dir.rmdir(start.fileName()); }else{ QFile::remove(start.absoluteFilePath()); } } void FilesystemWidget::copyFiles(const QStringList &files, const QString &dest){ foreach(const QString file, files){ QFileInfo fi(file); if(fi.isDir()){ copyRecursive(fi, dest); }else if(fi.isFile()){ const QString destFile = QString("%1/%2").arg(dest).arg(fi.fileName()); QFile::copy(fi.absoluteFilePath(), destFile); } } } void FilesystemWidget::moveFiles(const QStringList &files, const QString &dest){ foreach(const QString file, files){ QFileInfo fi(file); const QString destFile = QString("%1/%2").arg(dest).arg(fi.fileName()); QFile::rename(file, destFile); } } QPair FilesystemWidget::programData(const QString &prefix, const QString &preferred){ QSettings s; QString section = QString("programs_%1").arg(prefix); QHash data = s.value(QString("%1/data").arg(section)).toHash(); if(data.isEmpty()){ return QPair(); } QHash programData; if(!preferred.isEmpty()){ if(data.keys().contains(preferred)){ programData = data.value(preferred).toHash(); return qMakePair(programData.value("path").toString(), programData.value("args").toStringList()); } return QPair(); } QString defaultProg = s.value(QString("%1/default").arg(section)).toString(); if(defaultProg.isEmpty()){ return QPair(); } programData = data.value(defaultProg).toHash(); return qMakePair(programData.value("path").toString(), programData.value("args").toStringList()); } void FilesystemWidget::copyRecursive(const QFileInfo &start, const QString &destdir){ if(!start.isDir()){ return; } QDir source(start.absoluteFilePath()); QDir dest = QDir(destdir); if(!dest.exists(source.dirName())){ dest.mkdir(source.dirName()); } QString d = QString("%1/%2").arg(destdir).arg(source.dirName()); foreach(QFileInfo cur, source.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)){ if(cur.isDir()){ copyRecursive(cur, d); }else{ QString destPath = QString("%1/%2").arg(d).arg(cur.fileName()); QFile::copy(cur.absoluteFilePath(), destPath); } } } const QString FilesystemWidget::selectedDir(){ const QModelIndexList selected = mDirView->selectionModel()->selectedRows(); if(!selected.isEmpty()){ return selected.at(0).data(QFileSystemModel::FilePathRole).toString(); } return QString(); } void FilesystemWidget::dirExpanded(const QModelIndex &idx){ QModelIndex real = mDirProxy->mapToSource(idx); if(real.isValid()){ mExpandedDirs << mModel->filePath(real); } } void FilesystemWidget::dirCollapsed(const QModelIndex &idx){ QModelIndex real = mDirProxy->mapToSource(idx); if(real.isValid()){ QString path = mModel->filePath(real); if(mExpandedDirs.contains(path)){ mExpandedDirs.removeAll(path); } } } void FilesystemWidget::fileEditorClosed(const QModelIndex &idx){ QModelIndex real = mDirProxy->mapFromSource(idx); if(real.isValid()){ mDirView->update(real); } } QStringList FilesystemWidget::selectedFiles(){ QStringList retval; QModelIndexList selected = fileView()->selectionModel()->selectedRows(); if(selected.isEmpty()){ return QStringList(); } QSortFilterProxyModel *proxy = static_cast(fileView()->model()); foreach(QModelIndex idx, selected){ QModelIndex src = proxy->mapToSource(idx); retval << mModel->filePath(src); } return retval; } //FileSystemModel FileSystemModel::FileSystemModel(QObject *parent) : QFileSystemModel(parent){ mDb = QSqlDatabase::database("treedb"); mDb.open(); QSqlQuery readSeen("SELECT tpath, dtseen FROM seen", mDb); while(readSeen.next()){ mSeen.insert(readSeen.value(0).toString(), readSeen.value(1).toDateTime()); } mDeleteFromSeenQuery = new QSqlQuery(mDb); mDeleteFromSeenQuery->prepare("DELETE FROM seen WHERE tpath = :path"); mMarkAsSeenQuery = new QSqlQuery(mDb); mMarkAsSeenQuery->prepare("INSERT INTO seen (tpath) VALUES(:path)"); cleanup(); } FileSystemModel::~FileSystemModel(){ delete mDeleteFromSeenQuery; delete mMarkAsSeenQuery; mDb = QSqlDatabase(); } QVariant FileSystemModel::data(const QModelIndex &index, int role) const{ if(role == Qt::ForegroundRole){ QString path = index.data(QFileSystemModel::FilePathRole).toString(); if(mSeen.keys().contains(path)){ return QBrush(Qt::red); } if(mClipEntries.contains(index)){ return QBrush(Qt::darkBlue); } } return QFileSystemModel::data(index, role); } bool FileSystemModel::isSeen(const QString &path) const{ return mSeen.keys().contains(path); } void FileSystemModel::markAsSeen(const QString &path, bool seen){ if(seen){ if(mSeen.keys().contains(path)){ return; } mMarkAsSeenQuery->bindValue(":path", path); mMarkAsSeenQuery->exec(); mSeen.insert(path, QDateTime::currentDateTime()); }else{ mDeleteFromSeenQuery->bindValue(":path", path); mDeleteFromSeenQuery->exec(); mSeen.remove(path); } } void FileSystemModel::markForClipboard(const QPersistentModelIndex &idx){ if(idx.isValid()){ if(!mClipEntries.contains(idx)){ mClipEntries << idx; } } } void FileSystemModel::clearClipboardList(){ QList tmp(mClipEntries); mClipEntries.clear(); foreach(QPersistentModelIndex ent, tmp){ emit dataChanged(ent, ent); } } void FileSystemModel::cleanup(){ QStringList toRemove; foreach(QString p, mSeen.keys()){ QFileInfo fi(p); if(!fi.exists()){ mDeleteFromSeenQuery->bindValue(":path", p); mDeleteFromSeenQuery->exec(); toRemove << p; } } foreach(QString r, toRemove){ mSeen.remove(r); } }