reimplented shortcuts in the config dialog using model/view programming

Wed, 26 Dec 2018 23:01:45 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Wed, 26 Dec 2018 23:01:45 +0200
changeset 1433
bd3a9e237ef5
parent 1432
4cc687851fbb
child 1434
4c06435bd87f

reimplented shortcuts in the config dialog using model/view programming

CMakeLists.txt file | annotate | diff | comparison | revisions
src/basics.h file | annotate | diff | comparison | revisions
src/dialogs/configdialog.cpp file | annotate | diff | comparison | revisions
src/dialogs/configdialog.h file | annotate | diff | comparison | revisions
src/dialogs/configdialog.ui file | annotate | diff | comparison | revisions
src/dialogs/shortcutsmodel.cpp file | annotate | diff | comparison | revisions
src/dialogs/shortcutsmodel.h file | annotate | diff | comparison | revisions
src/mainwindow.cpp file | annotate | diff | comparison | revisions
src/mainwindow.h file | annotate | diff | comparison | revisions
src/widgets/extendedkeysequenceeditor.cpp file | annotate | diff | comparison | revisions
src/widgets/extendedkeysequenceeditor.h file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Wed Dec 26 16:38:38 2018 +0200
+++ b/CMakeLists.txt	Wed Dec 26 23:01:45 2018 +0200
@@ -69,6 +69,7 @@
 	src/dialogs/externalprogrampathdialog.cpp
 	src/dialogs/generateprimitivedialog.cpp
 	src/dialogs/ldrawpathdialog.cpp
+	src/dialogs/shortcutsmodel.cpp
 	src/dialogs/subfilereferenceeditor.cpp
 	src/dialogs/newpartdialog.cpp
 	src/editmodes/abstractEditMode.cpp
@@ -101,6 +102,7 @@
 	src/widgets/circularsectioneditor.cpp
 	src/widgets/colorbutton.cpp
 	src/widgets/doublespinbox.cpp
+	src/widgets/extendedkeysequenceeditor.cpp
 	src/widgets/headeredit.cpp
 	src/widgets/matrixeditor.cpp
 	src/widgets/vertexobjecteditor.cpp
@@ -145,6 +147,7 @@
 	src/dialogs/generateprimitivedialog.h
 	src/dialogs/ldrawpathdialog.h
 	src/dialogs/newpartdialog.h
+	src/dialogs/shortcutsmodel.h
 	src/dialogs/subfilereferenceeditor.h
 	src/editmodes/abstractEditMode.h
 	src/editmodes/circleMode.h
@@ -185,6 +188,7 @@
 	src/widgets/circularsectioneditor.h
 	src/widgets/colorbutton.h
 	src/widgets/doublespinbox.h
+	src/widgets/extendedkeysequenceeditor.h
 	src/widgets/headeredit.h
 	src/widgets/matrixeditor.h
 	src/widgets/vertexobjecteditor.h
--- a/src/basics.h	Wed Dec 26 16:38:38 2018 +0200
+++ b/src/basics.h	Wed Dec 26 23:01:45 2018 +0200
@@ -104,6 +104,9 @@
 	matrix(2, 3) += vector.z();
 }
 
+template<typename... Ts>
+inline void ignore(Ts&&...) {}
+
 qreal determinant(qreal a, qreal b, qreal c, qreal d);
 qreal determinant(qreal a, qreal b, qreal c, qreal d, qreal e, qreal f, qreal g, qreal h, qreal i);
 qreal determinant(const QMatrix2x2& matrix);
--- a/src/dialogs/configdialog.cpp	Wed Dec 26 16:38:38 2018 +0200
+++ b/src/dialogs/configdialog.cpp	Wed Dec 26 23:01:45 2018 +0200
@@ -14,10 +14,6 @@
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *  =====================================================================
- *
- *  configDialog.cxx: Settings dialog and everything related to it.
- *  Actual configuration core is in config.cxx.
  */
 
 #include <QGridLayout>
