feat(ui): add power-user file actions
This commit is contained in:
+2
-3
@@ -5,16 +5,15 @@ set(CMAKE_CXX_STANDARD 20)
|
|||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent)
|
find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent DBus)
|
||||||
|
|
||||||
qt_add_executable(searchmyfiles
|
qt_add_executable(searchmyfiles
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(searchmyfiles PRIVATE Qt6::Widgets Qt6::Concurrent)
|
target_link_libraries(searchmyfiles PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::DBus)
|
||||||
target_compile_options(searchmyfiles PRIVATE -Wall -Wextra -Wpedantic)
|
target_compile_options(searchmyfiles PRIVATE -Wall -Wextra -Wpedantic)
|
||||||
|
|
||||||
install(TARGETS searchmyfiles RUNTIME DESTINATION bin)
|
install(TARGETS searchmyfiles RUNTIME DESTINATION bin)
|
||||||
install(FILES packaging/searchmyfiles.desktop
|
install(FILES packaging/searchmyfiles.desktop
|
||||||
DESTINATION share/applications)
|
DESTINATION share/applications)
|
||||||
|
|
||||||
|
|||||||
+527
-23
@@ -1,10 +1,15 @@
|
|||||||
#include <QAbstractTableModel>
|
#include <QAbstractTableModel>
|
||||||
|
#include <QActionGroup>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
|
#include <QCloseEvent>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
#include <QDBusConnection>
|
||||||
|
#include <QDBusInterface>
|
||||||
|
#include <QDBusMessage>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
@@ -20,6 +25,7 @@
|
|||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QKeyEvent>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
@@ -48,6 +54,7 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <functional>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@@ -86,6 +93,8 @@ struct SearchOptions {
|
|||||||
QString mode = QStringLiteral("Standard search");
|
QString mode = QStringLiteral("Standard search");
|
||||||
QString duplicateNameMode = QStringLiteral("All files and folders");
|
QString duplicateNameMode = QStringLiteral("All files and folders");
|
||||||
bool duplicateNameWithoutExtension = false;
|
bool duplicateNameWithoutExtension = false;
|
||||||
|
bool showOnlyDuplicateFiles = true;
|
||||||
|
bool includeSubfoldersInSummary = false;
|
||||||
QString hidden = QStringLiteral("Any");
|
QString hidden = QStringLiteral("Any");
|
||||||
QString readonly = QStringLiteral("Any");
|
QString readonly = QStringLiteral("Any");
|
||||||
QString executable = QStringLiteral("Any");
|
QString executable = QStringLiteral("Any");
|
||||||
@@ -550,6 +559,10 @@ public slots:
|
|||||||
for (const auto &record : candidates)
|
for (const auto &record : candidates)
|
||||||
if (record.type == QStringLiteral("File") && !duplicatedPaths.contains(record.path))
|
if (record.type == QStringLiteral("File") && !duplicatedPaths.contains(record.path))
|
||||||
results.push_back(record);
|
results.push_back(record);
|
||||||
|
} else if (!o.showOnlyDuplicateFiles) {
|
||||||
|
for (const auto &record : candidates)
|
||||||
|
if (record.type == QStringLiteral("File") && !duplicatedPaths.contains(record.path))
|
||||||
|
results.push_back(record);
|
||||||
}
|
}
|
||||||
} else if (o.mode == QStringLiteral("Duplicate names search")) {
|
} else if (o.mode == QStringLiteral("Duplicate names search")) {
|
||||||
QHash<QString, QVector<FileRecord>> byName;
|
QHash<QString, QVector<FileRecord>> byName;
|
||||||
@@ -592,8 +605,25 @@ public slots:
|
|||||||
}
|
}
|
||||||
} else if (o.mode == QStringLiteral("Summary")) {
|
} else if (o.mode == QStringLiteral("Summary")) {
|
||||||
QHash<QString, QVector<FileRecord>> byFolder;
|
QHash<QString, QVector<FileRecord>> byFolder;
|
||||||
for (const auto &record : candidates)
|
for (const auto &record : candidates) {
|
||||||
byFolder[record.folder].push_back(record);
|
byFolder[record.folder].push_back(record);
|
||||||
|
if (o.includeSubfoldersInSummary) {
|
||||||
|
QDir parent(record.folder);
|
||||||
|
while (parent.cdUp()) {
|
||||||
|
const QString ancestor = parent.absolutePath();
|
||||||
|
bool belongsToRoot = false;
|
||||||
|
for (const QString &root : patterns(o.roots)) {
|
||||||
|
if (ancestor == QDir(root).absolutePath()) {
|
||||||
|
belongsToRoot = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byFolder[ancestor].push_back(record);
|
||||||
|
if (belongsToRoot)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for (auto it = byFolder.cbegin(); it != byFolder.cend(); ++it) {
|
for (auto it = byFolder.cbegin(); it != byFolder.cend(); ++it) {
|
||||||
qint64 total = 0;
|
qint64 total = 0;
|
||||||
qint64 totalOnDisk = 0;
|
qint64 totalOnDisk = 0;
|
||||||
@@ -982,6 +1012,7 @@ public:
|
|||||||
setWindowTitle(tr("SearchMyFiles for Linux"));
|
setWindowTitle(tr("SearchMyFiles for Linux"));
|
||||||
resize(1280, 760);
|
resize(1280, 760);
|
||||||
model_ = new ResultsModel(this);
|
model_ = new ResultsModel(this);
|
||||||
|
model_->setDisplayOptions(sizeUnit_, showGmt_, markDuplicates_, duplicateColorSet_);
|
||||||
proxy_ = new QSortFilterProxyModel(this);
|
proxy_ = new QSortFilterProxyModel(this);
|
||||||
proxy_->setSourceModel(model_);
|
proxy_->setSourceModel(model_);
|
||||||
proxy_->setSortRole(Qt::EditRole);
|
proxy_->setSortRole(Qt::EditRole);
|
||||||
@@ -995,10 +1026,18 @@ public:
|
|||||||
table_->horizontalHeader()->setSectionResizeMode(ResultsModel::Name, QHeaderView::ResizeToContents);
|
table_->horizontalHeader()->setSectionResizeMode(ResultsModel::Name, QHeaderView::ResizeToContents);
|
||||||
table_->horizontalHeader()->setSectionResizeMode(ResultsModel::Folder, QHeaderView::Stretch);
|
table_->horizontalHeader()->setSectionResizeMode(ResultsModel::Folder, QHeaderView::Stretch);
|
||||||
setCentralWidget(table_);
|
setCentralWidget(table_);
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
restoreGeometry(settings.value(QStringLiteral("ui/windowGeometry")).toByteArray());
|
||||||
|
table_->horizontalHeader()->restoreState(
|
||||||
|
settings.value(QStringLiteral("ui/headerState")).toByteArray());
|
||||||
|
table_->setShowGrid(showGrid_);
|
||||||
|
table_->viewport()->setAttribute(Qt::WA_AlwaysShowToolTips, showTooltips_);
|
||||||
|
}
|
||||||
|
|
||||||
auto *toolbar = addToolBar(tr("Main"));
|
auto *toolbar = addToolBar(tr("Main"));
|
||||||
searchAction_ = toolbar->addAction(QIcon::fromTheme(QStringLiteral("system-search")), tr("Search options…"));
|
searchAction_ = toolbar->addAction(QIcon::fromTheme(QStringLiteral("system-search")), tr("Search options…"));
|
||||||
searchAction_->setShortcut(Qt::Key_F9);
|
searchAction_->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_F9));
|
||||||
stopAction_ = toolbar->addAction(QIcon::fromTheme(QStringLiteral("process-stop")), tr("Stop"));
|
stopAction_ = toolbar->addAction(QIcon::fromTheme(QStringLiteral("process-stop")), tr("Stop"));
|
||||||
stopAction_->setShortcut(Qt::Key_Escape);
|
stopAction_->setShortcut(Qt::Key_Escape);
|
||||||
stopAction_->setEnabled(false);
|
stopAction_->setEnabled(false);
|
||||||
@@ -1008,8 +1047,23 @@ public:
|
|||||||
auto *exportAction = toolbar->addAction(QIcon::fromTheme(QStringLiteral("document-save")), tr("Export…"));
|
auto *exportAction = toolbar->addAction(QIcon::fromTheme(QStringLiteral("document-save")), tr("Export…"));
|
||||||
exportAction->setShortcut(QKeySequence::Save);
|
exportAction->setShortcut(QKeySequence::Save);
|
||||||
auto *openAction = toolbar->addAction(QIcon::fromTheme(QStringLiteral("document-open")), tr("Open"));
|
auto *openAction = toolbar->addAction(QIcon::fromTheme(QStringLiteral("document-open")), tr("Open"));
|
||||||
openAction->setShortcut(Qt::Key_Return);
|
openAction->setShortcut(Qt::Key_F9);
|
||||||
auto *folderAction = toolbar->addAction(QIcon::fromTheme(QStringLiteral("folder-open")), tr("Open folder"));
|
auto *folderAction = toolbar->addAction(QIcon::fromTheme(QStringLiteral("folder-open")), tr("Open folder"));
|
||||||
|
folderAction->setShortcut(Qt::Key_F8);
|
||||||
|
auto *openWithAction = new QAction(tr("Open With…"), this);
|
||||||
|
openWithAction->setShortcut(Qt::Key_F7);
|
||||||
|
auto *selectInFolderAction = new QAction(tr("Select File In File Manager"), this);
|
||||||
|
auto *trashAction = new QAction(tr("Move To Trash"), this);
|
||||||
|
trashAction->setShortcut(Qt::Key_Delete);
|
||||||
|
auto *deleteAction = new QAction(tr("Delete Selected Files"), this);
|
||||||
|
deleteAction->setShortcut(QKeySequence(Qt::SHIFT | Qt::Key_Delete));
|
||||||
|
auto *renameAction = new QAction(tr("Rename"), this);
|
||||||
|
renameAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_R));
|
||||||
|
auto *propertiesAction = new QAction(tr("Properties"), this);
|
||||||
|
propertiesAction->setShortcut(QKeySequence(Qt::ALT | Qt::Key_Return));
|
||||||
|
for (QAction *action : {openWithAction, selectInFolderAction, trashAction,
|
||||||
|
deleteAction, renameAction, propertiesAction})
|
||||||
|
addAction(action);
|
||||||
|
|
||||||
auto *fileMenu = menuBar()->addMenu(tr("&File"));
|
auto *fileMenu = menuBar()->addMenu(tr("&File"));
|
||||||
fileMenu->addAction(searchAction_);
|
fileMenu->addAction(searchAction_);
|
||||||
@@ -1018,8 +1072,15 @@ public:
|
|||||||
fileMenu->addSeparator();
|
fileMenu->addSeparator();
|
||||||
fileMenu->addAction(openAction);
|
fileMenu->addAction(openAction);
|
||||||
fileMenu->addAction(folderAction);
|
fileMenu->addAction(folderAction);
|
||||||
|
fileMenu->addAction(openWithAction);
|
||||||
|
fileMenu->addAction(selectInFolderAction);
|
||||||
|
fileMenu->addSeparator();
|
||||||
|
fileMenu->addAction(trashAction);
|
||||||
|
fileMenu->addAction(deleteAction);
|
||||||
|
fileMenu->addAction(renameAction);
|
||||||
fileMenu->addSeparator();
|
fileMenu->addSeparator();
|
||||||
fileMenu->addAction(exportAction);
|
fileMenu->addAction(exportAction);
|
||||||
|
fileMenu->addAction(propertiesAction);
|
||||||
fileMenu->addSeparator();
|
fileMenu->addSeparator();
|
||||||
fileMenu->addAction(tr("Exit"), QKeySequence::Quit, this, &QWidget::close);
|
fileMenu->addAction(tr("Exit"), QKeySequence::Quit, this, &QWidget::close);
|
||||||
|
|
||||||
@@ -1035,6 +1096,8 @@ public:
|
|||||||
copyPathsAction->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_C));
|
copyPathsAction->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_C));
|
||||||
auto *explorerCopyAction = editMenu->addAction(tr("Explorer copy"));
|
auto *explorerCopyAction = editMenu->addAction(tr("Explorer copy"));
|
||||||
explorerCopyAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E));
|
explorerCopyAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E));
|
||||||
|
auto *explorerCutAction = editMenu->addAction(tr("Explorer cut"));
|
||||||
|
explorerCutAction->setShortcut(QKeySequence::Cut);
|
||||||
editMenu->addSeparator();
|
editMenu->addSeparator();
|
||||||
editMenu->addAction(tr("Select all"), QKeySequence::SelectAll, table_, &QTableView::selectAll);
|
editMenu->addAction(tr("Select all"), QKeySequence::SelectAll, table_, &QTableView::selectAll);
|
||||||
auto *deselectAction = editMenu->addAction(tr("Deselect all"));
|
auto *deselectAction = editMenu->addAction(tr("Deselect all"));
|
||||||
@@ -1043,26 +1106,129 @@ public:
|
|||||||
auto *viewMenu = menuBar()->addMenu(tr("&View"));
|
auto *viewMenu = menuBar()->addMenu(tr("&View"));
|
||||||
auto *gridAction = viewMenu->addAction(tr("Show grid lines"));
|
auto *gridAction = viewMenu->addAction(tr("Show grid lines"));
|
||||||
gridAction->setCheckable(true);
|
gridAction->setCheckable(true);
|
||||||
gridAction->setChecked(true);
|
gridAction->setChecked(showGrid_);
|
||||||
auto *tooltipsAction = viewMenu->addAction(tr("Show tooltips"));
|
auto *tooltipsAction = viewMenu->addAction(tr("Show tooltips"));
|
||||||
tooltipsAction->setCheckable(true);
|
tooltipsAction->setCheckable(true);
|
||||||
tooltipsAction->setChecked(true);
|
tooltipsAction->setChecked(showTooltips_);
|
||||||
viewMenu->addSeparator();
|
viewMenu->addSeparator();
|
||||||
viewMenu->addAction(tr("Auto size columns"), this, [this] {
|
auto *autoSizeAction = viewMenu->addAction(tr("Auto size columns"), this, [this] {
|
||||||
table_->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
|
table_->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
|
||||||
table_->resizeColumnsToContents();
|
table_->resizeColumnsToContents();
|
||||||
|
saveUiSettings();
|
||||||
});
|
});
|
||||||
|
autoSizeAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Plus));
|
||||||
auto *columnsMenu = viewMenu->addMenu(tr("Choose columns"));
|
auto *columnsMenu = viewMenu->addMenu(tr("Choose columns"));
|
||||||
for (int column = 0; column < ResultsModel::ColumnCount; ++column) {
|
for (int column = 0; column < ResultsModel::ColumnCount; ++column) {
|
||||||
auto *action = columnsMenu->addAction(
|
auto *action = columnsMenu->addAction(
|
||||||
model_->headerData(column, Qt::Horizontal, Qt::DisplayRole).toString());
|
model_->headerData(column, Qt::Horizontal, Qt::DisplayRole).toString());
|
||||||
action->setCheckable(true);
|
action->setCheckable(true);
|
||||||
action->setChecked(true);
|
action->setChecked(!table_->isColumnHidden(column));
|
||||||
connect(action, &QAction::toggled, this, [this, column](bool visible) {
|
connect(action, &QAction::toggled, this, [this, column](bool visible) {
|
||||||
table_->setColumnHidden(column, !visible);
|
table_->setColumnHidden(column, !visible);
|
||||||
|
saveUiSettings();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto *optionsMenu = menuBar()->addMenu(tr("&Options"));
|
||||||
|
const auto addChoiceMenu = [this, optionsMenu](
|
||||||
|
const QString &title,
|
||||||
|
const QStringList &choices,
|
||||||
|
const QString &selected,
|
||||||
|
const std::function<void(const QString &)> &changed) {
|
||||||
|
auto *menu = optionsMenu->addMenu(title);
|
||||||
|
auto *group = new QActionGroup(menu);
|
||||||
|
group->setExclusive(true);
|
||||||
|
for (const QString &choice : choices) {
|
||||||
|
auto *action = menu->addAction(choice);
|
||||||
|
action->setCheckable(true);
|
||||||
|
action->setChecked(choice == selected);
|
||||||
|
group->addAction(action);
|
||||||
|
connect(action, &QAction::triggered, this, [this, choice, changed] {
|
||||||
|
changed(choice);
|
||||||
|
saveUiSettings();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return menu;
|
||||||
|
};
|
||||||
|
|
||||||
|
addChoiceMenu(tr("File Size Unit"),
|
||||||
|
{tr("Automatic"), tr("Bytes"), tr("KB"), tr("MB"), tr("GB")},
|
||||||
|
sizeUnitName(), [this](const QString &choice) {
|
||||||
|
sizeUnit_ = sizeUnitFromName(choice);
|
||||||
|
applyDisplayOptions();
|
||||||
|
});
|
||||||
|
addChoiceMenu(tr("Summary File Size Unit"),
|
||||||
|
{tr("Automatic"), tr("Bytes"), tr("KB"), tr("MB"), tr("GB")},
|
||||||
|
summarySizeUnitName_, [this](const QString &choice) {
|
||||||
|
summarySizeUnitName_ = choice;
|
||||||
|
if (options_.mode == QStringLiteral("Summary"))
|
||||||
|
applyDisplayOptions();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto *duplicateOptions = optionsMenu->addMenu(tr("Duplicate Search Options"));
|
||||||
|
auto *duplicateOptionsGroup = new QActionGroup(duplicateOptions);
|
||||||
|
auto *showAllDuplicates = duplicateOptions->addAction(tr("Show All Files"));
|
||||||
|
auto *showOnlyDuplicates = duplicateOptions->addAction(tr("Show Only Duplicate Files"));
|
||||||
|
for (QAction *action : {showAllDuplicates, showOnlyDuplicates}) {
|
||||||
|
action->setCheckable(true);
|
||||||
|
duplicateOptionsGroup->addAction(action);
|
||||||
|
}
|
||||||
|
showOnlyDuplicates->setChecked(options_.showOnlyDuplicateFiles);
|
||||||
|
showAllDuplicates->setChecked(!options_.showOnlyDuplicateFiles);
|
||||||
|
connect(showOnlyDuplicates, &QAction::triggered, this, [this] {
|
||||||
|
options_.showOnlyDuplicateFiles = true;
|
||||||
|
saveOptions();
|
||||||
|
});
|
||||||
|
connect(showAllDuplicates, &QAction::triggered, this, [this] {
|
||||||
|
options_.showOnlyDuplicateFiles = false;
|
||||||
|
saveOptions();
|
||||||
|
});
|
||||||
|
|
||||||
|
addChoiceMenu(tr("Duplicate Mark Color Set"),
|
||||||
|
{tr("Color Set 1"), tr("Color Set 2")},
|
||||||
|
duplicateColorSet_ == 1 ? tr("Color Set 1") : tr("Color Set 2"),
|
||||||
|
[this](const QString &choice) {
|
||||||
|
duplicateColorSet_ = choice.endsWith(u'1') ? 1 : 2;
|
||||||
|
applyDisplayOptions();
|
||||||
|
});
|
||||||
|
addChoiceMenu(tr("Double-Click Action"), actionChoices(), doubleClickAction_,
|
||||||
|
[this](const QString &choice) { doubleClickAction_ = choice; });
|
||||||
|
addChoiceMenu(tr("Enter Key Action"), actionChoices(), enterKeyAction_,
|
||||||
|
[this](const QString &choice) { enterKeyAction_ = choice; });
|
||||||
|
|
||||||
|
auto addToggle = [this, optionsMenu](const QString &label, bool checked,
|
||||||
|
const std::function<void(bool)> &changed) {
|
||||||
|
auto *action = optionsMenu->addAction(label);
|
||||||
|
action->setCheckable(true);
|
||||||
|
action->setChecked(checked);
|
||||||
|
connect(action, &QAction::toggled, this, [this, changed](bool value) {
|
||||||
|
changed(value);
|
||||||
|
saveUiSettings();
|
||||||
|
});
|
||||||
|
return action;
|
||||||
|
};
|
||||||
|
addToggle(tr("Include Subfolders in Summary Totals"), options_.includeSubfoldersInSummary,
|
||||||
|
[this](bool value) { options_.includeSubfoldersInSummary = value; saveOptions(); });
|
||||||
|
addToggle(tr("Show Time In GMT"), showGmt_, [this](bool value) {
|
||||||
|
showGmt_ = value; applyDisplayOptions();
|
||||||
|
});
|
||||||
|
optionsMenu->addSeparator();
|
||||||
|
addToggle(tr("Mark Duplicate Files"), markDuplicates_, [this](bool value) {
|
||||||
|
markDuplicates_ = value; applyDisplayOptions();
|
||||||
|
});
|
||||||
|
addToggle(tr("Add Header Line To CSV/Tab-Delimited File"), addExportHeader_,
|
||||||
|
[this](bool value) { addExportHeader_ = value; });
|
||||||
|
addToggle(tr("Retrieve File Owner"), options_.retrieveOwner,
|
||||||
|
[this](bool value) { options_.retrieveOwner = value; saveOptions(); });
|
||||||
|
addToggle(tr("Accurate Progress (Count Files First)"), options_.accurateProgress,
|
||||||
|
[this](bool value) { options_.accurateProgress = value; saveOptions(); });
|
||||||
|
addToggle(tr("Set Focus On Search Start"), focusOnSearchStart_,
|
||||||
|
[this](bool value) { focusOnSearchStart_ = value; });
|
||||||
|
addToggle(tr("Set Focus On Search End"), focusOnSearchEnd_,
|
||||||
|
[this](bool value) { focusOnSearchEnd_ = value; });
|
||||||
|
addToggle(tr("Auto Size Columns On Search End"), autoSizeOnSearchEnd_,
|
||||||
|
[this](bool value) { autoSizeOnSearchEnd_ = value; });
|
||||||
|
|
||||||
menuBar()->addMenu(tr("&Help"))->addAction(tr("About"), this, [this] {
|
menuBar()->addMenu(tr("&Help"))->addAction(tr("About"), this, [this] {
|
||||||
QMessageBox::about(this, tr("About"),
|
QMessageBox::about(this, tr("About"),
|
||||||
tr("SearchMyFiles for Linux\n\nFast unindexed file search.\nC++20 + Qt 6."));
|
tr("SearchMyFiles for Linux\n\nFast unindexed file search.\nC++20 + Qt 6."));
|
||||||
@@ -1075,7 +1241,7 @@ public:
|
|||||||
progress_->setTextVisible(true);
|
progress_->setTextVisible(true);
|
||||||
progress_->hide();
|
progress_->hide();
|
||||||
statusBar()->addPermanentWidget(progress_);
|
statusBar()->addPermanentWidget(progress_);
|
||||||
statusBar()->showMessage(tr("Press F9 to configure and start a search"));
|
statusBar()->showMessage(tr("Press Ctrl+F9 to configure and start a search"));
|
||||||
|
|
||||||
engine_ = new SearchEngine;
|
engine_ = new SearchEngine;
|
||||||
engine_->moveToThread(&workerThread_);
|
engine_->moveToThread(&workerThread_);
|
||||||
@@ -1115,17 +1281,32 @@ public:
|
|||||||
connect(exportAction, &QAction::triggered, this, &MainWindow::exportResults);
|
connect(exportAction, &QAction::triggered, this, &MainWindow::exportResults);
|
||||||
connect(openAction, &QAction::triggered, this, &MainWindow::openSelected);
|
connect(openAction, &QAction::triggered, this, &MainWindow::openSelected);
|
||||||
connect(folderAction, &QAction::triggered, this, &MainWindow::openFolder);
|
connect(folderAction, &QAction::triggered, this, &MainWindow::openFolder);
|
||||||
|
connect(openWithAction, &QAction::triggered, this, &MainWindow::openWith);
|
||||||
|
connect(selectInFolderAction, &QAction::triggered, this, &MainWindow::selectInFileManager);
|
||||||
|
connect(trashAction, &QAction::triggered, this, &MainWindow::moveToTrash);
|
||||||
|
connect(deleteAction, &QAction::triggered, this, &MainWindow::deleteSelected);
|
||||||
|
connect(renameAction, &QAction::triggered, this, &MainWindow::renameSelected);
|
||||||
|
connect(propertiesAction, &QAction::triggered, this, &MainWindow::showProperties);
|
||||||
connect(findAction, &QAction::triggered, this, &MainWindow::findText);
|
connect(findAction, &QAction::triggered, this, &MainWindow::findText);
|
||||||
connect(findNextAction, &QAction::triggered, this, &MainWindow::findNext);
|
connect(findNextAction, &QAction::triggered, this, &MainWindow::findNext);
|
||||||
connect(copyInfoAction, &QAction::triggered, this, &MainWindow::copyInformation);
|
connect(copyInfoAction, &QAction::triggered, this, &MainWindow::copyInformation);
|
||||||
connect(copyPathsAction, &QAction::triggered, this, &MainWindow::copyPaths);
|
connect(copyPathsAction, &QAction::triggered, this, &MainWindow::copyPaths);
|
||||||
connect(explorerCopyAction, &QAction::triggered, this, &MainWindow::explorerCopy);
|
connect(explorerCopyAction, &QAction::triggered, this, &MainWindow::explorerCopy);
|
||||||
|
connect(explorerCutAction, &QAction::triggered, this, &MainWindow::explorerCut);
|
||||||
connect(deselectAction, &QAction::triggered, table_, &QTableView::clearSelection);
|
connect(deselectAction, &QAction::triggered, table_, &QTableView::clearSelection);
|
||||||
connect(gridAction, &QAction::toggled, table_, &QTableView::setShowGrid);
|
connect(gridAction, &QAction::toggled, this, [this](bool enabled) {
|
||||||
connect(tooltipsAction, &QAction::toggled, this, [this](bool enabled) {
|
showGrid_ = enabled;
|
||||||
table_->viewport()->setAttribute(Qt::WA_AlwaysShowToolTips, enabled);
|
table_->setShowGrid(enabled);
|
||||||
|
saveUiSettings();
|
||||||
|
});
|
||||||
|
connect(tooltipsAction, &QAction::toggled, this, [this](bool enabled) {
|
||||||
|
showTooltips_ = enabled;
|
||||||
|
table_->viewport()->setAttribute(Qt::WA_AlwaysShowToolTips, enabled);
|
||||||
|
saveUiSettings();
|
||||||
|
});
|
||||||
|
connect(table_, &QTableView::doubleClicked, this, [this] {
|
||||||
|
executeConfiguredAction(doubleClickAction_);
|
||||||
});
|
});
|
||||||
connect(table_, &QTableView::doubleClicked, this, &MainWindow::openSelected);
|
|
||||||
connect(table_, &QWidget::customContextMenuRequested, this, &MainWindow::contextMenu);
|
connect(table_, &QWidget::customContextMenuRequested, this, &MainWindow::contextMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1136,6 +1317,24 @@ public:
|
|||||||
workerThread_.wait();
|
workerThread_.wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void closeEvent(QCloseEvent *event) override
|
||||||
|
{
|
||||||
|
saveUiSettings();
|
||||||
|
QMainWindow::closeEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void keyPressEvent(QKeyEvent *event) override
|
||||||
|
{
|
||||||
|
if ((event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
|
||||||
|
&& event->modifiers() == Qt::NoModifier) {
|
||||||
|
executeConfiguredAction(enterKeyAction_);
|
||||||
|
event->accept();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QMainWindow::keyPressEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void startRequested(const SearchOptions &options);
|
void startRequested(const SearchOptions &options);
|
||||||
void stopRequested();
|
void stopRequested();
|
||||||
@@ -1232,11 +1431,31 @@ private slots:
|
|||||||
QApplication::clipboard()->setMimeData(mime);
|
QApplication::clipboard()->setMimeData(mime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void explorerCut()
|
||||||
|
{
|
||||||
|
const QStringList paths = selectedPaths();
|
||||||
|
if (paths.isEmpty())
|
||||||
|
return;
|
||||||
|
auto *mime = new QMimeData;
|
||||||
|
QList<QUrl> urls;
|
||||||
|
QByteArray gnomeData("cut\n");
|
||||||
|
for (const QString &path : paths) {
|
||||||
|
const QUrl url = QUrl::fromLocalFile(path);
|
||||||
|
urls.push_back(url);
|
||||||
|
gnomeData += url.toEncoded() + '\n';
|
||||||
|
}
|
||||||
|
mime->setUrls(urls);
|
||||||
|
mime->setText(paths.join(u'\n'));
|
||||||
|
mime->setData(QStringLiteral("x-special/gnome-copied-files"), gnomeData);
|
||||||
|
QApplication::clipboard()->setMimeData(mime);
|
||||||
|
}
|
||||||
|
|
||||||
void startCurrentSearch()
|
void startCurrentSearch()
|
||||||
{
|
{
|
||||||
if (isSearching_ || options_.roots.trimmed().isEmpty())
|
if (isSearching_ || options_.roots.trimmed().isEmpty())
|
||||||
return;
|
return;
|
||||||
isSearching_ = true;
|
isSearching_ = true;
|
||||||
|
applyDisplayOptions();
|
||||||
model_->setRows({});
|
model_->setRows({});
|
||||||
searchAction_->setEnabled(false);
|
searchAction_->setEnabled(false);
|
||||||
stopAction_->setEnabled(true);
|
stopAction_->setEnabled(true);
|
||||||
@@ -1246,6 +1465,8 @@ private slots:
|
|||||||
progress_->setFormat(tr("Scanning…"));
|
progress_->setFormat(tr("Scanning…"));
|
||||||
setWindowTitle(tr("%1 — %2 — SearchMyFiles for Linux")
|
setWindowTitle(tr("%1 — %2 — SearchMyFiles for Linux")
|
||||||
.arg(options_.roots, options_.mode));
|
.arg(options_.roots, options_.mode));
|
||||||
|
if (focusOnSearchStart_)
|
||||||
|
table_->setFocus();
|
||||||
emit startRequested(options_);
|
emit startRequested(options_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1258,6 +1479,12 @@ private slots:
|
|||||||
refreshAction_->setEnabled(true);
|
refreshAction_->setEnabled(true);
|
||||||
progress_->hide();
|
progress_->hide();
|
||||||
statusBar()->showMessage(message);
|
statusBar()->showMessage(message);
|
||||||
|
if (autoSizeOnSearchEnd_) {
|
||||||
|
table_->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
|
||||||
|
table_->resizeColumnsToContents();
|
||||||
|
}
|
||||||
|
if (focusOnSearchEnd_)
|
||||||
|
table_->setFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void openSelected()
|
void openSelected()
|
||||||
@@ -1273,6 +1500,137 @@ private slots:
|
|||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(paths.front()).absolutePath()));
|
QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(paths.front()).absolutePath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void openWith()
|
||||||
|
{
|
||||||
|
const QStringList paths = selectedPaths();
|
||||||
|
if (!paths.isEmpty())
|
||||||
|
QProcess::startDetached(QStringLiteral("gio"), {QStringLiteral("open"), paths.front()});
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectInFileManager()
|
||||||
|
{
|
||||||
|
const QStringList paths = selectedPaths();
|
||||||
|
if (paths.isEmpty())
|
||||||
|
return;
|
||||||
|
QStringList urls;
|
||||||
|
for (const QString &path : paths)
|
||||||
|
urls.push_back(QUrl::fromLocalFile(path).toString());
|
||||||
|
QDBusInterface fileManager(
|
||||||
|
QStringLiteral("org.freedesktop.FileManager1"),
|
||||||
|
QStringLiteral("/org/freedesktop/FileManager1"),
|
||||||
|
QStringLiteral("org.freedesktop.FileManager1"),
|
||||||
|
QDBusConnection::sessionBus());
|
||||||
|
const QDBusMessage reply = fileManager.call(QStringLiteral("ShowItems"), urls, QString{});
|
||||||
|
if (!fileManager.isValid() || reply.type() == QDBusMessage::ErrorMessage)
|
||||||
|
openFolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
void moveToTrash()
|
||||||
|
{
|
||||||
|
const QStringList paths = selectedPaths();
|
||||||
|
if (paths.isEmpty()
|
||||||
|
|| QMessageBox::question(
|
||||||
|
this, tr("Move To Trash"),
|
||||||
|
tr("Move %1 selected item(s) to Trash?").arg(paths.size()),
|
||||||
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes)
|
||||||
|
return;
|
||||||
|
int failed = 0;
|
||||||
|
for (const QString &path : paths)
|
||||||
|
if (!QFile::moveToTrash(path))
|
||||||
|
++failed;
|
||||||
|
if (failed)
|
||||||
|
QMessageBox::warning(this, tr("Move To Trash"),
|
||||||
|
tr("Failed to move %1 item(s) to Trash.").arg(failed));
|
||||||
|
refreshSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteSelected()
|
||||||
|
{
|
||||||
|
const QStringList paths = selectedPaths();
|
||||||
|
if (paths.isEmpty()
|
||||||
|
|| QMessageBox::warning(
|
||||||
|
this, tr("Delete Selected Files"),
|
||||||
|
tr("Permanently delete %1 selected item(s)?\n\nThis cannot be undone.")
|
||||||
|
.arg(paths.size()),
|
||||||
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes)
|
||||||
|
return;
|
||||||
|
int failed = 0;
|
||||||
|
for (const QString &path : paths) {
|
||||||
|
const QFileInfo info(path);
|
||||||
|
const bool removed = info.isDir() ? QDir().rmdir(path) : QFile::remove(path);
|
||||||
|
if (!removed)
|
||||||
|
++failed;
|
||||||
|
}
|
||||||
|
if (failed)
|
||||||
|
QMessageBox::warning(this, tr("Delete Selected Files"),
|
||||||
|
tr("Failed to delete %1 item(s).").arg(failed));
|
||||||
|
refreshSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void renameSelected()
|
||||||
|
{
|
||||||
|
const auto record = currentRecord();
|
||||||
|
if (!record)
|
||||||
|
return;
|
||||||
|
bool accepted = false;
|
||||||
|
const QString newName = QInputDialog::getText(
|
||||||
|
this, tr("Rename"), tr("New filename:"), QLineEdit::Normal,
|
||||||
|
record->name, &accepted);
|
||||||
|
if (!accepted || newName.isEmpty() || newName == record->name)
|
||||||
|
return;
|
||||||
|
const QString newPath = QDir(record->folder).filePath(newName);
|
||||||
|
if (!QFile::rename(record->path, newPath))
|
||||||
|
QMessageBox::critical(this, tr("Rename"), tr("Failed to rename this item."));
|
||||||
|
else
|
||||||
|
refreshSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void showProperties()
|
||||||
|
{
|
||||||
|
const auto record = currentRecord();
|
||||||
|
if (!record)
|
||||||
|
return;
|
||||||
|
QDialog dialog(this);
|
||||||
|
dialog.setWindowTitle(tr("Properties"));
|
||||||
|
dialog.resize(780, 540);
|
||||||
|
auto *layout = new QVBoxLayout(&dialog);
|
||||||
|
auto *form = new QFormLayout;
|
||||||
|
const auto addValue = [form](const QString &label, const QString &value) {
|
||||||
|
auto *edit = new QLineEdit(value);
|
||||||
|
edit->setReadOnly(true);
|
||||||
|
edit->setCursorPosition(0);
|
||||||
|
form->addRow(label, edit);
|
||||||
|
};
|
||||||
|
const auto date = [this](qint64 timestamp) {
|
||||||
|
if (!timestamp)
|
||||||
|
return QString{};
|
||||||
|
QDateTime value = QDateTime::fromSecsSinceEpoch(timestamp);
|
||||||
|
if (showGmt_)
|
||||||
|
value = value.toUTC();
|
||||||
|
return value.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
};
|
||||||
|
addValue(tr("Filename:"), record->name);
|
||||||
|
addValue(tr("Folder:"), record->folder);
|
||||||
|
addValue(tr("Full Path:"), record->path);
|
||||||
|
addValue(tr("Size:"), QString::number(record->size));
|
||||||
|
addValue(tr("Size On Disk:"), QString::number(record->sizeOnDisk));
|
||||||
|
addValue(tr("Modified Time:"), date(record->modified));
|
||||||
|
addValue(tr("Created Time:"), date(record->created));
|
||||||
|
addValue(tr("Last Accessed Time:"), date(record->accessed));
|
||||||
|
addValue(tr("Entry Modified Time:"), date(record->changed));
|
||||||
|
addValue(tr("Attributes:"), record->attributes);
|
||||||
|
addValue(tr("Extension:"), QFileInfo(record->name).suffix());
|
||||||
|
addValue(tr("Duplicate Number:"), record->group ? QString::number(record->group) : QString{});
|
||||||
|
addValue(tr("Duplicate Group:"),
|
||||||
|
record->duplicateCopy ? QString::number(record->duplicateCopy) : QString{});
|
||||||
|
addValue(tr("File Owner:"), record->owner);
|
||||||
|
layout->addLayout(form);
|
||||||
|
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||||
|
connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
|
||||||
|
layout->addWidget(buttons);
|
||||||
|
dialog.exec();
|
||||||
|
}
|
||||||
|
|
||||||
void copyPaths()
|
void copyPaths()
|
||||||
{
|
{
|
||||||
QApplication::clipboard()->setText(selectedPaths().join(u'\n'));
|
QApplication::clipboard()->setText(selectedPaths().join(u'\n'));
|
||||||
@@ -1281,11 +1639,45 @@ private slots:
|
|||||||
void contextMenu(const QPoint &point)
|
void contextMenu(const QPoint &point)
|
||||||
{
|
{
|
||||||
QMenu menu(this);
|
QMenu menu(this);
|
||||||
menu.addAction(tr("Open"), this, &MainWindow::openSelected);
|
menu.addAction(tr("Open Selected File"), QKeySequence(Qt::Key_F9), this, &MainWindow::openSelected);
|
||||||
menu.addAction(tr("Open containing folder"), this, &MainWindow::openFolder);
|
menu.addAction(tr("Open Folder"), QKeySequence(Qt::Key_F8), this, &MainWindow::openFolder);
|
||||||
|
menu.addAction(tr("Open With…"), QKeySequence(Qt::Key_F7), this, &MainWindow::openWith);
|
||||||
|
menu.addAction(tr("Select File In File Manager"), this, &MainWindow::selectInFileManager);
|
||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
menu.addAction(tr("Copy paths"), this, &MainWindow::copyPaths);
|
menu.addAction(tr("Explorer Copy"), QKeySequence(Qt::CTRL | Qt::Key_E),
|
||||||
menu.addAction(tr("Export selected…"), this, &MainWindow::exportResults);
|
this, &MainWindow::explorerCopy);
|
||||||
|
menu.addAction(tr("Explorer Cut"), QKeySequence::Cut, this, &MainWindow::explorerCut);
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.addAction(tr("Move To Trash"), QKeySequence(Qt::Key_Delete),
|
||||||
|
this, &MainWindow::moveToTrash);
|
||||||
|
menu.addAction(tr("Delete Selected Files"), QKeySequence(Qt::SHIFT | Qt::Key_Delete),
|
||||||
|
this, &MainWindow::deleteSelected);
|
||||||
|
menu.addAction(tr("Rename"), QKeySequence(Qt::CTRL | Qt::Key_R),
|
||||||
|
this, &MainWindow::renameSelected);
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.addAction(tr("Save Files Information"), QKeySequence::Save,
|
||||||
|
this, &MainWindow::exportResults);
|
||||||
|
menu.addAction(tr("Copy Files Information"), QKeySequence::Copy,
|
||||||
|
this, &MainWindow::copyInformation);
|
||||||
|
menu.addSeparator();
|
||||||
|
auto *columns = menu.addMenu(tr("Choose Columns"));
|
||||||
|
for (int column = 0; column < ResultsModel::ColumnCount; ++column) {
|
||||||
|
auto *action = columns->addAction(
|
||||||
|
model_->headerData(column, Qt::Horizontal, Qt::DisplayRole).toString());
|
||||||
|
action->setCheckable(true);
|
||||||
|
action->setChecked(!table_->isColumnHidden(column));
|
||||||
|
connect(action, &QAction::toggled, this, [this, column](bool visible) {
|
||||||
|
table_->setColumnHidden(column, !visible);
|
||||||
|
saveUiSettings();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
menu.addAction(tr("Auto Size Columns"), QKeySequence(Qt::CTRL | Qt::Key_Plus), this, [this] {
|
||||||
|
table_->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
|
||||||
|
table_->resizeColumnsToContents();
|
||||||
|
});
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.addAction(tr("Properties"), QKeySequence(Qt::ALT | Qt::Key_Return),
|
||||||
|
this, &MainWindow::showProperties);
|
||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
menu.addAction(tr("Refresh"), QKeySequence(Qt::Key_F5), this, &MainWindow::refreshSearch);
|
menu.addAction(tr("Refresh"), QKeySequence(Qt::Key_F5), this, &MainWindow::refreshSearch);
|
||||||
menu.exec(table_->viewport()->mapToGlobal(point));
|
menu.exec(table_->viewport()->mapToGlobal(point));
|
||||||
@@ -1317,13 +1709,21 @@ private slots:
|
|||||||
};
|
};
|
||||||
const auto values = [](const FileRecord &r) {
|
const auto values = [](const FileRecord &r) {
|
||||||
const auto date = [](qint64 t) { return t ? QDateTime::fromSecsSinceEpoch(t).toString(Qt::ISODate) : QString{}; };
|
const auto date = [](qint64 t) { return t ? QDateTime::fromSecsSinceEpoch(t).toString(Qt::ISODate) : QString{}; };
|
||||||
return QStringList{r.name, r.folder, QString::number(r.size), QString::number(r.sizeOnDisk), date(r.modified),
|
return QStringList{
|
||||||
date(r.accessed), date(r.changed), r.type, r.owner, r.attributes,
|
r.name, r.folder, QString::number(r.size), QString::number(r.sizeOnDisk),
|
||||||
r.group ? QString::number(r.group) : QString{}, r.path};
|
date(r.modified), date(r.created), date(r.accessed), date(r.changed),
|
||||||
|
r.attributes, QFileInfo(r.name).suffix(),
|
||||||
|
r.group ? QString::number(r.group) : QString{},
|
||||||
|
r.duplicateCopy ? QString::number(r.duplicateCopy) : QString{},
|
||||||
|
r.type, r.owner, r.path
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const QStringList headers{
|
||||||
|
tr("Filename"), tr("Folder"), tr("Size"), tr("Size On Disk"), tr("Modified Time"),
|
||||||
|
tr("Created Time"), tr("Last Accessed Time"), tr("Entry Modified Time"),
|
||||||
|
tr("Attributes"), tr("Extension"), tr("Duplicate Number"), tr("Duplicate Group"),
|
||||||
|
tr("Type"), tr("File Owner"), tr("Full Path")
|
||||||
};
|
};
|
||||||
const QStringList headers{tr("Name"), tr("Folder"), tr("Size"), tr("Size On Disk"), tr("Modified Time"),
|
|
||||||
tr("Accessed Time"), tr("Changed Time"), tr("Type"),
|
|
||||||
tr("Owner"), tr("Attributes"), tr("Duplicate Number"), tr("Full Path")};
|
|
||||||
QTextStream out(&file);
|
QTextStream out(&file);
|
||||||
const QString suffix = QFileInfo(path).suffix().toLower();
|
const QString suffix = QFileInfo(path).suffix().toLower();
|
||||||
if (suffix == QStringLiteral("json")) {
|
if (suffix == QStringLiteral("json")) {
|
||||||
@@ -1362,7 +1762,8 @@ private slots:
|
|||||||
out << "</SearchResults>";
|
out << "</SearchResults>";
|
||||||
} else {
|
} else {
|
||||||
const QChar separator = suffix == QStringLiteral("csv") ? u',' : u'\t';
|
const QChar separator = suffix == QStringLiteral("csv") ? u',' : u'\t';
|
||||||
out << headers.join(separator) << u'\n';
|
if (addExportHeader_)
|
||||||
|
out << headers.join(separator) << u'\n';
|
||||||
for (const auto &record : rows) {
|
for (const auto &record : rows) {
|
||||||
QStringList row = values(record);
|
QStringList row = values(record);
|
||||||
if (separator == u',')
|
if (separator == u',')
|
||||||
@@ -1374,6 +1775,77 @@ private slots:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::optional<FileRecord> currentRecord() const
|
||||||
|
{
|
||||||
|
const QModelIndex current = table_->currentIndex();
|
||||||
|
if (!current.isValid())
|
||||||
|
return std::nullopt;
|
||||||
|
const int row = proxy_->mapToSource(current).row();
|
||||||
|
if (row < 0 || row >= model_->rows().size())
|
||||||
|
return std::nullopt;
|
||||||
|
return model_->rows().at(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList actionChoices() const
|
||||||
|
{
|
||||||
|
return {tr("No Action"), tr("Open Properties Window"), tr("Open Selected File"),
|
||||||
|
tr("Open With…"), tr("Open Folder"), tr("Select File In File Manager")};
|
||||||
|
}
|
||||||
|
|
||||||
|
void executeConfiguredAction(const QString &action)
|
||||||
|
{
|
||||||
|
if (action == tr("Open Properties Window"))
|
||||||
|
showProperties();
|
||||||
|
else if (action == tr("Open Selected File"))
|
||||||
|
openSelected();
|
||||||
|
else if (action == tr("Open With…"))
|
||||||
|
openWith();
|
||||||
|
else if (action == tr("Open Folder"))
|
||||||
|
openFolder();
|
||||||
|
else if (action == tr("Select File In File Manager"))
|
||||||
|
selectInFileManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString sizeUnitName() const
|
||||||
|
{
|
||||||
|
static const QStringList units{tr("Automatic"), tr("Bytes"), tr("KB"), tr("MB"), tr("GB")};
|
||||||
|
return units.value(sizeUnit_, units.front());
|
||||||
|
}
|
||||||
|
|
||||||
|
int sizeUnitFromName(const QString &name) const
|
||||||
|
{
|
||||||
|
const QStringList units{tr("Automatic"), tr("Bytes"), tr("KB"), tr("MB"), tr("GB")};
|
||||||
|
return std::max(0, int(units.indexOf(name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyDisplayOptions()
|
||||||
|
{
|
||||||
|
const int unit = options_.mode == QStringLiteral("Summary")
|
||||||
|
? sizeUnitFromName(summarySizeUnitName_) : sizeUnit_;
|
||||||
|
model_->setDisplayOptions(unit, showGmt_, markDuplicates_, duplicateColorSet_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveUiSettings()
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
settings.setValue(QStringLiteral("ui/sizeUnit"), sizeUnit_);
|
||||||
|
settings.setValue(QStringLiteral("ui/summarySizeUnit"), summarySizeUnitName_);
|
||||||
|
settings.setValue(QStringLiteral("ui/showGmt"), showGmt_);
|
||||||
|
settings.setValue(QStringLiteral("ui/markDuplicates"), markDuplicates_);
|
||||||
|
settings.setValue(QStringLiteral("ui/duplicateColorSet"), duplicateColorSet_);
|
||||||
|
settings.setValue(QStringLiteral("ui/doubleClickAction"), doubleClickAction_);
|
||||||
|
settings.setValue(QStringLiteral("ui/enterKeyAction"), enterKeyAction_);
|
||||||
|
settings.setValue(QStringLiteral("ui/addExportHeader"), addExportHeader_);
|
||||||
|
settings.setValue(QStringLiteral("ui/focusOnSearchStart"), focusOnSearchStart_);
|
||||||
|
settings.setValue(QStringLiteral("ui/focusOnSearchEnd"), focusOnSearchEnd_);
|
||||||
|
settings.setValue(QStringLiteral("ui/autoSizeOnSearchEnd"), autoSizeOnSearchEnd_);
|
||||||
|
settings.setValue(QStringLiteral("ui/showGrid"), showGrid_);
|
||||||
|
settings.setValue(QStringLiteral("ui/showTooltips"), showTooltips_);
|
||||||
|
settings.setValue(QStringLiteral("ui/windowGeometry"), saveGeometry());
|
||||||
|
if (table_)
|
||||||
|
settings.setValue(QStringLiteral("ui/headerState"), table_->horizontalHeader()->saveState());
|
||||||
|
}
|
||||||
|
|
||||||
QStringList selectedPaths() const
|
QStringList selectedPaths() const
|
||||||
{
|
{
|
||||||
QStringList paths;
|
QStringList paths;
|
||||||
@@ -1414,6 +1886,8 @@ private:
|
|||||||
settings.setValue(QStringLiteral("mode"), options_.mode);
|
settings.setValue(QStringLiteral("mode"), options_.mode);
|
||||||
settings.setValue(QStringLiteral("duplicateNameMode"), options_.duplicateNameMode);
|
settings.setValue(QStringLiteral("duplicateNameMode"), options_.duplicateNameMode);
|
||||||
settings.setValue(QStringLiteral("duplicateNameWithoutExtension"), options_.duplicateNameWithoutExtension);
|
settings.setValue(QStringLiteral("duplicateNameWithoutExtension"), options_.duplicateNameWithoutExtension);
|
||||||
|
settings.setValue(QStringLiteral("showOnlyDuplicateFiles"), options_.showOnlyDuplicateFiles);
|
||||||
|
settings.setValue(QStringLiteral("includeSubfoldersInSummary"), options_.includeSubfoldersInSummary);
|
||||||
settings.setValue(QStringLiteral("hidden"), options_.hidden);
|
settings.setValue(QStringLiteral("hidden"), options_.hidden);
|
||||||
settings.setValue(QStringLiteral("readonly"), options_.readonly);
|
settings.setValue(QStringLiteral("readonly"), options_.readonly);
|
||||||
settings.setValue(QStringLiteral("executable"), options_.executable);
|
settings.setValue(QStringLiteral("executable"), options_.executable);
|
||||||
@@ -1448,9 +1922,26 @@ private:
|
|||||||
options_.mode = s.value(QStringLiteral("mode"), options_.mode).toString();
|
options_.mode = s.value(QStringLiteral("mode"), options_.mode).toString();
|
||||||
options_.duplicateNameMode = s.value(QStringLiteral("duplicateNameMode"), options_.duplicateNameMode).toString();
|
options_.duplicateNameMode = s.value(QStringLiteral("duplicateNameMode"), options_.duplicateNameMode).toString();
|
||||||
options_.duplicateNameWithoutExtension = s.value(QStringLiteral("duplicateNameWithoutExtension"), false).toBool();
|
options_.duplicateNameWithoutExtension = s.value(QStringLiteral("duplicateNameWithoutExtension"), false).toBool();
|
||||||
|
options_.showOnlyDuplicateFiles = s.value(QStringLiteral("showOnlyDuplicateFiles"), true).toBool();
|
||||||
|
options_.includeSubfoldersInSummary = s.value(QStringLiteral("includeSubfoldersInSummary"), false).toBool();
|
||||||
options_.hidden = s.value(QStringLiteral("hidden"), options_.hidden).toString();
|
options_.hidden = s.value(QStringLiteral("hidden"), options_.hidden).toString();
|
||||||
options_.readonly = s.value(QStringLiteral("readonly"), options_.readonly).toString();
|
options_.readonly = s.value(QStringLiteral("readonly"), options_.readonly).toString();
|
||||||
options_.executable = s.value(QStringLiteral("executable"), options_.executable).toString();
|
options_.executable = s.value(QStringLiteral("executable"), options_.executable).toString();
|
||||||
|
sizeUnit_ = s.value(QStringLiteral("ui/sizeUnit"), 0).toInt();
|
||||||
|
summarySizeUnitName_ = s.value(QStringLiteral("ui/summarySizeUnit"), tr("Automatic")).toString();
|
||||||
|
showGmt_ = s.value(QStringLiteral("ui/showGmt"), false).toBool();
|
||||||
|
markDuplicates_ = s.value(QStringLiteral("ui/markDuplicates"), true).toBool();
|
||||||
|
duplicateColorSet_ = s.value(QStringLiteral("ui/duplicateColorSet"), 1).toInt();
|
||||||
|
doubleClickAction_ = s.value(
|
||||||
|
QStringLiteral("ui/doubleClickAction"), tr("Open Properties Window")).toString();
|
||||||
|
enterKeyAction_ = s.value(
|
||||||
|
QStringLiteral("ui/enterKeyAction"), tr("Open Selected File")).toString();
|
||||||
|
addExportHeader_ = s.value(QStringLiteral("ui/addExportHeader"), true).toBool();
|
||||||
|
focusOnSearchStart_ = s.value(QStringLiteral("ui/focusOnSearchStart"), true).toBool();
|
||||||
|
focusOnSearchEnd_ = s.value(QStringLiteral("ui/focusOnSearchEnd"), true).toBool();
|
||||||
|
autoSizeOnSearchEnd_ = s.value(QStringLiteral("ui/autoSizeOnSearchEnd"), false).toBool();
|
||||||
|
showGrid_ = s.value(QStringLiteral("ui/showGrid"), true).toBool();
|
||||||
|
showTooltips_ = s.value(QStringLiteral("ui/showTooltips"), true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchOptions options_;
|
SearchOptions options_;
|
||||||
@@ -1464,6 +1955,19 @@ private:
|
|||||||
QThread workerThread_;
|
QThread workerThread_;
|
||||||
SearchEngine *engine_{};
|
SearchEngine *engine_{};
|
||||||
QString findQuery_;
|
QString findQuery_;
|
||||||
|
QString summarySizeUnitName_ = QStringLiteral("Automatic");
|
||||||
|
QString doubleClickAction_ = QStringLiteral("Open Properties Window");
|
||||||
|
QString enterKeyAction_ = QStringLiteral("Open Selected File");
|
||||||
|
int sizeUnit_ = 0;
|
||||||
|
int duplicateColorSet_ = 1;
|
||||||
|
bool showGmt_ = false;
|
||||||
|
bool markDuplicates_ = true;
|
||||||
|
bool addExportHeader_ = true;
|
||||||
|
bool focusOnSearchStart_ = true;
|
||||||
|
bool focusOnSearchEnd_ = true;
|
||||||
|
bool autoSizeOnSearchEnd_ = false;
|
||||||
|
bool showGrid_ = true;
|
||||||
|
bool showTooltips_ = true;
|
||||||
bool isSearching_ = false;
|
bool isSearching_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user