aboutsummaryrefslogblamecommitdiff
path: root/qdeduper/preferencedialog.cpp
blob: be16b6e358b782a9faf8bf75579fa52f2ab05f28 (plain) (tree)
1
2
3
4
5
6
7

                  
                  


                     
                    







                           


                             

                                
                           

                      



                               






                                                 





                                                                                                                     
                               
















                                                                                       















                                                                                                                                            















                                                    
                                       

















































                                                                              
                                                                                                                                     
 
                            







                                                                                
















                                                                                                                                  

 





































































                                                                                                 
































                                                                      

                                                                   
                                    
     




                                                                  
                                                               






                                                                                         

















                                                                                          





















                                                                                           


































                                                                      

                                              
                                                                                     
                                                                                                   







                                                                         
     



                                                                                               
 






























                                                                                                     






















                                                                                                                                  
 












































































                                                                                                      
//Chris Xiong 2022
//License: MPL-2.0
#include <climits>
#include <functional>

#include <QDebug>
#include <QKeyEvent>
#include <QLabel>
#include <QTabWidget>
#include <QGridLayout>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QDialogButtonBox>
#include <QCheckBox>
#include <QVBoxLayout>
#include <QAction>
#include <QTableView>
#include <QStandardItemModel>
#include <QSortFilterProxyModel>
#include <QListView>
#include <QKeySequenceEdit>
#include <QGroupBox>
#include <QMessageBox>

#include "preferencedialog.hpp"
#include "settings.hpp"

enum ToolbarActionRoles
{
    action_role = Qt::ItemDataRole::UserRole + 1,
    order_role,
    hide_role
};

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");
    this->setMinimumWidth(480);
    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()
{
    QKeySequence bks;
    switch (verify_shortcuts(&bks))
    {
        case 0:
        break;
        case 1:
            QMessageBox::critical(this, "Ambiguous shortcut found",
                                  QString("Shortcut %1 is assigned to multiple actions.").arg(bks.toString()));
        return;
        case 2:
            QMessageBox::critical(this, "Bad shortcut assignment",
                                  QString("Shortcut %1 has more than one key and cannot be assigned to item actions.").arg(bks.toString()));
        return;
        default:
        break;
    }
    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];
        if (!p.desc.length()) continue;
        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::set_hkactions(int tab, const std::vector<std::string> &actlist, const std::map<std::string, QAction*> &actmap)
{
    this->actlist = actlist;
    this->actmap = actmap;
    this->hktv = new QTableView();
    this->hkim = new QStandardItemModel();
    this->tabs[tab]->addWidget(hktv, 0, 0);
    this->hktv->setModel(hkim);
    this->hktv->setSortingEnabled(false);
    this->hktv->setSelectionMode(QAbstractItemView::SelectionMode::NoSelection);
    this->hktv->setItemDelegateForColumn(1, new ShortcutEditorDelegate);

    QGroupBox *gb = new QGroupBox("Action Modifiers");
    QGridLayout *l = new QGridLayout();
    gb->setLayout(l);
    this->tabs[tab]->addWidget(gb, 1, 0);

    const QStringList llist = {"Mark/Unmark", "Mark All Except", "Maximize", "Open with System Viewer", "Open Containing Folder"};

    for (int i = 0; i < llist.size(); ++i)
    {
        auto &lt = llist[i];
        ModifierEdit *me = new ModifierEdit();
        QLabel *lb = new QLabel(lt);
        l->addWidget(lb, i, 0);
        l->addWidget(me, i, 1);
        mes.push_back(me);
    }
}

