diff options
author | Chris Xiong <chirs241097@gmail.com> | 2022-09-26 22:14:18 -0400 |
---|---|---|
committer | Chris Xiong <chirs241097@gmail.com> | 2022-09-26 22:17:44 -0400 |
commit | 2e7924a07d45c1d1468552f951dcd2f2dcc948fa (patch) | |
tree | 24b6788b505a82f70f7dd82f8d9f1570fc88fef1 | |
parent | 43a058c0957bec3fc564a7a019bdc03829e32e70 (diff) | |
download | deduper-2e7924a07d45c1d1468552f951dcd2f2dcc948fa.tar.xz |
Add a few basic settings items.
Yes I stole qmp's settings design and silently relicensed the
(largely identical) code. But who in the world cares, plus I'm the
author of qmp anyway.
-rw-r--r-- | qdeduper/CMakeLists.txt | 2 | ||||
-rw-r--r-- | qdeduper/imageitem.cpp | 12 | ||||
-rw-r--r-- | qdeduper/imageitem.hpp | 4 | ||||
-rw-r--r-- | qdeduper/mingui.cpp | 42 | ||||
-rw-r--r-- | qdeduper/mingui.hpp | 8 | ||||
-rw-r--r-- | qdeduper/preferencedialog.cpp | 170 | ||||
-rw-r--r-- | qdeduper/preferencedialog.hpp | 32 | ||||
-rw-r--r-- | qdeduper/settings.cpp | 129 | ||||
-rw-r--r-- | qdeduper/settings.hpp | 58 | ||||
-rw-r--r-- | qdeduper/sigdb_qt.cpp | 44 | ||||
-rw-r--r-- | qdeduper/sigdb_qt.hpp | 6 |
11 files changed, 479 insertions, 28 deletions
diff --git a/qdeduper/CMakeLists.txt b/qdeduper/CMakeLists.txt index 06e3048..7373331 100644 --- a/qdeduper/CMakeLists.txt +++ b/qdeduper/CMakeLists.txt @@ -19,6 +19,8 @@ add_executable(qdeduper sigdb_qt.cpp filescanner.cpp pathchooser.cpp + settings.cpp + preferencedialog.cpp resources.qrc ) diff --git a/qdeduper/imageitem.cpp b/qdeduper/imageitem.cpp index 7be667c..896334b 100644 --- a/qdeduper/imageitem.cpp +++ b/qdeduper/imageitem.cpp @@ -149,7 +149,6 @@ QSize ImageItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QMod fnt.setBold(true); QFontMetrics fm(fnt); int extra_height = 2 * MARGIN + 2 * BORDER + LINESP + fm.height() + 2 * HKPADD + HKSHDS; - int min_height = 64; int max_height = imh; QSize dim = index.data(ImageItem::ImageItemRoles::dimension_role).value<QSize>(); @@ -178,12 +177,21 @@ void ImageItemDelegate::resize() Q_EMIT sizeHintChanged(im->index(i, 0)); } -void ImageItemDelegate::setScrollbarMargins(int vw, int hh) +void ImageItemDelegate::set_scrollbar_margins(int vw, int hh) { this->vw = vw; this->hh = hh; } +void ImageItemDelegate::set_min_height(int mh) +{ + if (mh != this->min_height) + { + this->min_height = mh; + resize(); + } +} + void ImageItemDelegate::set_single_item_mode(bool enabled) { if (enabled == singlemode) return; diff --git a/qdeduper/imageitem.hpp b/qdeduper/imageitem.hpp index abef719..66e4644 100644 --- a/qdeduper/imageitem.hpp +++ b/qdeduper/imageitem.hpp @@ -40,6 +40,7 @@ private: const static int HKSHDS = 2; int vw = -1; int hh = -1; + int min_height = 64; bool singlemode = false; bool show_hotkey = true; QAbstractItemModel *im = nullptr; @@ -47,7 +48,8 @@ public: void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; void resize(); - void setScrollbarMargins(int vw, int hh); + void set_scrollbar_margins(int vw, int hh); + void set_min_height(int mh); void set_single_item_mode(bool enabled); bool is_single_item_mode(); diff --git a/qdeduper/mingui.cpp b/qdeduper/mingui.cpp index 5d12e7c..0c32d05 100644 --- a/qdeduper/mingui.cpp +++ b/qdeduper/mingui.cpp @@ -3,6 +3,8 @@ #include "filescanner.hpp" #include "pathchooser.hpp" #include "sigdb_qt.hpp" +#include "settings.hpp" +#include "preferencedialog.hpp" #include <chrono> #include <fstream> @@ -18,6 +20,7 @@ #include <QMouseEvent> #include <QScrollBar> #include <QPushButton> +#include <QStandardPaths> #include <QToolBar> #include <QToolButton> #include <QTimer> @@ -98,7 +101,7 @@ DeduperMainWindow::DeduperMainWindow() lv->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn); lv->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn); id = new ImageItemDelegate(); - id->setScrollbarMargins(lv->verticalScrollBar()->width(), + id->set_scrollbar_margins(lv->verticalScrollBar()->width(), lv->horizontalScrollBar()->height()); id->set_model(im); lv->setItemDelegate(id); @@ -130,6 +133,20 @@ DeduperMainWindow::DeduperMainWindow() infopanel->setFont(fnt); pd->setFont(fnt); + sr = new SettingsRegistry(QStandardPaths::writableLocation(QStandardPaths::StandardLocation::ConfigLocation) + QString("/qdeduperrc")); + int generalt = sr->register_tab("General"); + sr->register_int_option(generalt, "min_image_dim", "Minimal Dimension in Image View", 16, 4096, 64); + sr->register_int_option(generalt, "thread_count", "Number of Threads (0 = Automatic)", 0, 4096, 0); + sr->register_bool_option(generalt, "toolbar_text", "Show Text in Toolbar Buttons", true); + int sigt = sr->register_tab("Signature"); + sr->register_double_option(sigt, "signature/threshold", "Distance Threshold", 0, 1, 0.3); + prefdlg = new PreferenceDialog(sr, this); + prefdlg->setModal(true); + prefdlg->close(); + QObject::connect(menuact["preferences"], &QAction::triggered, prefdlg, &PreferenceDialog::open); + QObject::connect(prefdlg, &PreferenceDialog::accepted, this, &DeduperMainWindow::apply_prefs); + apply_prefs(); + for (size_t i = 0; i < keys.size(); ++i) { auto &k = keys[i]; @@ -189,6 +206,11 @@ DeduperMainWindow::DeduperMainWindow() sort_order = Qt::SortOrder::AscendingOrder; } +DeduperMainWindow::~DeduperMainWindow() +{ + delete sr; +} + void DeduperMainWindow::setup_menu() { QMenu *file = this->menuBar()->addMenu("&File"); @@ -268,7 +290,8 @@ void DeduperMainWindow::setup_menu() }); menuact["search_image"] = search_img; file->addSeparator(); - file->addAction("Preferences..."); + QAction *pref = file->addAction("Preferences..."); + menuact["preferences"] = pref; QAction *exita = file->addAction("Exit"); QObject::connect(exita, &QAction::triggered, [this] { if (this->quit_check()) qApp->quit(); @@ -680,7 +703,13 @@ void DeduperMainWindow::scan_dirs(std::vector<std::pair<fs::path, bool>> paths) this->pd->setLabelText("Finalizing..."); } }, Qt::ConnectionType::QueuedConnection); - this->sdb->scan_files(fs->file_list(), std::thread::hardware_concurrency()); + populate_cfg_t c = this->sdb->get_sig_config(); + int nthreads = this->sr->get_option_int("thread_count"); + if (!nthreads) nthreads = std::thread::hardware_concurrency(); + c.njobs = nthreads; + c.threshold = this->sr->get_option_double("signature/threshold"); + this->sdb->set_sig_config(c); + this->sdb->scan_files(fs->file_list()); delete fs; this->fsc = nullptr; }); @@ -730,6 +759,13 @@ void DeduperMainWindow::show_marked() this->permamsg->setText("Viewing marked images"); } +void DeduperMainWindow::apply_prefs() +{ + id->set_min_height(sr->get_option_int("min_image_dim")); + tb->setToolButtonStyle(sr->get_option_bool("toolbar_text") ? Qt::ToolButtonStyle::ToolButtonTextBesideIcon + : Qt::ToolButtonStyle::ToolButtonIconOnly); +} + void DeduperMainWindow::sort_reassign_hotkeys() { im->setSortRole(this->sort_role); diff --git a/qdeduper/mingui.hpp b/qdeduper/mingui.hpp index 3803eb7..5686c3e 100644 --- a/qdeduper/mingui.hpp +++ b/qdeduper/mingui.hpp @@ -25,6 +25,8 @@ class QStandardItemModel; class QToolBar; class FileScanner; class ImageItemDelegate; +class SettingsRegistry; +class PreferenceDialog; namespace fs = std::filesystem; @@ -44,7 +46,7 @@ private: QLabel *permamsg; QStatusBar *sb; QListView *lv; - QToolBar *tb; + QToolBar *tb = nullptr; std::map<std::string, QAction*> menuact; QList<QAction*> selhk; QStandardItemModel *im = nullptr; @@ -52,6 +54,8 @@ private: QProgressDialog *pd = nullptr; SignatureDB *sdb = nullptr; FileScanner *fsc = nullptr; + SettingsRegistry *sr = nullptr; + PreferenceDialog *prefdlg = nullptr; std::unordered_set<fs::path> marked; int sort_role; Qt::SortOrder sort_order; @@ -73,6 +77,7 @@ protected: bool eventFilter(QObject *obj, QEvent *ev) override; public: DeduperMainWindow(); + ~DeduperMainWindow(); void setup_menu(); void sort_reassign_hotkeys(); @@ -89,6 +94,7 @@ public Q_SLOTS: void update_actions(); void show_group(size_t gid); void show_marked(); + void apply_prefs(); Q_SIGNALS: void next(); void prev(); diff --git a/qdeduper/preferencedialog.cpp b/qdeduper/preferencedialog.cpp new file mode 100644 index 0000000..6851634 --- /dev/null +++ b/qdeduper/preferencedialog.cpp @@ -0,0 +1,170 @@ +#include <QLabel> +#include <QTabWidget> +#include <QGridLayout> +#include <QSpinBox> +#include <QDoubleSpinBox> +#include <QDialogButtonBox> +#include <QCheckBox> +#include <QVBoxLayout> + +#include "preferencedialog.hpp" +#include "settings.hpp" + +PreferenceDialog::PreferenceDialog(SettingsRegistry *sr, QWidget *parent) : QDialog(parent) +{ + this->sr = sr; + tw = new QTabWidget(this); + bb = new QDialogButtonBox(QDialogButtonBox::StandardButton::Ok | QDialogButtonBox::StandardButton::Cancel, this); + this->setWindowTitle("Preferences"); + QVBoxLayout *l = new QVBoxLayout(); + this->setLayout(l); + l->addWidget(tw); + l->addWidget(bb); + QObject::connect(bb, &QDialogButtonBox::accepted, this, &PreferenceDialog::accept); + QObject::connect(bb, &QDialogButtonBox::rejected, this, &PreferenceDialog::reject); + setup_widgets(); +} + +void PreferenceDialog::open() +{ + load_widget_status(); + QDialog::open(); +} + +void PreferenceDialog::accept() +{ + save_widget_status(); + QDialog::accept(); +} + +void PreferenceDialog::setup_widgets() +{ + for (auto &i : sr->tabs) + { + QWidget *container = new QWidget(); + QGridLayout *l = new QGridLayout(container); + tw->addTab(container, i); + tabs.push_back(l); + } + for (auto &k : sr->klist) + { + auto &p = sr->smap[k]; + QWidget *w = nullptr; + QGridLayout *l = p.tab < tabs.size() ? tabs[p.tab] : nullptr; + if (!l) continue; + switch (p.type) + { + case SettingsItem::ParameterType::_int: + { + QSpinBox *sb = new QSpinBox(); + sb->setMinimum(p.min.value<int>()); + sb->setMaximum(p.max.value<int>()); + w = sb; + } + break; + case SettingsItem::ParameterType::_bool: + { + QCheckBox *cb = new QCheckBox(p.desc); + w = cb; + } + break; + case SettingsItem::ParameterType::_double: + { + QDoubleSpinBox *sb = new QDoubleSpinBox(); + sb->setMinimum(p.min.value<double>()); + sb->setMaximum(p.max.value<double>()); + sb->setSingleStep((sb->maximum() - sb->minimum()) / 100.); + w = sb; + } + break; + case SettingsItem::ParameterType::_keyseq: + break; + default: + break; + } + if (!w) continue; + p.w = w; + if (p.type == SettingsItem::ParameterType::_bool) + { + l->addWidget(w, l->rowCount(), 0, 1, 2); + } + else + { + QLabel *lb = new QLabel(p.desc); + lb->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + int r = l->rowCount(); + l->addWidget(lb, r, 0); + l->addWidget(w, r, 1); + } + } +} + +void PreferenceDialog::load_widget_status() +{ + for (auto &k : sr->klist) + { + auto &p = sr->smap[k]; + QWidget *w = p.w; + switch (p.type) + { + case SettingsItem::ParameterType::_int: + { + QSpinBox *sb = qobject_cast<QSpinBox*>(w); + if (!sb) continue; + sb->setValue(sr->get_option_int(p.key)); + } + break; + case SettingsItem::ParameterType::_bool: + { + QCheckBox *cb = qobject_cast<QCheckBox*>(w); + cb->setChecked(sr->get_option_bool(p.key)); + } + break; + case SettingsItem::ParameterType::_double: + { + QDoubleSpinBox *sb = qobject_cast<QDoubleSpinBox*>(w); + sb->setValue(sr->get_option_double(p.key)); + } + break; + case SettingsItem::ParameterType::_keyseq: + break; + default: + break; + } + } +} + +void PreferenceDialog::save_widget_status() +{ + for (auto &k : sr->klist) + { + auto &p = sr->smap[k]; + QWidget *w = p.w; + switch (p.type) + { + case SettingsItem::ParameterType::_int: + { + QSpinBox *sb = qobject_cast<QSpinBox*>(w); + if (!sb) continue; + sr->set_option_int(p.key, sb->value()); + } + break; + case SettingsItem::ParameterType::_bool: + { + QCheckBox *cb = qobject_cast<QCheckBox*>(w); + sr->set_option_bool(p.key, cb->isChecked()); + } + break; + case SettingsItem::ParameterType::_double: + { + QDoubleSpinBox *sb = qobject_cast<QDoubleSpinBox*>(w); + sr->set_option_double(p.key, sb->value()); + } + break; + case SettingsItem::ParameterType::_keyseq: + break; + default: + break; + } + } +} diff --git a/qdeduper/preferencedialog.hpp b/qdeduper/preferencedialog.hpp new file mode 100644 index 0000000..06977fd --- /dev/null +++ b/qdeduper/preferencedialog.hpp @@ -0,0 +1,32 @@ +#ifndef PREFERENCEDIALOG_HPP +#define PREFERENCEDIALOG_HPP + +#include <vector> + +#include <QDialog> +#include <QGridLayout> + +#include "settings.hpp" + +class QTabWidget; +class QGridLayout; +class QDialogButtonBox; + +class PreferenceDialog : public QDialog +{ + Q_OBJECT +public: + PreferenceDialog(SettingsRegistry *sr, QWidget *parent = nullptr); + void setup_widgets(); + void load_widget_status(); + void save_widget_status(); + void open() override; + void accept() override; +private: + SettingsRegistry *sr; + QTabWidget *tw; + std::vector<QGridLayout*> tabs; + QDialogButtonBox *bb; +}; + +#endif diff --git a/qdeduper/settings.cpp b/qdeduper/settings.cpp new file mode 100644 index 0000000..75dfbfd --- /dev/null +++ b/qdeduper/settings.cpp @@ -0,0 +1,129 @@ +#include <limits> + +#include "settings.hpp" + +SettingsRegistry::SettingsRegistry(QString path) +{ + s = new QSettings(path, QSettings::Format::IniFormat); +} +SettingsRegistry::~SettingsRegistry() +{ + delete s; +} +int SettingsRegistry::register_tab(QString tab_name) +{ + tabs.push_back(tab_name); + return tabs.size() - 1; +} + +void SettingsRegistry::register_int_option(int tab, std::string key, QString desc, int min, int max, int defaultval) +{ + klist.push_back(key); + smap[key] = { + SettingsItem::ParameterType::_int, + tab, + key, + desc, + QVariant::fromValue<int>(min), + QVariant::fromValue<int>(max), + QVariant::fromValue<int>(defaultval), + nullptr}; +} + +int SettingsRegistry::get_option_int(std::string key) +{ + if (smap.find(key) == smap.end() || smap[key].type != SettingsItem::ParameterType::_int) + return -1; + return s->value(QString::fromStdString(key), smap[key].defaultv).value<int>(); +} + +void SettingsRegistry::set_option_int(std::string key, int val) +{ + if (smap.find(key) == smap.end() || smap[key].type != SettingsItem::ParameterType::_int) + return; + s->setValue(QString::fromStdString(key), QVariant::fromValue<int>(val)); +} + +void SettingsRegistry::register_bool_option(int tab, std::string key, QString desc, bool defaultval) +{ + klist.push_back(key); + smap[key] = { + SettingsItem::ParameterType::_bool, + tab, + key, + desc, + QVariant(), + QVariant(), + QVariant::fromValue<bool>(defaultval), + nullptr}; +} + +bool SettingsRegistry::get_option_bool(std::string key) +{ + if (smap.find(key) == smap.end() || smap[key].type != SettingsItem::ParameterType::_bool) + return false; + return s->value(QString::fromStdString(key), smap[key].defaultv).value<bool>(); +} + +void SettingsRegistry::set_option_bool(std::string key, bool val) +{ + if (smap.find(key) == smap.end() || smap[key].type != SettingsItem::ParameterType::_bool) + return; + s->setValue(QString::fromStdString(key), QVariant::fromValue<bool>(val)); +} + +void SettingsRegistry::register_double_option(int tab, std::string key, QString desc, double min, double max, double defaultval) +{ + klist.push_back(key); + smap[key] = { + SettingsItem::ParameterType::_double, + tab, + key, + desc, + QVariant::fromValue<double>(min), + QVariant::fromValue<double>(max), + QVariant::fromValue<double>(defaultval), + nullptr}; +} + +double SettingsRegistry::get_option_double(std::string key) +{ + if (smap.find(key) == smap.end() || smap[key].type != SettingsItem::ParameterType::_double) + return std::numeric_limits<double>::quiet_NaN(); + return s->value(QString::fromStdString(key), smap[key].defaultv).value<double>(); +} + +void SettingsRegistry::set_option_double(std::string key, double val) +{ + if (smap.find(key) == smap.end() || smap[key].type != SettingsItem::ParameterType::_double) + return; + s->setValue(QString::fromStdString(key), QVariant::fromValue<double>(val)); +} + +void SettingsRegistry::register_keyseq_option(int tab, std::string key, QString desc, QKeySequence defaultval) +{ + klist.push_back(key); + smap[key] = { + SettingsItem::ParameterType::_keyseq, + tab, + key, + desc, + QVariant(), + QVariant(), + QVariant::fromValue<QKeySequence>(defaultval), + nullptr}; +} + +QKeySequence SettingsRegistry::get_option_keyseq(std::string key) +{ + if (smap.find(key) == smap.end() || smap[key].type != SettingsItem::ParameterType::_keyseq) + return QKeySequence(); + return s->value(QString::fromStdString(key), smap[key].defaultv).value<QKeySequence>(); +} + +void SettingsRegistry::set_option_keyseq(std::string key, QKeySequence ks) +{ + if (smap.find(key) == smap.end() || smap[key].type != SettingsItem::ParameterType::_keyseq) + return; + s->setValue(QString::fromStdString(key), QVariant::fromValue<QKeySequence>(ks)); +} diff --git a/qdeduper/settings.hpp b/qdeduper/settings.hpp new file mode 100644 index 0000000..fb1daa4 --- /dev/null +++ b/qdeduper/settings.hpp @@ -0,0 +1,58 @@ +#ifndef SETTINGS_HPP +#define SETTINGS_HPP + +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include <QKeySequence> +#include <QSettings> + +struct SettingsItem +{ + enum ParameterType + { + _int, + _bool, + _double, + _keyseq, + _str, + _strlist + }; + ParameterType type; + int tab; + std::string key; + QString desc; + QVariant min, max; + QVariant defaultv; + QWidget *w; +}; + +class SettingsRegistry +{ +public: + SettingsRegistry(QString path); + ~SettingsRegistry(); + int register_tab(QString tab_name); + void register_int_option(int tab, std::string key, QString desc, int min, int max, int defaultval); + int get_option_int(std::string key); + void set_option_int(std::string key, int val); + void register_bool_option(int tab, std::string key, QString desc, bool defaultval); + bool get_option_bool(std::string key); + void set_option_bool(std::string key, bool val); + void register_double_option(int tab, std::string key, QString desc, double min, double max, double defaultval); + double get_option_double(std::string key); + void set_option_double(std::string key, double val); + void register_keyseq_option(int tab, std::string key, QString desc, QKeySequence defaultval); + QKeySequence get_option_keyseq(std::string key); + void set_option_keyseq(std::string key, QKeySequence ks); +private: + QSettings *s; + QStringList tabs; + std::map<std::string, SettingsItem> smap; + std::vector<std::string> klist; + friend class PreferenceDialog; +}; + +#endif diff --git a/qdeduper/sigdb_qt.cpp b/qdeduper/sigdb_qt.cpp index a67f330..2128f8d 100644 --- a/qdeduper/sigdb_qt.cpp +++ b/qdeduper/sigdb_qt.cpp @@ -32,6 +32,15 @@ signature_config cfg_subslice = SignatureDB::SignatureDB() : QObject(nullptr) { sdb = new signature_db(); + cfg = { + 3, + 3, + cfg_full, + cfg_subslice, + 0.3, + nullptr, + 1 + }; } SignatureDB::SignatureDB(const fs::path &dbpath) : QObject(nullptr) @@ -82,17 +91,21 @@ bool SignatureDB::is_dirty() return sdb->is_dirty(); } -void SignatureDB::scan_files(const std::vector<fs::path> &files, int njobs) +populate_cfg_t SignatureDB::get_sig_config() { - populate_cfg_t pcfg = { - 3, - 3, - cfg_full, - cfg_subslice, - 0.3, - [this](size_t c,int){Q_EMIT image_scanned(c);}, - njobs - }; + return cfg; +} + +void SignatureDB::set_sig_config(populate_cfg_t cfg) +{ + this->cfg = cfg; +} + +void SignatureDB::scan_files(const std::vector<fs::path> &files) +{ + populate_cfg_t pcfg = cfg; + pcfg.callback = [this](size_t c,int){Q_EMIT image_scanned(c);}; + sdb->populate(files, pcfg); Q_EMIT image_scanned(~size_t(0)); @@ -109,17 +122,8 @@ void SignatureDB::interrupt_scan() std::vector<std::pair<size_t, double>> SignatureDB::search_file(const fs::path &files) { - populate_cfg_t pcfg = { - 3, - 3, - cfg_full, - cfg_subslice, - 0.3, - nullptr, - 0 - }; if(sdb) - return sdb->search_image(files, pcfg, false); + return sdb->search_image(files, cfg, false); return {}; } diff --git a/qdeduper/sigdb_qt.hpp b/qdeduper/sigdb_qt.hpp index 1672915..39b57b7 100644 --- a/qdeduper/sigdb_qt.hpp +++ b/qdeduper/sigdb_qt.hpp @@ -22,6 +22,7 @@ private: std::unordered_map<fs::path, size_t> frmap; std::map<std::pair<size_t, size_t>, double> distmap; std::vector<std::vector<size_t>> groups; + populate_cfg_t cfg; void create_priv_struct(); public: @@ -32,7 +33,10 @@ public: bool valid(); bool is_dirty(); - void scan_files(const std::vector<fs::path> &files, int njobs); + populate_cfg_t get_sig_config(); + void set_sig_config(populate_cfg_t cfg); + + void scan_files(const std::vector<fs::path> &files); void interrupt_scan(); std::vector<std::pair<size_t, double>> search_file(const fs::path &files); |