Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eabf420ac3 | |||
| 9f7908b275 | |||
| 010bd0f374 |
@@ -42,7 +42,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
set +e
|
||||
QT_QPA_PLATFORM=offscreen timeout 3s ./build/searchmyfiles
|
||||
QT_QPA_PLATFORM=offscreen timeout 3s ./build/folderscope
|
||||
status="$?"
|
||||
test "$status" = 0 || test "$status" = 124
|
||||
|
||||
|
||||
@@ -5,6 +5,22 @@ All notable changes to FolderScope are documented here.
|
||||
The project uses [Semantic Versioning](https://semver.org/). Release tags are
|
||||
formatted as `vMAJOR.MINOR.PATCH`.
|
||||
|
||||
## [0.3.0] - 2026-07-26
|
||||
|
||||
### Added
|
||||
|
||||
- Added persistent input history for folder and wildcard fields in Search
|
||||
Options.
|
||||
|
||||
### Changed
|
||||
|
||||
- Renamed the executable, desktop entry, icon, and application identity from
|
||||
SearchMyFiles to FolderScope.
|
||||
- Preserved existing settings and search profiles when migrating to the new
|
||||
application identity.
|
||||
- Kept the base-folder browser button compact so the input field uses the
|
||||
available width.
|
||||
|
||||
## [0.2.5] - 2026-07-26
|
||||
|
||||
### Added
|
||||
@@ -82,3 +98,4 @@ formatted as `vMAJOR.MINOR.PATCH`.
|
||||
[0.2.3]: https://gitea.forust.xyz/forust/folderscope/releases/tag/v0.2.3
|
||||
[0.2.4]: https://gitea.forust.xyz/forust/folderscope/releases/tag/v0.2.4
|
||||
[0.2.5]: https://gitea.forust.xyz/forust/folderscope/releases/tag/v0.2.5
|
||||
[0.3.0]: https://gitea.forust.xyz/forust/folderscope/releases/tag/v0.3.0
|
||||
|
||||
+7
-7
@@ -22,7 +22,7 @@ target_link_libraries(folderscope_core PUBLIC Qt6::Core)
|
||||
target_compile_options(folderscope_core PRIVATE -Wall -Wextra -Wpedantic)
|
||||
target_include_directories(folderscope_core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
|
||||
qt_add_executable(searchmyfiles
|
||||
qt_add_executable(folderscope
|
||||
src/long_long_spin_box.cpp
|
||||
src/long_long_spin_box.h
|
||||
src/main.cpp
|
||||
@@ -38,14 +38,14 @@ qt_add_executable(searchmyfiles
|
||||
src/options_dialog.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(searchmyfiles PRIVATE folderscope_core Qt6::Widgets Qt6::Concurrent Qt6::DBus)
|
||||
target_compile_options(searchmyfiles PRIVATE -Wall -Wextra -Wpedantic)
|
||||
target_compile_definitions(searchmyfiles PRIVATE FOLDERSCOPE_VERSION="${PROJECT_VERSION}")
|
||||
target_link_libraries(folderscope PRIVATE folderscope_core Qt6::Widgets Qt6::Concurrent Qt6::DBus)
|
||||
target_compile_options(folderscope PRIVATE -Wall -Wextra -Wpedantic)
|
||||
target_compile_definitions(folderscope PRIVATE FOLDERSCOPE_VERSION="${PROJECT_VERSION}")
|
||||
|
||||
install(TARGETS searchmyfiles RUNTIME DESTINATION bin)
|
||||
install(FILES packaging/searchmyfiles.desktop
|
||||
install(TARGETS folderscope RUNTIME DESTINATION bin)
|
||||
install(FILES packaging/folderscope.desktop
|
||||
DESTINATION share/applications)
|
||||
install(FILES packaging/searchmyfiles.svg
|
||||
install(FILES packaging/folderscope.svg
|
||||
DESTINATION share/icons/hicolor/scalable/apps)
|
||||
|
||||
include(CTest)
|
||||
|
||||
@@ -34,7 +34,7 @@ Requirements:
|
||||
```bash
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build -j
|
||||
./build/searchmyfiles
|
||||
./build/folderscope
|
||||
```
|
||||
|
||||
Run the automated tests:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=SearchMyFiles
|
||||
Name=FolderScope
|
||||
Comment=Fast advanced file search
|
||||
Exec=searchmyfiles
|
||||
Icon=searchmyfiles
|
||||
Exec=folderscope
|
||||
Icon=folderscope
|
||||
Terminal=false
|
||||
Categories=Utility;FileTools;
|
||||
|
Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 324 B |
+25
-2
@@ -1,15 +1,38 @@
|
||||
#include <QApplication>
|
||||
#include <QMetaType>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
|
||||
#include "main_window.h"
|
||||
#include "search_core.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void migrateLegacySettings()
|
||||
{
|
||||
QSettings settings;
|
||||
const QString migrationKey =
|
||||
QStringLiteral("migration/searchMyFilesSettingsImported");
|
||||
if (settings.value(migrationKey, false).toBool())
|
||||
return;
|
||||
|
||||
QSettings legacy(QStringLiteral("SearchMyFilesLinux"),
|
||||
QStringLiteral("SearchMyFiles"));
|
||||
for (const QString &key : legacy.allKeys()) {
|
||||
if (!settings.contains(key))
|
||||
settings.setValue(key, legacy.value(key));
|
||||
}
|
||||
settings.setValue(migrationKey, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
QCoreApplication::setOrganizationName(QStringLiteral("SearchMyFilesLinux"));
|
||||
QCoreApplication::setApplicationName(QStringLiteral("SearchMyFiles"));
|
||||
QCoreApplication::setOrganizationName(QStringLiteral("FolderScope"));
|
||||
QCoreApplication::setApplicationName(QStringLiteral("FolderScope"));
|
||||
migrateLegacySettings();
|
||||
QApplication::setStyle(QStringLiteral("Fusion"));
|
||||
qRegisterMetaType<SearchOptions>();
|
||||
qRegisterMetaType<QVector<FileRecord>>();
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@
|
||||
MainWindow::MainWindow()
|
||||
{
|
||||
loadOptions();
|
||||
setWindowTitle(tr("SearchMyFiles for Linux"));
|
||||
setWindowTitle(tr("FolderScope"));
|
||||
resize(1280, 760);
|
||||
model_ = new ResultsModel(this);
|
||||
model_->setDisplayOptions(sizeUnit_, showGmt_, markDuplicates_, duplicateColorSet_);
|
||||
@@ -507,7 +507,7 @@ void MainWindow::startCurrentSearch()
|
||||
progress_->show();
|
||||
progress_->setRange(0, 0);
|
||||
progress_->setFormat(tr("Scanning…"));
|
||||
setWindowTitle(tr("%1 — %2 — SearchMyFiles for Linux")
|
||||
setWindowTitle(tr("%1 — %2 — FolderScope")
|
||||
.arg(options_.roots, options_.mode));
|
||||
if (focusOnSearchStart_)
|
||||
table_->setFocus();
|
||||
|
||||
+75
-30
@@ -8,6 +8,7 @@
|
||||
#include <QMessageBox>
|
||||
#include <QMimeData>
|
||||
#include <QSettings>
|
||||
#include <QSizePolicy>
|
||||
|
||||
#include "long_long_spin_box.h"
|
||||
|
||||
@@ -87,22 +88,29 @@ void OptionsDialog::setupUi(const SearchOptions &o)
|
||||
|
||||
auto *filesPage = new QWidget;
|
||||
auto *filesForm = new QFormLayout(filesPage);
|
||||
roots_ = new QLineEdit(o.roots);
|
||||
roots_->setAcceptDrops(true);
|
||||
roots_ = historyCombo(QStringLiteral("roots"), o.roots);
|
||||
roots_->lineEdit()->setAcceptDrops(true);
|
||||
roots_->setToolTip(tr("Drop folders here from your file manager"));
|
||||
roots_->installEventFilter(this);
|
||||
roots_->lineEdit()->installEventFilter(this);
|
||||
auto *rootRow = new QWidget;
|
||||
auto *rootLayout = new QHBoxLayout(rootRow);
|
||||
rootLayout->setContentsMargins(0, 0, 0, 0);
|
||||
rootLayout->addWidget(roots_);
|
||||
auto *browse = new QPushButton(tr("Browse…"));
|
||||
browse->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
rootLayout->addWidget(browse);
|
||||
rootLayout->setStretch(0, 1);
|
||||
filesForm->addRow(tr("Base folders (separate with ;):"), rootRow);
|
||||
fileMasks_ = addLine(filesForm, tr("Files wildcard:"), o.fileWildcards);
|
||||
subfolderMasks_ = addLine(filesForm, tr("Subfolders wildcard:"), o.subfolderWildcards);
|
||||
excludeFiles_ = addLine(filesForm, tr("Exclude files:"), o.excludeFiles);
|
||||
excludeFolders_ = addLine(filesForm, tr("Exclude folders:"), o.excludeFolders);
|
||||
includeFolders_ = addLine(filesForm, tr("Include only folders:"), o.includeFolders);
|
||||
fileMasks_ = addHistoryCombo(filesForm, tr("Files wildcard:"),
|
||||
QStringLiteral("fileWildcards"), o.fileWildcards);
|
||||
subfolderMasks_ = addHistoryCombo(filesForm, tr("Subfolders wildcard:"),
|
||||
QStringLiteral("subfolderWildcards"), o.subfolderWildcards);
|
||||
excludeFiles_ = addHistoryCombo(filesForm, tr("Exclude files:"),
|
||||
QStringLiteral("excludeFiles"), o.excludeFiles);
|
||||
excludeFolders_ = addHistoryCombo(filesForm, tr("Exclude folders:"),
|
||||
QStringLiteral("excludeFolders"), o.excludeFolders);
|
||||
includeFolders_ = addHistoryCombo(filesForm, tr("Include only folders:"),
|
||||
QStringLiteral("includeFolders"), o.includeFolders);
|
||||
recursive_ = new QCheckBox(tr("Search subfolders")); recursive_->setChecked(o.recursive);
|
||||
findFiles_ = new QCheckBox(tr("Find files")); findFiles_->setChecked(o.findFiles);
|
||||
findFolders_ = new QCheckBox(tr("Find folders")); findFolders_->setChecked(o.findFolders);
|
||||
@@ -183,7 +191,7 @@ void OptionsDialog::setupUi(const SearchOptions &o)
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &OptionsDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
connect(browse, &QPushButton::clicked, this, [this] {
|
||||
const QStringList existingFolders = patterns(roots_->text());
|
||||
const QStringList existingFolders = patterns(roots_->currentText());
|
||||
const QString startFolder = existingFolders.isEmpty()
|
||||
? QDir::homePath() : existingFolders.constLast();
|
||||
const QString folder = QFileDialog::getExistingDirectory(
|
||||
@@ -195,7 +203,7 @@ void OptionsDialog::setupUi(const SearchOptions &o)
|
||||
const QString cleanPath = QDir::cleanPath(folder);
|
||||
if (!folders.contains(cleanPath))
|
||||
folders.push_back(cleanPath);
|
||||
roots_->setText(folders.join(u';'));
|
||||
roots_->setEditText(folders.join(u';'));
|
||||
});
|
||||
layout->addWidget(buttons);
|
||||
}
|
||||
@@ -203,12 +211,12 @@ void OptionsDialog::setupUi(const SearchOptions &o)
|
||||
SearchOptions OptionsDialog::options() const
|
||||
{
|
||||
SearchOptions o;
|
||||
o.roots = roots_->text();
|
||||
o.fileWildcards = fileMasks_->text();
|
||||
o.subfolderWildcards = subfolderMasks_->text();
|
||||
o.excludeFiles = excludeFiles_->text();
|
||||
o.excludeFolders = excludeFolders_->text();
|
||||
o.includeFolders = includeFolders_->text();
|
||||
o.roots = roots_->currentText();
|
||||
o.fileWildcards = fileMasks_->currentText();
|
||||
o.subfolderWildcards = subfolderMasks_->currentText();
|
||||
o.excludeFiles = excludeFiles_->currentText();
|
||||
o.excludeFolders = excludeFolders_->currentText();
|
||||
o.includeFolders = includeFolders_->currentText();
|
||||
o.contains = contains_->toPlainText();
|
||||
o.binary = binary_->isChecked();
|
||||
o.multipleValues = multiple_->isChecked();
|
||||
@@ -244,14 +252,35 @@ void OptionsDialog::accept()
|
||||
if (!validateDate(minTime_, tr("From time"))
|
||||
|| !validateDate(maxTime_, tr("To time")))
|
||||
return;
|
||||
rememberHistory(QStringLiteral("roots"), roots_->currentText());
|
||||
rememberHistory(QStringLiteral("fileWildcards"), fileMasks_->currentText());
|
||||
rememberHistory(QStringLiteral("subfolderWildcards"), subfolderMasks_->currentText());
|
||||
rememberHistory(QStringLiteral("excludeFiles"), excludeFiles_->currentText());
|
||||
rememberHistory(QStringLiteral("excludeFolders"), excludeFolders_->currentText());
|
||||
rememberHistory(QStringLiteral("includeFolders"), includeFolders_->currentText());
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
QLineEdit *OptionsDialog::addLine(QFormLayout *layout, const QString &label, const QString &value)
|
||||
QComboBox *OptionsDialog::historyCombo(const QString &key, const QString &value)
|
||||
{
|
||||
auto *edit = new QLineEdit(value);
|
||||
layout->addRow(label, edit);
|
||||
return edit;
|
||||
auto *combo = new QComboBox;
|
||||
combo->setEditable(true);
|
||||
combo->setInsertPolicy(QComboBox::NoInsert);
|
||||
combo->setObjectName(QStringLiteral("history%1").arg(key));
|
||||
|
||||
QSettings settings;
|
||||
const QStringList history = settings.value(QStringLiteral("inputHistory/%1").arg(key)).toStringList();
|
||||
combo->addItems(history);
|
||||
combo->setEditText(value);
|
||||
return combo;
|
||||
}
|
||||
|
||||
QComboBox *OptionsDialog::addHistoryCombo(QFormLayout *layout, const QString &label,
|
||||
const QString &key, const QString &value)
|
||||
{
|
||||
auto *combo = historyCombo(key, value);
|
||||
layout->addRow(label, combo);
|
||||
return combo;
|
||||
}
|
||||
|
||||
LongLongSpinBox *OptionsDialog::sizeSpin(qint64 value)
|
||||
@@ -284,6 +313,22 @@ bool OptionsDialog::validateDate(QLineEdit *edit, const QString &label)
|
||||
return false;
|
||||
}
|
||||
|
||||
void OptionsDialog::rememberHistory(const QString &key, const QString &value)
|
||||
{
|
||||
if (value.isEmpty())
|
||||
return;
|
||||
|
||||
QSettings settings;
|
||||
const QString settingsKey = QStringLiteral("inputHistory/%1").arg(key);
|
||||
QStringList history = settings.value(settingsKey).toStringList();
|
||||
history.removeAll(value);
|
||||
history.prepend(value);
|
||||
constexpr qsizetype maxHistoryEntries = 20;
|
||||
if (history.size() > maxHistoryEntries)
|
||||
history.erase(history.begin() + maxHistoryEntries, history.end());
|
||||
settings.setValue(settingsKey, history);
|
||||
}
|
||||
|
||||
QComboBox *OptionsDialog::attrCombo(const QString &value)
|
||||
{
|
||||
auto *combo = new QComboBox;
|
||||
@@ -294,7 +339,7 @@ QComboBox *OptionsDialog::attrCombo(const QString &value)
|
||||
|
||||
bool OptionsDialog::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (obj == roots_ && event->type() == QEvent::DragEnter) {
|
||||
if (obj == roots_->lineEdit() && event->type() == QEvent::DragEnter) {
|
||||
auto *dragEnter = static_cast<QDragEnterEvent *>(event);
|
||||
if (dragEnter->mimeData()->hasUrls()) {
|
||||
dragEnter->acceptProposedAction();
|
||||
@@ -302,11 +347,11 @@ bool OptionsDialog::eventFilter(QObject *obj, QEvent *event)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (obj == roots_ && event->type() == QEvent::Drop) {
|
||||
if (obj == roots_->lineEdit() && event->type() == QEvent::Drop) {
|
||||
auto *drop = static_cast<QDropEvent *>(event);
|
||||
if (!drop->mimeData()->hasUrls())
|
||||
return false;
|
||||
QStringList folders = patterns(roots_->text());
|
||||
QStringList folders = patterns(roots_->currentText());
|
||||
for (const QUrl &url : drop->mimeData()->urls()) {
|
||||
if (!url.isLocalFile())
|
||||
continue;
|
||||
@@ -314,7 +359,7 @@ bool OptionsDialog::eventFilter(QObject *obj, QEvent *event)
|
||||
if (QFileInfo::exists(cleanPath) && !folders.contains(cleanPath))
|
||||
folders.append(cleanPath);
|
||||
}
|
||||
roots_->setText(folders.join(u';'));
|
||||
roots_->setEditText(folders.join(u';'));
|
||||
drop->acceptProposedAction();
|
||||
return true;
|
||||
}
|
||||
@@ -401,12 +446,12 @@ void OptionsDialog::loadProfile()
|
||||
auto loadInt = [&](const char *key, int def) { return settings.value(QString::fromLatin1(key), def).toInt(); };
|
||||
auto loadLong = [&](const char *key, qint64 def) { return settings.value(QString::fromLatin1(key), def).toLongLong(); };
|
||||
|
||||
roots_->setText(load("roots"));
|
||||
fileMasks_->setText(load("fileWildcards"));
|
||||
subfolderMasks_->setText(load("subfolderWildcards"));
|
||||
excludeFiles_->setText(load("excludeFiles"));
|
||||
excludeFolders_->setText(load("excludeFolders"));
|
||||
includeFolders_->setText(load("includeFolders"));
|
||||
roots_->setEditText(load("roots"));
|
||||
fileMasks_->setEditText(load("fileWildcards"));
|
||||
subfolderMasks_->setEditText(load("subfolderWildcards"));
|
||||
excludeFiles_->setEditText(load("excludeFiles"));
|
||||
excludeFolders_->setEditText(load("excludeFolders"));
|
||||
includeFolders_->setEditText(load("includeFolders"));
|
||||
contains_->setText(load("contains"));
|
||||
binary_->setChecked(loadBool("binary", false));
|
||||
multiple_->setChecked(loadBool("multipleValues", false));
|
||||
|
||||
+10
-7
@@ -29,10 +29,13 @@ protected:
|
||||
|
||||
private:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
static QLineEdit *addLine(QFormLayout *layout, const QString &label, const QString &value);
|
||||
static QComboBox *historyCombo(const QString &key, const QString &value);
|
||||
static QComboBox *addHistoryCombo(QFormLayout *layout, const QString &label,
|
||||
const QString &key, const QString &value);
|
||||
static LongLongSpinBox *sizeSpin(qint64 value);
|
||||
static qint64 dateValue(const QLineEdit *edit);
|
||||
bool validateDate(QLineEdit *edit, const QString &label);
|
||||
void rememberHistory(const QString &key, const QString &value);
|
||||
static QComboBox *attrCombo(const QString &value);
|
||||
|
||||
void setupUi(const SearchOptions &o);
|
||||
@@ -42,12 +45,12 @@ private:
|
||||
QComboBox *mode_{};
|
||||
QComboBox *duplicateMode_{};
|
||||
QCheckBox *duplicateWithoutExtension_{};
|
||||
QLineEdit *roots_{};
|
||||
QLineEdit *fileMasks_{};
|
||||
QLineEdit *subfolderMasks_{};
|
||||
QLineEdit *excludeFiles_{};
|
||||
QLineEdit *excludeFolders_{};
|
||||
QLineEdit *includeFolders_{};
|
||||
QComboBox *roots_{};
|
||||
QComboBox *fileMasks_{};
|
||||
QComboBox *subfolderMasks_{};
|
||||
QComboBox *excludeFiles_{};
|
||||
QComboBox *excludeFolders_{};
|
||||
QComboBox *includeFolders_{};
|
||||
QTextEdit *contains_{};
|
||||
QCheckBox *binary_{};
|
||||
QCheckBox *multiple_{};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "options_dialog.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QSettings>
|
||||
#include <QtTest>
|
||||
|
||||
class OptionsDialogTest final : public QObject {
|
||||
@@ -23,6 +25,35 @@ private slots:
|
||||
strict->setChecked(false);
|
||||
QVERIFY(!dialog.options().strictDuplicateComparison);
|
||||
}
|
||||
|
||||
void remembersInputHistory()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.remove(QStringLiteral("inputHistory"));
|
||||
|
||||
SearchOptions options;
|
||||
OptionsDialog first(options);
|
||||
auto *roots = first.findChild<QComboBox *>(QStringLiteral("historyroots"));
|
||||
QVERIFY(roots);
|
||||
roots->setEditText(QStringLiteral("/tmp/first;/tmp/second"));
|
||||
QVERIFY(QMetaObject::invokeMethod(&first, "accept"));
|
||||
|
||||
QCOMPARE(settings.value(QStringLiteral("inputHistory/roots")).toStringList(),
|
||||
QStringList{QStringLiteral("/tmp/first;/tmp/second")});
|
||||
|
||||
OptionsDialog second(options);
|
||||
auto *history = second.findChild<QComboBox *>(QStringLiteral("historyroots"));
|
||||
QVERIFY(history);
|
||||
QCOMPARE(history->itemText(0), QStringLiteral("/tmp/first;/tmp/second"));
|
||||
|
||||
history->setEditText(QStringLiteral("/tmp/new"));
|
||||
QVERIFY(QMetaObject::invokeMethod(&second, "accept"));
|
||||
QCOMPARE(settings.value(QStringLiteral("inputHistory/roots")).toStringList(),
|
||||
(QStringList{QStringLiteral("/tmp/new"),
|
||||
QStringLiteral("/tmp/first;/tmp/second")}));
|
||||
|
||||
settings.remove(QStringLiteral("inputHistory"));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(OptionsDialogTest)
|
||||
|
||||
Reference in New Issue
Block a user