@@ -47,38 +43,19 @@
 #endif
 	"All files (*.*)(*.*)";
 
-ShortcutListItem::ShortcutListItem (QListWidget* view, int type) :
-	QListWidgetItem (view, type) {}
-
-QAction* ShortcutListItem::action() const
-{
-	return m_action;
-}
-
-void ShortcutListItem::setAction (QAction* action)
-{
-	m_action = action;
-}
-
-QKeySequence ShortcutListItem::sequence() const
-{
-	return m_sequence;
-}
-
-void ShortcutListItem::setSequence (const QKeySequence& sequence)
-{
-	m_sequence = sequence;
-}
-
 ConfigDialog::ConfigDialog (QWidget* parent, ConfigDialog::Tab defaulttab, Qt::WindowFlags f) :
 	QDialog (parent, f),
 	HierarchyElement (parent),
 	ui (*new Ui_ConfigDialog),
 	librariesModel {new LibrariesModel {this->libraries, this}},
-	libraries {config::libraries()}
+	libraries {config::libraries()},
+	shortcuts {m_window},
+	shortcutsDelegate {this}
 {
 	ui.setupUi (this);
 	ui.librariesView->setModel(this->librariesModel);
+	ui.shortcutsList->setModel(&shortcuts);
+	ui.shortcutsList->setItemDelegateForColumn(ShortcutsModel::KeySequenceColumn, &shortcutsDelegate);
 
 	// Set defaults
 	applyToWidgetOptions([&](QWidget* widget, QString confname)
@@ -122,18 +99,8 @@
 		}
 	});
 
-	m_window->applyToActions ([&](QAction* act)
-	{
-		addShortcut (act);
-	});
-
-	ui.shortcutsList->setSortingEnabled (true);
-	ui.shortcutsList->sortItems();
 	initExtProgs();
 	selectPage (defaulttab);
-	connect (ui.shortcut_set, SIGNAL (clicked()), this, SLOT (slot_setShortcut()));
-	connect (ui.shortcut_reset, SIGNAL (clicked()), this, SLOT (slot_resetShortcut()));
-	connect (ui.shortcut_clear, SIGNAL (clicked()), this, SLOT (slot_clearShortcut()));
 	connect (ui.findDownloadPath, SIGNAL (clicked (bool)), this, SLOT (slot_findDownloadFolder()));
 	connect (ui.buttonBox, SIGNAL (clicked (QAbstractButton*)),
 		this, SLOT (buttonClicked (QAbstractButton*)));
@@ -195,25 +162,6 @@
 }
 
 //
-// Adds a shortcut entry to the list of shortcuts.
-//
-void ConfigDialog::addShortcut (QAction* act)
-{
-	ShortcutListItem* item = new ShortcutListItem;
-	item->setIcon (act->icon());
-	item->setAction (act);
-	item->setSequence (act->shortcut());
-	setShortcutText (item);
-
-	// If the action doesn't have a valid icon, use an empty one
-	// so that the list is kept aligned.
-	if (act->icon().isNull())
-		item->setIcon (MainWindow::getIcon ("empty"));
-
-	ui.shortcutsList->insertItem (ui.shortcutsList->count(), item);
-}
-
-//
 // Initializes the stuff in the ext programs tab
 //
 void ConfigDialog::initExtProgs()
@@ -310,6 +258,7 @@
 	});
 
 	ui.colorToolbarEditor->saveChanges();
+	shortcuts.saveChanges();
 	config::setLibraries(this->libraries);
 
 	// Ext program settings
@@ -324,13 +273,6 @@
 			toolset->setWineSetting (program, widgets.wineBox->isChecked());
 	}
 
-	// Apply shortcuts
-	for (int i = 0; i < ui.shortcutsList->count(); ++i)
-	{
-		auto item = static_cast<ShortcutListItem*> (ui.shortcutsList->item (i));
-		item->action()->setShortcut (item->sequence());
-	}
-
 	settingsObject().sync();
 	emit settingsChanged();
 }