void PreferenceDialog::set_toolbaractions(int tab, const std::map<std::string, QAction*> &actmap)
{
    QGridLayout *l = this->tabs[tab];
    tbaav = new QListView();
    tbaam = new QStandardItemModel();
    tbapm = new QSortFilterProxyModel();
    tbapm->setSourceModel(tbaam);
    tbapm->setSortRole(ToolbarActionRoles::order_role);
    tbapm->setFilterRole(ToolbarActionRoles::hide_role);
    tbapm->setFilterKeyColumn(0);
    tbapm->setFilterFixedString("0");
    tbaav->setModel(tbapm);
    tbeav = new QListView();
    tbeam = new QStandardItemModel();
    tbeav->setModel(tbeam);
    tbaav->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
    tbeav->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
    tbaav->setDragDropMode(QAbstractItemView::DragDropMode::NoDragDrop);
    tbeav->setDragDropMode(QAbstractItemView::DragDropMode::InternalMove);
    QPushButton *pbadd = new QPushButton(">");
    QPushButton *pbdel = new QPushButton("<");
    pbadd->setMaximumWidth(24);
    pbdel->setMaximumWidth(24);
    QVBoxLayout *vbl = new QVBoxLayout();
    vbl->addWidget(pbadd);
    vbl->addWidget(pbdel);
    l->addWidget(new QLabel("Available Buttons"), 0, 0);
    l->addWidget(new QLabel("Enabled Buttons"), 0, 2);
    l->addWidget(tbaav, 1, 0);
    l->addLayout(vbl, 1, 1);
    l->addWidget(tbeav, 1, 2);
    l->addWidget(new QLabel("Drag to sort enabled buttons."), 2, 0, 1, 3);
    tbaam->clear();
    for (size_t i = 0; i < actlist.size(); ++i)
    {
        auto &actn = actlist[i];
        QAction *act = this->actmap[actn];
        QStandardItem *itm = new QStandardItem(act->text());
        itm->setIcon(act->icon());
        itm->setData(QString::fromStdString(actn), ToolbarActionRoles::action_role);
        itm->setData(QVariant::fromValue<size_t>(i), ToolbarActionRoles::order_role);
        itm->setData("0", ToolbarActionRoles::hide_role);
        tbaam->appendRow(itm);
    }
    QObject::connect(pbadd, &QPushButton::clicked, [this] {
        QModelIndex idx = tbaav->currentIndex();
        if (!idx.isValid()) return;
        QString actn = idx.data(ToolbarActionRoles::action_role).value<QString>();
        QAction *act = this->actmap[actn.toStdString()];
        QStandardItem *itm = new QStandardItem(act->text());
        itm->setIcon(act->icon());
        itm->setData(actn, ToolbarActionRoles::action_role);
        itm->setDropEnabled(false);
        tbeam->appendRow(itm);
        tbaam->setData(tbapm->mapToSource(idx), "1", ToolbarActionRoles::hide_role);
    });
    QObject::connect(pbdel, &QPushButton::clicked, [this] {
        QModelIndex idx = tbeav->currentIndex();
        if (!idx.isValid()) return;
        QString actn = idx.data(ToolbarActionRoles::action_role).value<QString>();
        for (int i = 0; i < tbaam->rowCount(); ++i)
        {
            const auto &idx = tbaam->index(i, 0);
            if (tbaam->data(idx, ToolbarActionRoles::action_role).value<QString>() == actn)
                tbaam->setData(idx, "0", ToolbarActionRoles::hide_role);
        }
        tbeam->removeRows(idx.row(), 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;
        }
    }
    this->hkim->clear();
    this->hkim->setHorizontalHeaderLabels({"Menu Item", "Hotkey"});
    for (auto &actn : this->actlist)
    {
        QKeySequence ks = sr->get_option_keyseq("hotkey/" + actn);
        if (this->actmap.find(actn) == this->actmap.end())
            continue;
        QAction *act = this->actmap[actn];
        QStandardItem *itma = new QStandardItem(act->text());
        QStandardItem *itmk = new QStandardItem(ks.toString());
        itma->setIcon(act->icon());
        itma->setEditable(false);
        itma->setData(QString::fromStdString(actn), Qt::ItemDataRole::UserRole);
        itmk->setData(QVariant::fromValue<QKeySequence>(ks), Qt::ItemDataRole::UserRole);
        this->hkim->appendRow({itma, itmk});
    }
    this->hktv->resizeColumnsToContents();
    for (size_t i = 0; i < 16; ++i)
    {
        std::string iakt = "item_" + std::to_string(i) + "_action_key";
        int ik = sr->get_option_int("hotkey/" + iakt);
        QKeySequence ks(ik);
        QStandardItem *itma = new QStandardItem(QString("Item %1 Action Key").arg(i + 1));
        QStandardItem *itmk = new QStandardItem(ks.toString());
        itma->setEditable(false);
        itma->setData(QString::fromStdString(iakt), Qt::ItemDataRole::UserRole);
        itmk->setData(QVariant::fromValue<QKeySequence>(ks), Qt::ItemDataRole::UserRole);
        this->hkim->appendRow({itma, itmk});
    }
    for (size_t i = 0; i < 5; ++i)
    {
        std::string iamt = "hotkey/item_action_mod_" + std::to_string(i);
        int im = sr->get_option_int(iamt);
        mes[i]->set_modifier(static_cast<Qt::Modifier>(im));
    }
    for (int i = 0; i < tbaam->rowCount(); ++i)
    {
        const auto &idx = tbaam->index(i, 0);
        tbaam->setData(idx, "0", ToolbarActionRoles::hide_role);
    }
    auto tbal = sr->get_option_strlist("toolbar_actions");
    this->tbeam->clear();
    for (auto &tban : tbal)
    {
        QAction *act = this->actmap[tban.toStdString()];
        QStandardItem *itm = new QStandardItem(act->text());
        itm->setIcon(act->icon());
        itm->setData(tban, ToolbarActionRoles::action_role);
        itm->setDropEnabled(false);
        for (int i = 0; i < tbaam->rowCount(); ++i)
        {
            const auto &idx = tbaam->index(i, 0);
            if (tbaam->data(idx, ToolbarActionRoles::action_role).value<QString>() == tban)
                tbaam->setData(idx, "1", ToolbarActionRoles::hide_role);
        }
        tbeam->appendRow(itm);
    }
}

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;
        }
    }
    for (int i = 0; i < hkim->rowCount(); ++i)
    {
        QString actn = hkim->item(i, 0)->data(Qt::ItemDataRole::UserRole).toString();
        QKeySequence ks = hkim->item(i, 1)->data(Qt::ItemDataRole::UserRole).value<QKeySequence>();
        if (actn.endsWith("_action_key"))
            sr->set_option_int("hotkey/" + actn.toStdString(), ks[0]);
        else sr->set_option_keyseq("hotkey/" + actn.toStdString(), ks);
    }
    for (size_t i = 0; i < 5; ++i)
    {
        std::string iamt = "hotkey/item_action_mod_" + std::to_string(i);
        sr->set_option_int(iamt, mes[i]->get_modifier());
    }
    QStringList tbal;
    for (int i = 0; i < tbeam->rowCount(); ++i)
        tbal.push_back(tbeam->item(i)->data(ToolbarActionRoles::action_role).value<QString>());
    sr->set_option_strlist("toolbar_actions", tbal);
}

