From 2e7924a07d45c1d1468552f951dcd2f2dcc948fa Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Mon, 26 Sep 2022 22:14:18 -0400 Subject: 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. --- qdeduper/CMakeLists.txt | 2 + qdeduper/imageitem.cpp | 12 ++- qdeduper/imageitem.hpp | 4 +- qdeduper/mingui.cpp | 42 ++++++++++- qdeduper/mingui.hpp | 8 +- qdeduper/preferencedialog.cpp | 170 ++++++++++++++++++++++++++++++++++++++++++ qdeduper/preferencedialog.hpp | 32 ++++++++ qdeduper/settings.cpp | 129 ++++++++++++++++++++++++++++++++ qdeduper/settings.hpp | 58 ++++++++++++++ qdeduper/sigdb_qt.cpp | 44 ++++++----- qdeduper/sigdb_qt.hpp | 6 +- 11 files changed, 479 insertions(+), 28 deletions(-) create mode 100644 qdeduper/preferencedialog.cpp create mode 100644 qdeduper/preferencedialog.hpp create mode 100644 qdeduper/settings.cpp create mode 100644 qdeduper/settings.hpp (limited to 'qdeduper') 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(); @@ -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 #include @@ -18,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -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> 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 menuact; QList 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 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 +#include +#include +#include +#include +#include +#include +#include + +#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()); + sb->setMaximum(p.max.value()); + 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()); + sb->setMaximum(p.max.value()); + 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(w); + if (!sb) continue; + sb->setValue(sr->get_option_int(p.key)); + } + break; + case SettingsItem::ParameterType::_bool: + { + QCheckBox *cb = qobject_cast(w); + cb->setChecked(sr->get_option_bool(p.key)); + } + break; + case SettingsItem::ParameterType::_double: + { + QDoubleSpinBox *sb = qobject_cast(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(w); + if (!sb) continue; + sr->set_option_int(p.key, sb->value()); + } + break; + case SettingsItem::ParameterType::_bool: + { + QCheckBox *cb = qobject_cast(w); + sr->set_option_bool(p.key, cb->isChecked()); + } + break; + case SettingsItem::ParameterType::_double: + { + QDoubleSpinBox *sb = qobject_cast(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 + +#include +#include + +#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 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 + +#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(min), + QVariant::fromValue(max), + QVariant::fromValue(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(); +} + +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(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(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(); +} + +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(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(min), + QVariant::fromValue(max), + QVariant::fromValue(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::quiet_NaN(); + return s->value(QString::fromStdString(key), smap[key].defaultv).value(); +} + +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(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(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(); +} + +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(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 +#include +#include +#include + +#include +#include + +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 smap; + std::vector 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 &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 &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> 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 frmap; std::map, double> distmap; std::vector> 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 &files, int njobs); + populate_cfg_t get_sig_config(); + void set_sig_config(populate_cfg_t cfg); + + void scan_files(const std::vector &files); void interrupt_scan(); std::vector> search_file(const fs::path &files); -- cgit v1.2.3