@@ -391,81 +333,6 @@
 }
 
 //
-// Finds the given list widget item in the list of widget items given.
-//
-int ConfigDialog::getItemRow (QListWidgetItem* item, QVector<QListWidgetItem*>& haystack)
-{
-	int i = 0;
-
-	for (QListWidgetItem* it : haystack)
-	{
-		if (it == item)
-			return i;
-
-		++i;
-	}
-
-	return -1;
-}
-
-//
-// Get the list of shortcuts selected
-//
-QVector<ShortcutListItem*> ConfigDialog::getShortcutSelection()
-{
-	QVector<ShortcutListItem*> out;
-
-	for (QListWidgetItem* entry : ui.shortcutsList->selectedItems())
-		out << static_cast<ShortcutListItem*> (entry);
-
-	return out;
-}
-
-//
-// Edit the shortcut of a given action.
-//
-void ConfigDialog::slot_setShortcut()
-{
-	QVector<ShortcutListItem*> sel = getShortcutSelection();
-
-	if (countof(sel) < 1)
-		return;
-
-	ShortcutListItem* item = sel[0];
-
-	if (KeySequenceDialog::staticDialog (item, this))
-		setShortcutText (item);
-}
-
-//
-// Reset a shortcut to defaults
-//
-void ConfigDialog::slot_resetShortcut()
-{
-	QVector<ShortcutListItem*> sel = getShortcutSelection();
-
-	for (ShortcutListItem* item : sel)
-	{
-		item->setSequence (m_window->defaultShortcut (item->action()));
-		setShortcutText (item);
-	}
-}
-
-//
-// Remove the shortcut of an action.
-//
-void ConfigDialog::slot_clearShortcut()
-{
-	QVector<ShortcutListItem*> sel = getShortcutSelection();
-
-	for (ShortcutListItem* item : sel)
-	{
-		item->setSequence (QKeySequence());
-		setShortcutText (item);
-	}
-}
-
-//
 // Set the path of an external program
 //
 void ConfigDialog::slot_setExtProgPath()
@@ -506,72 +373,3 @@
 	if (not dpath.isEmpty())
 		ui.configDownloadFilePath->setText (dpath);
 }
-
-//
-//
-// Updates the text string for a given shortcut list item
-//
-void ConfigDialog::setShortcutText (ShortcutListItem* item)
-{
-	QAction* act = item->action();
-	QString label = act->iconText();
-	QString keybind = item->sequence().toString();
-	item->setText (format ("%1 (%2)", label, keybind));
-}
-
-//
-//
-KeySequenceDialog::KeySequenceDialog (QKeySequence seq, QWidget* parent, Qt::WindowFlags f) :
-	QDialog (parent, f), seq (seq)
-{
-	lb_output = new QLabel;
-
-	bbx_buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); \
-	connect (bbx_buttons, SIGNAL (accepted()), this, SLOT (accept())); \
-	connect (bbx_buttons, SIGNAL (rejected()), this, SLOT (reject())); \
-
-	setWhatsThis (tr ("Into this dialog you can input a key sequence for use as a "
-		"shortcut in LDForge. Use OK to confirm the new shortcut and Cancel to "
-		"dismiss."));
-
-	QVBoxLayout* layout = new QVBoxLayout;
-	layout->addWidget (lb_output);
-	layout->addWidget (bbx_buttons);
-	setLayout (layout);
-
-	updateOutput();
-}
-
-//
-//
-bool KeySequenceDialog::staticDialog (ShortcutListItem* item, QWidget* parent)
-{
-	KeySequenceDialog dlg (item->sequence(), parent);
-
-	if (dlg.exec() == QDialog::Rejected)
-		return false;
-
-	item->setSequence (dlg.seq);
-	return true;
-}
-
-//
-//
-void KeySequenceDialog::updateOutput()
-{
-	QString shortcut = seq.toString();
-
-	if (seq == QKeySequence())
-		shortcut = "&lt;empty&gt;";
-
-	QString text = format ("<center><b>%1</b></center>", shortcut);
-	lb_output->setText (text);
-}
-
-//
-//
-void KeySequenceDialog::keyPressEvent (QKeyEvent* ev)
-{
-	seq = ev->key() + ev->modifiers();
-	updateOutput();
-}
--- a/src/dialogs/configdialog.h	Wed Dec 26 16:38:38 2018 +0200
+++ b/src/dialogs/configdialog.h	Wed Dec 26 23:01:45 2018 +0200
@@ -19,24 +19,9 @@
 #pragma once
 #include "../mainwindow.h"
 #include "../toolsets/extprogramtoolset.h"