int PreferenceDialog::verify_shortcuts(QKeySequence *bks)
{
    QList<QKeySequence> ksl;
    for (int i = 0; i < hkim->rowCount(); ++i)
    {
        QString actn = hkim->item(i, 0)->data(Qt::ItemDataRole::UserRole).toString();
        QKeySequence ks = hkim->item(i, 1)->data(Qt::ItemDataRole::UserRole).value<QKeySequence>();
        if (actn.endsWith("_action_key"))
        {
            if (ks.count() > 1)
            {
                *bks = ks;
                return 2;
            }
            for (int j = 0;j < 5; ++j)
                if (mes[j]->get_modifier() != INT_MAX)
                    ksl.push_back(QKeySequence(mes[j]->get_modifier() | ks[0]));
        }
        else ksl.push_back(hkim->item(i, 1)->data(Qt::ItemDataRole::UserRole).value<QKeySequence>());
    }
    for (int i = 0; i < ksl.size(); ++i)
        for (int j = i + 1; j < ksl.size(); ++j)
            if (!ksl[i].isEmpty() && ksl[i] == ksl[j])
            {
                *bks = ksl[i];
                return 1;
            }
    return 0;
}

QWidget* ShortcutEditorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(option);
    Q_UNUSED(index);
    QKeySequenceEdit *kse = new QKeySequenceEdit(parent);
    QObject::connect(kse, &QKeySequenceEdit::editingFinished, [this, kse] {
        Q_EMIT const_cast<ShortcutEditorDelegate*>(this)->commitData(kse);
        Q_EMIT const_cast<ShortcutEditorDelegate*>(this)->closeEditor(kse);
    });
    return kse;
}

void ShortcutEditorDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    QKeySequenceEdit *kse = qobject_cast<QKeySequenceEdit*>(editor);
    kse->setKeySequence(index.data(Qt::ItemDataRole::UserRole).value<QKeySequence>());
}

void ShortcutEditorDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    QKeySequenceEdit *kse = qobject_cast<QKeySequenceEdit*>(editor);
    model->setData(index, QVariant::fromValue<QKeySequence>(kse->keySequence()), Qt::ItemDataRole::UserRole);
    model->setData(index, kse->keySequence().toString(), Qt::ItemDataRole::DisplayRole);
}

ModifierEdit::ModifierEdit(QWidget *par) : QPushButton(par)
{
    this->setCheckable(true);
    this->setFocusPolicy(Qt::FocusPolicy::StrongFocus);
    this->mod = static_cast<Qt::Modifier>(0);
    QObject::connect(this, &QPushButton::toggled, [this](bool c) {
        if (c)
        {
            if (int(this->mod) == INT_MAX)
                this->set_modifier(static_cast<Qt::Modifier>(0));
            this->setText("Input modifier...");
        }
        else
            this->set_modifier(this->mod);
    });
}

Qt::Modifier ModifierEdit::get_modifier()
{
    return this->mod;
}

void ModifierEdit::set_modifier(Qt::Modifier mod)
{
    this->mod = mod;
    int imod = mod;
    switch (imod)
    {
        case 0:
            if (this->isChecked())
                this->setText("Input modifier...");
            else
                this->setText("No modifier");
        break;
        case INT_MAX:
            this->setText("Disabled");
        break;
        default:
            QString kss = QKeySequence(mod).toString();
            while (kss.length() > 0 && kss.back() == '+') kss.chop(1);
            this->setText(kss);
    }
}

void ModifierEdit::keyPressEvent(QKeyEvent *e)
{
    if (!this->isChecked()) return QPushButton::keyPressEvent(e);
    switch (e->key())
    {
        case Qt::Key::Key_Enter:
        case Qt::Key::Key_Return:
        case Qt::Key::Key_Space:
            this->set_modifier(static_cast<Qt::Modifier>(Qt::KeyboardModifiers::Int(e->modifiers())));
            this->setChecked(false);
        break;
        case Qt::Key::Key_Escape:
            this->set_modifier(static_cast<Qt::Modifier>(0));
            this->setChecked(false);
        break;
        case Qt::Key::Key_Backspace:
        case Qt::Key::Key_Delete:
            this->set_modifier(static_cast<Qt::Modifier>(INT_MAX));
            this->setChecked(false);
        break;
    }
}

bool ModifierEdit::event(QEvent *e)
{
    if (e->type() == QEvent::Type::ShortcutOverride)
    {
        e->accept();
        return true;
    }
    return QPushButton::event(e);
}