+#include "shortcutsmodel.h"
 #include <QDialog>
 
-// =============================================================================
-class ShortcutListItem : public QListWidgetItem
-{
-public:
-	explicit ShortcutListItem (QListWidget* view = nullptr, int type = Type);
-
-	QAction* action() const;
-	QKeySequence sequence() const;
-	void setAction (QAction* action);
-	void setSequence (const QKeySequence& sequence);
-
-private:
-	QAction* m_action;
-	QKeySequence m_sequence;
-};
-
 struct ExternalProgramWidgets
 {
 	class QLineEdit* input;
@@ -44,7 +29,6 @@
 	class QCheckBox* wineBox;
 };
 
-// =============================================================================
 class ConfigDialog : public QDialog, public HierarchyElement
 {
 	Q_OBJECT
@@ -77,44 +61,18 @@
 	ExternalProgramWidgets m_externalProgramWidgets[NumExternalPrograms];
 	class LibrariesModel* librariesModel;
 	Libraries libraries;
+	ShortcutsModel shortcuts;
+	KeySequenceDelegate shortcutsDelegate;
 
 	void applySettings();
-	void addShortcut (QAction* act);
 	void setButtonBackground (QPushButton* button, QString value);
-	void setShortcutText (ShortcutListItem* item);
-	int getItemRow (QListWidgetItem* item, QVector<QListWidgetItem*>& haystack);
-	QVector<ShortcutListItem*> getShortcutSelection();
 	void initExtProgs();
 	void applyToWidgetOptions (std::function<void (QWidget*, QString)> func);
 
 private slots:
 	void setButtonColor();
-	void slot_setShortcut();
-	void slot_resetShortcut();
-	void slot_clearShortcut();
 	void slot_setExtProgPath();
 	void slot_findDownloadFolder();
 	void buttonClicked (QAbstractButton* button);
 	void selectPage (int row);
 };
-
-// =============================================================================
-//
-class KeySequenceDialog : public QDialog
-{
-	Q_OBJECT
-
-public:
-	explicit KeySequenceDialog (QKeySequence seq, QWidget* parent = nullptr, Qt::WindowFlags f = 0);
-	static bool staticDialog (ShortcutListItem* item, QWidget* parent = nullptr);
-
-	class QLabel* lb_output;
-	class QDialogButtonBox* bbx_buttons;
-	QKeySequence seq;
-
-private:
-	void updateOutput();
-
-private slots:
-	virtual void keyPressEvent (QKeyEvent* ev) override;
-};
--- a/src/dialogs/configdialog.ui	Wed Dec 26 16:38:38 2018 +0200
+++ b/src/dialogs/configdialog.ui	Wed Dec 26 23:01:45 2018 +0200
@@ -84,7 +84,7 @@
      <item>
       <widget class="QStackedWidget" name="m_pages">
        <property name="currentIndex">
-        <number>5</number>
+        <number>0</number>
        </property>
        <widget class="QWidget" name="page_3">
         <layout class="QVBoxLayout" name="verticalLayout_13">
@@ -617,58 +617,27 @@
            </property>
            <layout class="QHBoxLayout" name="horizontalLayout_5">
             <item>
-             <widget class="QListWidget" name="shortcutsList">
+             <widget class="QTableView" name="shortcutsList">
               <property name="verticalScrollBarPolicy">
                <enum>Qt::ScrollBarAsNeeded</enum>
               </property>
+              <property name="alternatingRowColors">
+               <bool>true</bool>
+              </property>
+              <property name="sortingEnabled">
+               <bool>true</bool>
+              </property>
+              <attribute name="horizontalHeaderDefaultSectionSize">
+               <number>200</number>
+              </attribute>
+              <attribute name="horizontalHeaderStretchLastSection">
+               <bool>true</bool>
+              </attribute>
+              <attribute name="verticalHeaderVisible">
+               <bool>false</bool>
+              </attribute>
              </widget>
             </item>
-            <item>
-             <layout class="QVBoxLayout" name="verticalLayout_4">
-              <item>
-               <widget class="QPushButton" name="shortcut_set">
-                <property name="text">
-                 <string>Set</string>
-                </property>
-               </widget>
-              </item>
-              <item>
-               <widget class="QPushButton" name="shortcut_reset">
-                <property name="text">
-                 <string>Reset</string>
-                </property>
-                <property name="icon">
-                 <iconset resource="../../ldforge.qrc">
-                  <normaloff>:/icons/undo.png</normaloff>:/icons/undo.png</iconset>
-                </property>
-               </widget>
-              </item>
-              <item>
-               <widget class="QPushButton" name="shortcut_clear">
-                <property name="text">
-                 <string>Clear</string>
-                </property>
-                <property name="icon">
-                 <iconset resource="../../ldforge.qrc">
-                  <normaloff>:/icons/delete.png</normaloff>:/icons/delete.png</iconset>
-                </property>
-               </widget>
-              </item>
-              <item>
-               <spacer name="verticalSpacer_2">
-                <property name="orientation">
-                 <enum>Qt::Vertical</enum>
-                </property>
-                <property name="sizeHint" stdset="0">
-                 <size>
-                  <width>20</width>
-                  <height>40</height>
-                 </size>
-                </property>
-               </spacer>
-              </item>
-             </layout>
-            </item>
            </layout>
           </widget>
          </item>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dialogs/shortcutsmodel.cpp	Wed Dec 26 23:01:45 2018 +0200
@@ -0,0 +1,262 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2018 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QKeySequenceEdit>
+#include <QSettings>
+#include "mainwindow.h"
+#include "widgets/extendedkeysequenceeditor.h"
+#include "shortcutsmodel.h"
+
+/*
+ * Constructs a new shortcuts model.
+ * Actions are acquired from the provided mainwindow.
+ */
+ShortcutsModel::ShortcutsModel(MainWindow* parent)
+{
+	for (QAction* action : parent->findChildren<QAction*>())
+	{
+		if (not action->objectName().isEmpty())
+			shortcuts.append({action, action->shortcut(), parent->defaultShortcut(action)});
+	}
+}
+
+/*
+ * Returns the amount of shortcuts.
+ */
+int ShortcutsModel::rowCount(const QModelIndex &) const
+{
+	return shortcuts.size();
+}
+
+/*
+ * Returns the amount of columns.
+ */
+int ShortcutsModel::columnCount(const QModelIndex &) const
+{
+	return 2;
+}
+
+/*
+ * Returns various shortcut data.
+ */
+QVariant ShortcutsModel::data(const QModelIndex &index, int role) const
+{
+	if (not isValidIndex(index))
+		return {};
+
+	Item const& entry = shortcuts[index.row()];
+
+	switch (role)
+	{
+	case Qt::DisplayRole:
+		switch (index.column())
+		{
+		case ActionColumn:
+			return entry.action->text().replace("&", "");
+
+		case KeySequenceColumn:
+			return entry.sequence.toString(QKeySequence::NativeText);
+
+		default:
+			return "";
+		}
+
+	case Qt::DecorationRole:
+		if (index.column() == ActionColumn)
+			return entry.action->icon();
+		else
+			return {};
+
+	case Qt::EditRole:
+		switch (index.column())
+		{
+		case KeySequenceColumn:
+			return QVariant::fromValue(entry.sequence);
+
+		default:
+			return {};
+		}
+
+	case DefaultKeySequenceRole:
+		return entry.defaultSequence;
+
+	default:
+		return {};
+	}
+}
+
+/*
+ * Reimplementation of QAbstractItemModel::flags that supplies the editable flag
+ * for the key sequence cells.
+ */
+Qt::ItemFlags ShortcutsModel::flags(const QModelIndex& index) const
+{
+	Qt::ItemFlags flags = QAbstractItemModel::flags(index);
+
+	if (index.column() == KeySequenceColumn)
+		flags |= Qt::ItemIsEditable;
+
+	return flags;
+}
+
+/*
+ * Returns whether or not the specified row is valid.
+ */
+bool ShortcutsModel::isValidRow(int row) const
+{
+	return row >= 0 and row < shortcuts.size();
+}
+
+/*
+ * Returns whether or not the specified model index is valid.
+ */
+bool ShortcutsModel::isValidIndex(const QModelIndex &index) const
+{
+	return index.column() >= 0 and index.column() < 2 and isValidRow(index.row());
+}
+
+/*
+ * Provides an interface for changing the key sequence.
+ */
+bool ShortcutsModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+	if (isValidIndex(index) and index.column() == KeySequenceColumn and role == Qt::EditRole)
+	{
+		shortcuts[index.row()].sequence = value.value<QKeySequence>();
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/*
+ * Saves shortcuts to the settings object and updates the actions.
+ */
+void ShortcutsModel::saveChanges()
+{
+	for (Item& shortcut : shortcuts)
+	{
+		shortcut.action->setShortcut(shortcut.sequence);
+		QString const key = "shortcut_" + shortcut.action->objectName();
+
+		if (shortcut.defaultSequence != shortcut.sequence)
+			settingsObject().setValue(key, shortcut.sequence);
+		else
+			settingsObject().remove(key);
+	}
+}
+
+/*
+ * Returns an index.
+ */
+QModelIndex ShortcutsModel::index(int row, int column, const QModelIndex& parent) const
+{
+	ignore(parent);
+	return createIndex(row, column);
+}
+
+/*
+ * Returns nothing, because the shortcuts model does not use parents.
+ */
+QModelIndex ShortcutsModel::parent(const QModelIndex &child) const
+{
+	ignore(child);
+	return {};
+}
+
+/*
+ * Returns headers.
+ */
+QVariant ShortcutsModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+	if (role == Qt::DisplayRole and orientation == Qt::Horizontal)
+	{
+		switch (section)
+		{
+		case ActionColumn:
+			return "Action";
+
+		case KeySequenceColumn:
+			return "Shortcut";
+
+		default:
+			return "";
+		}
+	}
+	else
+	{
+		return {};
+	}
+}
+
+/*
+ * Constructs a new key sequence delegate.
+ */
+KeySequenceDelegate::KeySequenceDelegate(QObject *parent) :
+	QStyledItemDelegate {parent} {}
+
+/*
+ * Creates a key sequence editor.
+ */
+QWidget *KeySequenceDelegate::createEditor(
+	QWidget *parent,
+	const QStyleOptionViewItem &option,
+	const QModelIndex &index) const
+{
+	ignore(option, index);
+	const QVariant variant = index.model()->data(index, ShortcutsModel::DefaultKeySequenceRole);
+	const QKeySequence defaultSequence = variant.value<QKeySequence>();
+	ExtendedKeySequenceEditor* editor = new ExtendedKeySequenceEditor {{}, defaultSequence, parent};
+	return editor;
+}
+
+/*
+ * Sets the initial key sequence used in the key sequence editor.
+ */
+void KeySequenceDelegate::setEditorData(QWidget *widget, const QModelIndex &index) const
+{
+	QKeySequence sequence = index.model()->data(index, Qt::EditRole).value<QKeySequence>();
+	ExtendedKeySequenceEditor* editor = static_cast<ExtendedKeySequenceEditor*>(widget);
+	editor->setKeySequence(sequence);
+}
+
+/*
+ * Updates the shortcuts model when the key sequence has been accepted by the user.
+ */
+void KeySequenceDelegate::setModelData(
+	QWidget *widget,
+	QAbstractItemModel *model,
+	const QModelIndex &index) const
+{
+	ExtendedKeySequenceEditor* editor = static_cast<ExtendedKeySequenceEditor*>(widget);
+	model->setData(index, editor->keySequence(), Qt::EditRole);
+}
+
+/*
+ * Updates editor geometry.
+ */
+void KeySequenceDelegate::updateEditorGeometry(
+	QWidget *editor,
+	const QStyleOptionViewItem &option,
+	const QModelIndex &index) const
+{
+	ignore(index);
+	editor->setGeometry(option.rect);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/dialogs/shortcutsmodel.h	Wed Dec 26 23:01:45 2018 +0200
@@ -0,0 +1,89 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2018 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QAbstractItemModel>
+#include <QAction>
+#include <QStyledItemDelegate>
+#include "main.h"
+
+/*
+ * Implements a delegate for editing key sequence cells.
+ */
+class KeySequenceDelegate : public QStyledItemDelegate
+{
+public:
+	KeySequenceDelegate(QObject* parent = nullptr);
+	QWidget* createEditor(
+		QWidget *parent,
+		const QStyleOptionViewItem &option,
+		const QModelIndex &index) const override;
+	void setEditorData(QWidget *widget, const QModelIndex &index) const override;
+	void setModelData(
+		QWidget *widget,
+		QAbstractItemModel *model,
+		const QModelIndex &index) const override;
+	void updateEditorGeometry(
+		QWidget *editor,
+		const QStyleOptionViewItem &option,
+		const QModelIndex &index) const override;
+};
+
+/*
+ * Models a table of shortcuts. Each action in the provided main window is given
+ * a row, which contains editable shortcuts.
+ *
+ * Calling saveChanges updates the actions and updates the settings object.
+ */
+class ShortcutsModel : public QAbstractItemModel
+{
+public:
+	enum Column
+	{
+		ActionColumn,
+		KeySequenceColumn
+	};
+
+	enum
+	{
+		DefaultKeySequenceRole = Qt::UserRole
+	};
+
+	ShortcutsModel(class MainWindow* parent);
+	int rowCount(QModelIndex const&) const override;
+	int columnCount(QModelIndex const&) const override;
+	QVariant data(const QModelIndex &index, int role) const override;
+	Qt::ItemFlags flags(const QModelIndex& index) const override;
+	bool isValidRow(int row) const;
+	bool isValidIndex(const QModelIndex &index) const;
+	bool setData(const QModelIndex &index, const QVariant &value, int role) override;
+	void saveChanges();
+	QModelIndex index(int row, int column, const QModelIndex& parent) const override;
+	QModelIndex parent(const QModelIndex& child) const override;
+	QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+
+private:
+	struct Item
+	{
+		QAction* action;
+		QKeySequence sequence;
+		const QKeySequence defaultSequence;
+	};
+
+	QVector<Item> shortcuts;
+};
--- a/src/mainwindow.cpp	Wed Dec 26 16:38:38 2018 +0200
+++ b/src/mainwindow.cpp	Wed Dec 26 23:01:45 2018 +0200
@@ -757,21 +757,6 @@
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void MainWindow::saveShortcuts()
-{
-	applyToActions([&](QAction* action)
-	{
-		QString const key = "shortcut_" + action->objectName();
-
-		if (m_defaultShortcuts[action] != action->shortcut())
-			settingsObject().setValue(key, action->shortcut());
-		else
-			settingsObject().remove(key);
-	});
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-//
 void MainWindow::applyToActions(function<void(QAction*)> function)
 {
 	for (QAction* act : findChildren<QAction*>())
--- a/src/mainwindow.h	Wed Dec 26 16:38:38 2018 +0200
+++ b/src/mainwindow.h	Wed Dec 26 23:01:45 2018 +0200
@@ -70,7 +70,6 @@
 	void replaceSelection(const QItemSelection& selection);
 	CircularSection circleToolSection() const;
 	bool save (LDDocument* doc, bool saveAs);
-	void saveShortcuts();
 	void select(const QModelIndex& objectIndex);
 	QModelIndexList selectedIndexes() const;
 	QSet<LDObject*> selectedObjects() const;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/extendedkeysequenceeditor.cpp	Wed Dec 26 23:01:45 2018 +0200
@@ -0,0 +1,90 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2018 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QKeySequenceEdit>
+#include <QPushButton>
+#include <QHBoxLayout>
+#include "extendedkeysequenceeditor.h"
+
+/*
+ * Constructs a new extended key sequence editor.
+ */
+ExtendedKeySequenceEditor::ExtendedKeySequenceEditor(
+	const QKeySequence& initialSequence,
+	const QKeySequence& defaultSequence,
+	QWidget* parent) :
+	QWidget {parent},
+	defaultSequence {defaultSequence},
+	editor {new QKeySequenceEdit {initialSequence, this}},
+	clearButton {new QPushButton {"×", this}},
+	resetButton {new QPushButton {"↺", this}},
+	layout {new QHBoxLayout {this}}
+{
+	layout->addWidget(editor, 1);
+	layout->addWidget(clearButton, 0);
+	layout->addWidget(resetButton, 0);
+	layout->setContentsMargins({});
+	setLayout(layout);
+
+	// Set up focus proxies so that the actual editing widget gets focused when focus
+	// is applied to this widget.
+	setFocusProxy(editor);
+	clearButton->setFocusProxy(editor);
+	resetButton->setFocusProxy(editor);
+
+	connect(clearButton, &QPushButton::clicked, editor, &QKeySequenceEdit::clear);
+	connect(resetButton, &QPushButton::clicked, [this]()
+	{
+		editor->setKeySequence(this->defaultSequence);
+	});
+}
+
+/*
+ * Destroys an extended key sequence editor.
+ */
+ExtendedKeySequenceEditor::~ExtendedKeySequenceEditor()
+{
+	delete editor;
+	delete clearButton;
+	delete resetButton;
+	delete layout;
+}
+
+/*
+ * Returns the current key sequence in the editor.
+ */
+QKeySequence ExtendedKeySequenceEditor::keySequence() const
+{
+	return editor->keySequence();
+}
+
+/*
+ * Changes the key sequence in the editor.
+ */
+void ExtendedKeySequenceEditor::setKeySequence(const QKeySequence& newSequence)
+{
+	editor->setKeySequence(newSequence);
+}
+
+/*
+ * Clears the key sequence.
+ */
+void ExtendedKeySequenceEditor::clear()
+{
+	editor->clear();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/widgets/extendedkeysequenceeditor.h	Wed Dec 26 23:01:45 2018 +0200
@@ -0,0 +1,49 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2018 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <QWidget>
+
+/*
+ * A widget which contains not only a key sequence editor, but also buttons for resetting and
+ * clearing the key sequence.
+ */
+class ExtendedKeySequenceEditor : public QWidget
+{
+	Q_OBJECT
+
+public:
+	explicit ExtendedKeySequenceEditor(
+		const QKeySequence& initialSequence = {},
+		const QKeySequence& defaultSequence = {},
+		QWidget *parent = nullptr);
+	~ExtendedKeySequenceEditor();
+
+	QKeySequence keySequence() const;
+
+public slots:
+	void setKeySequence(const QKeySequence& newSequence);
+	void clear();
+
+private:
+	const QKeySequence defaultSequence;
+	class QKeySequenceEdit* editor;
+	class QPushButton* clearButton;
+	class QPushButton* resetButton;
+	class QHBoxLayout* layout;
+};

mercurial