changeset 31696:8fed04d0607c

eliminate shortcut_manager class and revamp shortcut handling * settings-dialog.ui: Use a custom type for the * shortcuts-tree-widget.h, shortcuts-tree-widget.cc: New files. (shortcuts_tree_widget): New class to use for editing and displaying shortcuts in the settings dialog. Adapt constructor from shortcut_manager::fill_treewidget function. (enter_shortcut): Move here from shortcut-manager.h and shortcut-manager.cc. (tree_widget_shortcut_item): New class to use for items in the shortcuts_tree_widget class in place of QTreeWidgetItem. (shortcut_edit_dialog): New class to use for shortcut editing dialog in place of a simple QDialog. Allows for capturing edited values in the dialog object. * gui-preferences-sc.h, gui-preferences-sc.cc (sc_group): Drop trailing "/" from definition and move here from gui-preferences.h. Update code that prepends sc_group to a settings key. (get_shortcut_section): New function. * gui-preferences.h, gui-preferences.cc (sc_pref::def_value, sc_pref::def_text): New functions. (all_shortcut_preferences::value, all_shortcut_preferences::keys): New static funtions. (all_shortcut_preferences::do_value, all_shortcut_preferences::do_keys): New helper functions. * gui-settings.cc (gui_settings::sc_def_value): Simply call sc_pref::def_value. * settings-dialog.h, settings-dialog.cc (class settings_dialog): Eliminate use of base_qobject and shortcut_manager. (settings_dialog::import_shortcut_set): Get file name here. Call shortcuts_tree_widget::import_shortcuts instead of shortcut_manager::import_export. (settings_dialog::export_shortcut_set): Get file name here. Call shortcuts_tree_widget::export_shortcuts instead of shortcut_manager::import_export. (settings_dialog::default_shortcut_set): Check whether to overwrite shortcuts here. Call shortcuts_tree_widget::set_default_shortcuts instead of shortcut_manager::import_export. (settings_dialog::write_changed_settings): Eliminate CLOSING argument. Call shortcuts_tree_widget::write_settings instead of shortcut_manager::write_shortcuts. (settings_dialog::get_shortcuts_file_name): New function to prompt user for file name. (settings_dialog::overwrite_all_shortcuts): New function to ask user whether replacing shortcuts is OK. (import_export_action): Move enum decl here from shortcut-manager.h. * main-window.cc (main_window::process_settings_dialog_request): Eliminate m_octave_qobj in call to settings_dialog ctor. (main_window::main_window): Don't call shortcut_manager::init_data. * terminal-dock-widget.h, terminal-dock-widget.cc (terminal_dock_widget::init_control_d_shortcut_behavior): New function. (terminal_dock_widget::terminal_dock_widget): Use it instead of performing same action in shortcut_manager::init. * octave-qobject.h, octave-qobject.cc (base_qobject::m_shortcut_manager): Delete data member. (base_qobject::get_shortcut_manager): Delete. (base_qobject::base_qobject): Don't call shortcut_manager::init_data. * shortcut-manager.h, shortcut-manager.cc: Delete. Eliminates the now unnecessary shortcut_manager class. * libgui/src/module.mk: Update.
author John W. Eaton <jwe@octave.org>
date Mon, 26 Dec 2022 17:29:59 -0500
parents 5749674b826e
children dd904ce6f53f
files libgui/src/documentation.cc libgui/src/gui-preferences-sc.cc libgui/src/gui-preferences-sc.h libgui/src/gui-preferences.cc libgui/src/gui-preferences.h libgui/src/gui-settings.cc libgui/src/main-window.cc libgui/src/module.mk libgui/src/octave-qobject.cc libgui/src/octave-qobject.h libgui/src/settings-dialog.cc libgui/src/settings-dialog.h libgui/src/settings-dialog.ui libgui/src/shortcut-manager.cc libgui/src/shortcut-manager.h libgui/src/shortcuts-tree-widget.cc libgui/src/shortcuts-tree-widget.h libgui/src/terminal-dock-widget.cc libgui/src/terminal-dock-widget.h
diffstat 19 files changed, 1145 insertions(+), 1015 deletions(-) [+]
line wrap: on
line diff
--- a/libgui/src/documentation.cc	Sun Dec 25 20:37:53 2022 -0500
+++ b/libgui/src/documentation.cc	Mon Dec 26 17:29:59 2022 -0500
@@ -51,6 +51,7 @@
 #include <QTimer>
 #include <QWheelEvent>
 #include <QVBoxLayout>
+#include <QWheelEvent>
 
 #include "documentation.h"
 #include "documentation-bookmarks.h"
--- a/libgui/src/gui-preferences-sc.cc	Sun Dec 25 20:37:53 2022 -0500
+++ b/libgui/src/gui-preferences-sc.cc	Mon Dec 26 17:29:59 2022 -0500
@@ -216,3 +216,13 @@
 sc_pref sc_doc_go_back (QCoreApplication::translate ("shortcuts", "Go Back one Page"), sc_doc + ":go_back", QKeySequence::Back);
 sc_pref sc_doc_go_next (QCoreApplication::translate ("shortcuts", "Go Forward one Page"), sc_doc + ":go_next", QKeySequence::Forward);
 sc_pref sc_doc_bookmark (QCoreApplication::translate ("shortcuts", "Bookmark this Page"), sc_doc + ":bookmark", CTRL + Qt::Key_D);
+
+QString get_shortcut_section (const QString& key)
+{
+  QString section;
+
+  if (key.contains (':'))
+    section = key.section (':', 0, 0, QString::SectionSkipEmpty);
+
+  return section;
+}
--- a/libgui/src/gui-preferences-sc.h	Sun Dec 25 20:37:53 2022 -0500
+++ b/libgui/src/gui-preferences-sc.h	Mon Dec 26 17:29:59 2022 -0500
@@ -27,6 +27,7 @@
 #define octave_gui_preferences_sc_h 1
 
 #include <QSet>
+#include <QString>
 
 #include "gui-preferences.h"
 
@@ -57,8 +58,10 @@
 const Qt::KeyboardModifiers CTRL_SHIFT = CTRL | Qt::ShiftModifier;
 const Qt::KeyboardModifiers CTRL_ALT = CTRL | Qt::AltModifier;
 
+const QString sc_group ("shortcuts");
+
 // Shortcuts not related to specific Menus
- 
+
 // Dock widgets
 const QString sc_dock_widget ("dock_widget");
 extern sc_pref sc_dock_widget_dock;
@@ -270,4 +273,6 @@
 const gui_pref
 sc_prevent_rl_conflicts_menu ("shortcuts/prevent_readline_conflicts_menu", QVariant (false));
 
+extern QString get_shortcut_section (const QString& key);
+
 #endif
--- a/libgui/src/gui-preferences.cc	Sun Dec 25 20:37:53 2022 -0500
+++ b/libgui/src/gui-preferences.cc	Mon Dec 26 17:29:59 2022 -0500
@@ -54,6 +54,23 @@
   all_shortcut_preferences::insert (settings_key, *this);
 }
 
+QKeySequence sc_pref::def_value (void) const
+{
+  QKeySequence key_seq = QKeySequence ();
+
+  if (m_def)
+    key_seq = QKeySequence (m_def);
+  else if (m_def_std != QKeySequence::UnknownKey)
+    key_seq = QKeySequence (m_def_std);
+
+  return key_seq;
+}
+
+QString sc_pref::def_text (void) const
+{
+  return def_value ().toString ();
+}
+
 all_shortcut_preferences *all_shortcut_preferences::s_instance = nullptr;
 
 void all_shortcut_preferences::insert (const QString& settings_key,
@@ -64,12 +81,37 @@
   s_instance->do_insert (settings_key, scpref);
 }
 
+const sc_pref all_shortcut_preferences::value (const QString& settings_key)
+{
+  ensure_instance ();
+
+  return s_instance->do_value (settings_key);
+}
+
+QStringList all_shortcut_preferences::keys (void)
+{
+  ensure_instance ();
+
+  return s_instance->do_keys ();
+}
+
 void all_shortcut_preferences::do_insert (const QString& settings_key,
                                           const sc_pref& scpref)
 {
   m_hash.insert (settings_key, scpref);
 }
 
+const sc_pref
+all_shortcut_preferences::do_value (const QString& settings_key) const
+{
+  return m_hash.value (settings_key);
+}
+
+QStringList all_shortcut_preferences::do_keys (void) const
+{
+  return m_hash.keys ();
+}
+
 void all_shortcut_preferences::ensure_instance (void)
 {
   if (! s_instance)
--- a/libgui/src/gui-preferences.h	Sun Dec 25 20:37:53 2022 -0500
+++ b/libgui/src/gui-preferences.h	Mon Dec 26 17:29:59 2022 -0500
@@ -58,8 +58,6 @@
 // combination of Qt:Keys (resutling in an unsigend int, when added)
 // or as one of the predefined standard key sequences.
 
-const QString sc_group ("shortcuts/");  // group name is handled separately
-
 class sc_pref
 {
 public:
@@ -93,6 +91,10 @@
 
   QKeySequence::StandardKey def_std (void) const { return m_def_std; }
 
+  QKeySequence def_value (void) const;
+
+  QString def_text (void) const;
+
 private:
 
   // Description of the shortcut.
@@ -128,6 +130,10 @@
 
   static void insert (const QString& settings_key, const sc_pref& scpref);
 
+  static const sc_pref value (const QString& settings_key);
+
+  static QStringList keys (void);
+
 private:
 
   // Map from shortcut identifier (settings key) to sc_pref object.
@@ -135,6 +141,10 @@
 
   void do_insert (const QString& settings_key, const sc_pref& scpref);
 
+  const sc_pref do_value (const QString& settings_key) const;
+
+  QStringList do_keys (void) const;
+
   static void ensure_instance (void);
 
   // Map from shortcut identifier (settings key) to sc_pref object.
--- a/libgui/src/gui-settings.cc	Sun Dec 25 20:37:53 2022 -0500
+++ b/libgui/src/gui-settings.cc	Mon Dec 26 17:29:59 2022 -0500
@@ -45,6 +45,7 @@
 
 #include "gui-preferences-cs.h"
 #include "gui-preferences-ed.h"
+#include "gui-preferences-sc.h"
 #include "gui-preferences-global.h"
 #include "gui-settings.h"
 
@@ -122,23 +123,13 @@
 
     // Get the value from the settings where the key sequences are stored
     // as strings
-    return value (sc_group + scpref.settings_key (),
+    return value (sc_group + "/" + scpref.settings_key (),
                   key_seq.toString ()).toString ();
   }
 
   QKeySequence gui_settings::sc_def_value (const sc_pref& scpref) const
   {
-    QKeySequence key_seq = QKeySequence ();
-
-    // Check, which of the elements for the default value in the sc_pref
-    // structure has a valid value and take this as default.  If both
-    // elements are not valid, leave the key sequence empty
-    if (scpref.def ())
-      key_seq = QKeySequence (scpref.def ());
-    else if (scpref.def_std () != QKeySequence::UnknownKey)
-      key_seq = QKeySequence (scpref.def_std ());
-
-    return key_seq;
+    return scpref.def_value ();
   }
 
   void gui_settings::set_shortcut (QAction *action, const sc_pref& scpref,
--- a/libgui/src/main-window.cc	Sun Dec 25 20:37:53 2022 -0500
+++ b/libgui/src/main-window.cc	Mon Dec 26 17:29:59 2022 -0500
@@ -75,7 +75,6 @@
 #include "octave-qobject.h"
 #include "octave-qtutils.h"
 #include "settings-dialog.h"
-#include "shortcut-manager.h"
 #include "welcome-wizard.h"
 
 #include "cmd-edit.h"
@@ -150,13 +149,6 @@
     sys::env::putenv ("TERM", "xterm");
 #endif
 
-    // FIXME: can we do this job when creating the shortcut manager?
-    // A quick look shows that it may require some coordination with the
-    // resource manager.  Startup is complicated, but maybe we can make
-    // it simpler?
-    shortcut_manager& scmgr = m_octave_qobj.get_shortcut_manager ();
-    scmgr.init_data ();
-
     construct_central_widget ();
 
     m_status_bar = new QStatusBar (this);
@@ -875,7 +867,7 @@
         return;
       }
 
-    m_settings_dlg = new settings_dialog (this, m_octave_qobj, desired_tab);
+    m_settings_dlg = new settings_dialog (this, desired_tab);
 
     connect (m_settings_dlg, &settings_dialog::apply_new_settings,
              this, &main_window::request_reload_settings);
--- a/libgui/src/module.mk	Sun Dec 25 20:37:53 2022 -0500
+++ b/libgui/src/module.mk	Mon Dec 26 17:29:59 2022 -0500
@@ -301,7 +301,7 @@
   %reldir%/moc-color-picker.cc \
   %reldir%/moc-tab-bar.cc \
   %reldir%/moc-qt-interpreter-events.cc \
-  %reldir%/moc-shortcut-manager.cc \
+  %reldir%/moc-shortcuts-tree-widget.cc \
   %reldir%/moc-welcome-wizard.cc \
   %reldir%/moc-workspace-model.cc \
   %reldir%/moc-workspace-view.cc \
@@ -383,7 +383,7 @@
   %reldir%/qt-utils.h \
   %reldir%/release-notes.h \
   %reldir%/settings-dialog.h \
-  %reldir%/shortcut-manager.h \
+  %reldir%/shortcuts-tree-widget.h \
   %reldir%/tab-bar.h \
   %reldir%/terminal-dock-widget.h \
   %reldir%/color-picker.h \
@@ -430,7 +430,7 @@
   %reldir%/qt-application.cc \
   %reldir%/release-notes.cc \
   %reldir%/settings-dialog.cc \
-  %reldir%/shortcut-manager.cc \
+  %reldir%/shortcuts-tree-widget.cc \
   %reldir%/tab-bar.cc \
   %reldir%/terminal-dock-widget.cc \
   %reldir%/color-picker.cc \
--- a/libgui/src/octave-qobject.cc	Sun Dec 25 20:37:53 2022 -0500
+++ b/libgui/src/octave-qobject.cc	Mon Dec 26 17:29:59 2022 -0500
@@ -54,7 +54,6 @@
 #include "qt-application.h"
 #include "qt-interpreter-events.h"
 #include "release-notes.h"
-#include "shortcut-manager.h"
 #include "terminal-dock-widget.h"
 #include "variable-editor.h"
 #include "workspace-model.h"
@@ -174,7 +173,6 @@
       m_argc (m_app_context.sys_argc ()),
       m_argv (m_app_context.sys_argv ()),
       m_qapplication (new octave_qapplication (m_argc, m_argv)),
-      m_shortcut_manager (),
       m_qt_tr (new QTranslator ()),
       m_gui_tr (new QTranslator ()),
       m_qsci_tr (new QTranslator ()),
@@ -193,8 +191,7 @@
       m_variable_editor_widget (),
       m_main_window (nullptr)
   {
-    std::string show_gui_msgs =
-      sys::env::getenv ("OCTAVE_SHOW_GUI_MESSAGES");
+    std::string show_gui_msgs = sys::env::getenv ("OCTAVE_SHOW_GUI_MESSAGES");
 
     // Installing our handler suppresses the messages.
 
@@ -210,11 +207,14 @@
 
     qRegisterMetaType<octave_value_list> ("octave_value_list");
 
-// Bug #55940 (Disable App Nap on Mac)
+    // Bug #55940 (Disable App Nap on Mac)
 #if defined (Q_OS_MAC)
     // Mac App Nap feature causes pause() and sleep() to misbehave.
     // Disable it for the entire program run.
     disable_app_nap ();
+
+    // Don't let Qt interpret CMD key ("Meta" in Qt terminology) as Ctrl.
+    QCoreApplication::setAttribute (Qt::AA_MacDontSwapCtrlAndMeta, true);
 #endif
 
     // Force left-to-right alignment (see bug #46204)
@@ -313,9 +313,6 @@
 
             settings.config_icon_theme ();
 
-            // Initilize the shortcut-manager
-            m_shortcut_manager.init_data ();
-
             m_qapplication->setQuitOnLastWindowClosed (false);
           }
       }
--- a/libgui/src/octave-qobject.h	Sun Dec 25 20:37:53 2022 -0500
+++ b/libgui/src/octave-qobject.h	Mon Dec 26 17:29:59 2022 -0500
@@ -36,7 +36,6 @@
 #include <QStringList>
 
 #include "interpreter-qobject.h"
-#include "shortcut-manager.h"
 
 OCTAVE_BEGIN_NAMESPACE(octave)
 
@@ -136,11 +135,6 @@
     return m_main_window;
   }
 
-  shortcut_manager& get_shortcut_manager (void)
-  {
-    return m_shortcut_manager;
-  }
-
   std::shared_ptr<qt_interpreter_events> get_qt_interpreter_events (void)
   {
     return m_qt_interpreter_events;
@@ -263,8 +257,6 @@
 
   octave_qapplication *m_qapplication;
 
-  shortcut_manager m_shortcut_manager;
-
   QTranslator *m_qt_tr;
   QTranslator *m_gui_tr;
   QTranslator *m_qsci_tr;
--- a/libgui/src/settings-dialog.cc	Sun Dec 25 20:37:53 2022 -0500
+++ b/libgui/src/settings-dialog.cc	Mon Dec 26 17:29:59 2022 -0500
@@ -65,17 +65,16 @@
 
 #include "gui-preferences-all.h"
 #include "gui-settings.h"
-#include "octave-qobject.h"
 #include "octave-qtutils.h"
 #include "settings-dialog.h"
+#include "shortcuts-tree-widget.h"
 #include "variable-editor.h"
 #include "workspace-model.h"
 
 OCTAVE_BEGIN_NAMESPACE(octave)
 
-  settings_dialog::settings_dialog (QWidget *p, base_qobject& oct_qobj,
-                                    const QString& desired_tab)
-    : QDialog (p), Ui::settings_dialog (), m_octave_qobj (oct_qobj)
+  settings_dialog::settings_dialog (QWidget *p, const QString& desired_tab)
+    : QDialog (p), Ui::settings_dialog ()
   {
     setupUi (this);
 
@@ -421,8 +420,6 @@
 
     // shortcuts
 
-    shortcut_manager& scmgr = m_octave_qobj.get_shortcut_manager ();
-
     cb_prevent_readline_conflicts->setChecked (
           settings.value (sc_prevent_rl_conflicts.key,
                            sc_prevent_rl_conflicts.def).toBool ());
@@ -430,10 +427,9 @@
           settings.value (sc_prevent_rl_conflicts_menu.key,
                            sc_prevent_rl_conflicts_menu.def).toBool ());
 
-    // initialize the tree view with all shortcut data
-    scmgr.fill_treewidget (shortcuts_treewidget);
+    // connect the buttons for import/export of the shortcut sets
+    // FIXME: Should there also be a button to discard changes?
 
-    // connect the buttons for import/export of the shortcut sets
     connect (btn_import_shortcut_set, &QPushButton::clicked,
              this, &settings_dialog::import_shortcut_set);
 
@@ -553,7 +549,7 @@
     if (button_role == QDialogButtonBox::ApplyRole
         || button_role == QDialogButtonBox::AcceptRole)
       {
-        write_changed_settings (button_role == QDialogButtonBox::AcceptRole);
+        write_changed_settings ();
         emit apply_new_settings ();
       }
 
@@ -616,25 +612,58 @@
 
   // slots for import/export of shortcut sets
 
+  // Prompt for file name and import shortcuts from it.  Importing will
+  // change values in tree view but does not apply values to
+  // gui_settings_object so that the user may choose to apply or cancel
+  // the action.
+
   void settings_dialog::import_shortcut_set (void)
   {
-    shortcut_manager& scmgr = m_octave_qobj.get_shortcut_manager ();
+    if (! overwrite_all_shortcuts ())
+      return;
+
+    QString file = get_shortcuts_file_name (OSC_IMPORT);
+
+    gui_settings osc_settings (file, QSettings::IniFormat);
 
-    scmgr.import_export (shortcut_manager::OSC_IMPORT);
+    if (osc_settings.status () ==  QSettings::NoError)
+      shortcuts_treewidget->import_shortcuts (osc_settings);
+    else
+      qWarning () << (tr ("Failed to open %1 as Octave shortcut file")
+                      .arg (file));
   }
 
+  // Prompt for file name and export shortcuts to it.
+
+  // FIXME: Should exported settings values come from the gui_settings
+  // object or the tree view?  If modified values in the tree view have
+  // not been applied, should we offer to apply them first?  Offer a
+  // choice to save current application settings or the modified values
+  // in the dialog?
+
   void settings_dialog::export_shortcut_set (void)
   {
-    shortcut_manager& scmgr = m_octave_qobj.get_shortcut_manager ();
+    QString file = get_shortcuts_file_name (OSC_EXPORT);
+
+    gui_settings osc_settings (file, QSettings::IniFormat);
 
-    scmgr.import_export (shortcut_manager::OSC_EXPORT);
+    if (osc_settings.status () ==  QSettings::NoError)
+      shortcuts_treewidget->export_shortcuts (osc_settings);
+    else
+      qWarning () << (tr ("Failed to open %1 as Octave shortcut file")
+                      .arg (file));
   }
 
+  // Reset the tree view to default values.  Does not apply values to
+  // gui_settings object so that the user may choose to apply or cancel
+  // the action.
+
   void settings_dialog::default_shortcut_set (void)
   {
-    shortcut_manager& scmgr = m_octave_qobj.get_shortcut_manager ();
+    if (! overwrite_all_shortcuts ())
+      return;
 
-    scmgr.import_export (shortcut_manager::OSC_DEFAULT);
+    shortcuts_treewidget->set_default_shortcuts ();
   }
 
   void settings_dialog::update_editor_lexers (int def)
@@ -1017,7 +1046,7 @@
 
 #endif
 
-  void settings_dialog::write_changed_settings (bool closing)
+  void settings_dialog::write_changed_settings (void)
   {
     gui_settings settings;
 
@@ -1241,8 +1270,7 @@
     settings.setValue (sc_prevent_rl_conflicts.key, cb_prevent_readline_conflicts->isChecked ());
     settings.setValue (sc_prevent_rl_conflicts_menu.key, cb_prevent_readline_conflicts_menu->isChecked ());
 
-    shortcut_manager& scmgr = m_octave_qobj.get_shortcut_manager ();
-    scmgr.write_shortcuts (settings, closing);
+    shortcuts_treewidget->write_settings ();
 
     settings.sync ();
   }
@@ -1610,4 +1638,90 @@
     settings.sync ();
   }
 
+  QString settings_dialog::get_shortcuts_file_name (import_export_action action)
+  {
+    QString file;
+
+    // FIXME: Remove, if for all common KDE versions (bug #54607) is resolved.
+    int opts = 0;  // No options by default.
+
+    gui_settings settings;
+
+    if (! settings.value (global_use_native_dialogs).toBool ())
+      opts = QFileDialog::DontUseNativeDialog;
+
+    if (action == OSC_IMPORT)
+      file = QFileDialog::getOpenFileName
+        (this, tr ("Import shortcuts from file..."), QString (),
+         tr ("Octave Shortcut Files (*.osc);;All Files (*)"),
+         nullptr, QFileDialog::Option (opts));
+
+    else
+      file = QFileDialog::getSaveFileName
+        (this, tr ("Export shortcuts to file..."), QString (),
+         tr ("Octave Shortcut Files (*.osc);;All Files (*)"),
+         nullptr, QFileDialog::Option (opts));
+
+    return file;
+  }
+
+  // Ask whether to overwrite current shortcuts with settings from an
+  // imported file.  Optionally allow current shortcuts to be saved to a
+  // file.
+
+  // FIXME: If the tree view contains changes that have not yet been
+  //        saved to the application settings object, should we
+  //
+  //   * allow the user to choose whether to
+  //     - cancel the operation (X)
+  //     - save the modified settings (X)
+  //     - save the current application settings (XX)
+  //
+  //   * unconditionally display an error dialog and cancel the
+  //     export operation
+  //
+  //   (X) - already an option, but not based on whether the tree view
+  //         contains unsaved changes
+  //   (XX) - already possible (cancel operation, cancel settings
+  //          dialog, re-open settings dialog and export changes).
+
+  bool settings_dialog::overwrite_all_shortcuts (void)
+  {
+    QMessageBox msg_box;
+
+    msg_box.setWindowTitle (tr ("Overwriting Shortcuts"));
+    msg_box.setIcon (QMessageBox::Warning);
+    msg_box.setText (tr ("You are about to overwrite all shortcuts.\n"
+                         "Would you like to save the current shortcut set or cancel the action?"));
+    msg_box.setStandardButtons (QMessageBox::Save | QMessageBox::Cancel);
+
+    QPushButton *discard
+      = msg_box.addButton (tr ("Don't save"), QMessageBox::DestructiveRole);
+
+    msg_box.setDefaultButton (QMessageBox::Save);
+
+    int ret = msg_box.exec ();
+
+    if (msg_box.clickedButton () == discard)
+      return true;
+
+    if (ret == QMessageBox::Save)
+      {
+        QString file = get_shortcuts_file_name (OSC_EXPORT);
+
+        gui_settings osc_settings (file, QSettings::IniFormat);
+
+        if (osc_settings.status () ==  QSettings::NoError)
+          {
+            shortcuts_treewidget->export_shortcuts (osc_settings);
+            return true;
+          }
+        else
+          qWarning () << (tr ("Failed to open %1 as Octave shortcut file")
+                          .arg (file));
+      }
+
+    return false;
+  }
+
 OCTAVE_END_NAMESPACE(octave)
--- a/libgui/src/settings-dialog.h	Sun Dec 25 20:37:53 2022 -0500
+++ b/libgui/src/settings-dialog.h	Mon Dec 26 17:29:59 2022 -0500
@@ -39,15 +39,15 @@
 
 OCTAVE_BEGIN_NAMESPACE(octave)
 
-  class base_qobject;
-
   // Ui::settings_dialog is a generated class.
 
   class settings_dialog : public QDialog, private Ui::settings_dialog
   {
-  Q_OBJECT public:
+    Q_OBJECT
 
-    explicit settings_dialog (QWidget *parent, base_qobject& octave_qobj,
+  public:
+
+    explicit settings_dialog (QWidget *parent,
                               const QString& desired_tab = QString ());
 
     ~settings_dialog (void) = default;
@@ -82,13 +82,19 @@
 
   private:
 
+    enum import_export_action
+    {
+      OSC_IMPORT,
+      OSC_EXPORT
+    };
+
 #if defined (HAVE_QSCINTILLA)
     void update_lexer (QsciLexer *lexer, int mode, int def = 0);
     void get_lexer_settings (QsciLexer *lexer);
     void write_lexer_settings (QsciLexer *lexer);
 #endif
 
-    void write_changed_settings (bool closing);
+    void write_changed_settings (void);
 
     void read_workspace_colors (void);
     void write_workspace_colors (void);
@@ -99,7 +105,9 @@
     void read_varedit_colors (void);
     void write_varedit_colors (void);
 
-    base_qobject& m_octave_qobj;
+    QString get_shortcuts_file_name (import_export_action action);
+
+    bool overwrite_all_shortcuts (void);
 
     color_picker *m_widget_title_bg_color;
     color_picker *m_widget_title_bg_color_active;
--- a/libgui/src/settings-dialog.ui	Sun Dec 25 20:37:53 2022 -0500
+++ b/libgui/src/settings-dialog.ui	Mon Dec 26 17:29:59 2022 -0500
@@ -2681,7 +2681,7 @@
                 <number>0</number>
                </property>
                <item>
-                <widget class="QTreeWidget" name="shortcuts_treewidget">
+                <widget class="octave::shortcuts_tree_widget" name="shortcuts_treewidget">
                  <property name="sizePolicy">
                   <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
                    <horstretch>0</horstretch>
@@ -2959,6 +2959,13 @@
    </item>
   </layout>
  </widget>
+ <customwidgets>
+  <customwidget>
+   <class>octave::shortcuts_tree_widget</class>
+   <extends>QTreeWidget</extends>
+   <header>shortcuts-tree-widget.h</header>
+  </customwidget>
+ </customwidgets>
  <resources/>
  <connections>
   <connection>
--- a/libgui/src/shortcut-manager.cc	Sun Dec 25 20:37:53 2022 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,778 +0,0 @@
-////////////////////////////////////////////////////////////////////////
-//
-// Copyright (C) 2014-2022 The Octave Project Developers
-//
-// See the file COPYRIGHT.md in the top-level directory of this
-// distribution or <https://octave.org/copyright/>.
-//
-// This file is part of Octave.
-//
-// Octave 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.
-//
-// Octave 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 Octave; see the file COPYING.  If not, see
-// <https://www.gnu.org/licenses/>.
-//
-////////////////////////////////////////////////////////////////////////
-
-#if defined (HAVE_CONFIG_H)
-#  include "config.h"
-#endif
-
-#include <QAction>
-#include <QApplication>
-#include <QCheckBox>
-#include <QDialogButtonBox>
-#include <QFileDialog>
-#include <QGridLayout>
-#include <QHeaderView>
-#include <QKeySequence>
-#include <QLineEdit>
-#include <QMessageBox>
-#include <QPushButton>
-#include <QVBoxLayout>
-#include <QtCore>
-
-#include "shortcut-manager.h"
-#include "gui-preferences-global.h"
-#include "gui-preferences-sc.h"
-#include "gui-settings.h"
-#include "error.h"
-
-OCTAVE_BEGIN_NAMESPACE(octave)
-
-  // enter_shortcut:
-  // class derived from QLineEdit for directly entering key sequences which
-
-  enter_shortcut::enter_shortcut (QWidget *p) : QLineEdit (p)
-  {
-    m_direct_shortcut = true;      // the shortcut is directly entered
-    m_shift_modifier = false;      // the shift modifier is not added
-  }
-
-  // new keyPressEvent
-  void enter_shortcut::keyPressEvent (QKeyEvent *e)
-  {
-    if (! m_direct_shortcut)
-      {
-        QLineEdit::keyPressEvent (e);
-        return;
-      }
-
-    if (e->type () == QEvent::KeyPress)
-      {
-        int key = e->key ();
-
-        if (key == Qt::Key_unknown || key == 0)
-          return;
-
-        Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers (); //e->modifiers ();
-
-        if (m_shift_modifier || (modifiers & Qt::ShiftModifier))
-          key += Qt::SHIFT;
-        if (modifiers & Qt::ControlModifier)
-          key += Qt::CTRL;
-        if (modifiers & Qt::AltModifier)
-          key += Qt::ALT;
-        if (modifiers & Qt::MetaModifier)
-          key += Qt::META;
-
-        setText (QKeySequence (key).toString ());
-      }
-  }
-
-  // slot for checkbox whether the shortcut is directly entered or not
-  void enter_shortcut::handle_direct_shortcut (int state)
-  {
-    if (state)
-      m_direct_shortcut = true;  // the shortcut is directly entered
-    else
-      m_direct_shortcut = false; // the shortcut has to be written as text
-  }
-
-  // slot for checkbox whether the shift modifier should be added
-  void enter_shortcut::handle_shift_modifier (int state)
-  {
-    if (state)
-      m_shift_modifier = true;  // the shortcut is directly entered
-    else
-      m_shift_modifier = false; // the shortcut has to be written as text
-  }
-
-  shortcut_manager::shortcut_manager (void)
-  {
-    setObjectName ("Shortcut_Manager");
-
-    // Mac: don't let Qt interpret CMD key ("Meta" in Qt terminology) as Ctrl
-#if defined (Q_OS_MAC)
-    QCoreApplication::setAttribute (Qt::AA_MacDontSwapCtrlAndMeta, true);
-#endif
-  }
-
-  void shortcut_manager::init_data (void)
-  {
-    gui_settings settings;
-
-    settings.setValue (sc_main_ctrld.key, false); // reset use fo ctrl-d
-
-    // actions not related to specific menus or widgets
-
-    // dock widgets
-    init (tr ("Undock/Dock Widget"), sc_dock_widget_dock);
-    init (tr ("Close Widget"), sc_dock_widget_close);
-
-    // actions of the main window
-
-    // file
-    init (tr ("New File"), sc_main_file_new_file);
-    init (tr ("New Function"), sc_main_file_new_function);
-    init (tr ("New Figure"), sc_main_file_new_figure);
-    init (tr ("Open File"), sc_main_file_open_file);
-    init (tr ("Load Workspace"), sc_main_file_load_workspace);
-    init (tr ("Save Workspace As"), sc_main_file_save_workspace);
-    init (tr ("Exit Octave"), sc_main_file_exit);
-
-    // edit
-    init (tr ("Copy"), sc_main_edit_copy);
-    init (tr ("Paste"), sc_main_edit_paste);
-    init (tr ("Undo"), sc_main_edit_undo);
-    init (tr ("Select All"), sc_main_edit_select_all);
-    init (tr ("Clear Clipboard"), sc_main_edit_clear_clipboard);
-    init (tr ("Find in Files"), sc_main_edit_find_in_files);
-    init (tr ("Clear Command Window"), sc_main_edit_clear_command_window);
-    init (tr ("Clear Command History"), sc_main_edit_clear_history);
-    init (tr ("Clear Workspace"), sc_main_edit_clear_workspace);
-    init (tr ("Set Path"), sc_main_edit_set_path);
-    init (tr ("Preferences"), sc_main_edit_preferences);
-
-    // debug
-    init (tr ("Step"), sc_main_debug_step_over);
-    init (tr ("Step Into"), sc_main_debug_step_into);
-    init (tr ("Step Out"), sc_main_debug_step_out);
-    init (tr ("Continue"), sc_main_debug_continue);
-    init (tr ("Quit Debug Mode"), sc_main_debug_quit);
-
-    // tools
-    init (tr ("Start/Stop Profiler Session"), sc_main_tools_start_profiler);
-    init (tr ("Resume Profiler Session"), sc_main_tools_resume_profiler);
-    init (tr ("Show Profile Data"), sc_main_tools_show_profiler);
-
-    // window
-    init (tr ("Show Command Window"), sc_main_window_show_command);
-    init (tr ("Show Command History"), sc_main_window_show_history);
-    init (tr ("Show File Browser"), sc_main_window_show_file_browser);
-    init (tr ("Show Workspace"), sc_main_window_show_workspace);
-    init (tr ("Show Editor"), sc_main_window_show_editor);
-    init (tr ("Show Documentation"), sc_main_window_show_doc);
-    init (tr ("Show Variable Editor"), sc_main_window_show_variable_editor);
-    init (tr ("Command Window"), sc_main_window_command);
-    init (tr ("Command History"), sc_main_window_history);
-    init (tr ("File Browser"), sc_main_window_file_browser);
-    init (tr ("Workspace"), sc_main_window_workspace);
-    init (tr ("Editor"), sc_main_window_editor);
-    init (tr ("Documentation"), sc_main_window_doc);
-    init (tr ("Variable Editor"), sc_main_window_variable_editor);
-    init (tr ("Previous Widget"), sc_main_window_previous_dock);
-    init (tr ("Reset Default Window Layout"), sc_main_window_reset);
-
-    // help
-    init (tr ("Show On-disk Documentation"), sc_main_help_ondisk_doc);
-    init (tr ("Show Online Documentation"), sc_main_help_online_doc);
-    init (tr ("Report Bug"), sc_main_help_report_bug);
-    init (tr ("Octave Packages"), sc_main_help_packages);
-    init (tr ("Contribute to Octave"), sc_main_help_contribute);
-    init (tr ("Octave Developer Resources"), sc_main_help_developer);
-    init (tr ("About Octave"), sc_main_help_about);
-
-    // news
-    init (tr ("Release Notes"), sc_main_news_release_notes);
-    init (tr ("Community News"), sc_main_news_community_news);
-
-    // Tab handling
-    // The following shortcuts are moved into a separate tab.  The key names
-    // are not changed, to preserve compatibility with older versions.
-    init (tr ("Close Tab"), sc_edit_file_close);
-    init (tr ("Close All Tabs"), sc_edit_file_close_all);
-    init (tr ("Close Other Tabs"), sc_edit_file_close_other);
-    init (tr ("Switch to Left Tab"), sc_edit_tabs_switch_left_tab);
-    init (tr ("Switch to Right Tab"), sc_edit_tabs_switch_right_tab);
-    init (tr ("Move Tab Left"), sc_edit_tabs_move_tab_left);
-    init (tr ("Move Tab Right"), sc_edit_tabs_move_tab_right);
-
-    // Zooming
-    init (tr ("Zoom In"), sc_edit_view_zoom_in);
-    init (tr ("Zoom Out"), sc_edit_view_zoom_out);
-#if defined (Q_OS_MAC)
-    init (tr ("Zoom Normal"), sc_edit_view_zoom_normal);
-#else
-    init (tr ("Zoom Normal"), sc_edit_view_zoom_normal);
-#endif
-
-    // actions of the editor
-
-    // file
-    init (tr ("Edit Function"), sc_edit_file_edit_function);
-    init (tr ("Save File"), sc_edit_file_save);
-    init (tr ("Save File As"), sc_edit_file_save_as);
-    init (tr ("Print"), sc_edit_file_print);
-
-    // edit
-    init (tr ("Redo"), sc_edit_edit_redo);
-    init (tr ("Cut"), sc_edit_edit_cut);
-    init (tr ("Find and Replace"), sc_edit_edit_find_replace);
-    init (tr ("Find Next"), sc_edit_edit_find_next);
-    init (tr ("Find Previous"), sc_edit_edit_find_previous);
-    init (tr ("Delete to Start of Word"), sc_edit_edit_delete_start_word);
-    init (tr ("Delete to End of Word"), sc_edit_edit_delete_end_word);
-    init (tr ("Delete to Start of Line"), sc_edit_edit_delete_start_line);
-    init (tr ("Delete to End of Line"), sc_edit_edit_delete_end_line);
-    init (tr ("Delete Line"), sc_edit_edit_delete_line);
-    init (tr ("Copy Line"), sc_edit_edit_copy_line);
-    init (tr ("Cut Line"), sc_edit_edit_cut_line);
-    init (tr ("Duplicate Selection/Line"), sc_edit_edit_duplicate_selection);
-    init (tr ("Transpose Line"), sc_edit_edit_transpose_line);
-    init (tr ("Show Completion List"), sc_edit_edit_completion_list);
-
-    init (tr ("Comment Selection"), sc_edit_edit_comment_selection);
-    init (tr ("Uncomment Selection"), sc_edit_edit_uncomment_selection);
-    init (tr ("Comment Selection (Choosing String)"), sc_edit_edit_comment_var_selection);
-    init (tr ("Uppercase Selection"), sc_edit_edit_upper_case);
-    init (tr ("Lowercase Selection"), sc_edit_edit_lower_case);
-
-#if defined (Q_OS_MAC)
-    init (tr ("Indent Selection Rigidly"), sc_edit_edit_indent_selection);
-    init (tr ("Unindent Selection Rigidly"), sc_edit_edit_unindent_selection);
-#else
-    init (tr ("Indent Selection Rigidly"), sc_edit_edit_indent_selection);
-    init (tr ("Unindent Selection Rigidly"), sc_edit_edit_unindent_selection);
-#endif
-    init (tr ("Indent Code"), sc_edit_edit_smart_indent_line_or_selection);
-
-    init (tr ("Convert Line Endings to Windows"), sc_edit_edit_conv_eol_winows);
-    init (tr ("Convert Line Endings to Unix"), sc_edit_edit_conv_eol_unix);
-    init (tr ("Convert Line Endings to Mac"), sc_edit_edit_conv_eol_mac);
-
-    init (tr ("Goto Line"), sc_edit_edit_goto_line);
-    init (tr ("Move to Matching Brace"), sc_edit_edit_move_to_brace);
-    init (tr ("Select to Matching Brace"), sc_edit_edit_select_to_brace);
-    init (tr ("Toggle Bookmark"), sc_edit_edit_toggle_bookmark);
-    init (tr ("Next Bookmark"), sc_edit_edit_next_bookmark);
-    init (tr ("Previous Bookmark"), sc_edit_edit_previous_bookmark);
-    init (tr ("Remove All Bookmark"), sc_edit_edit_remove_bookmark);
-
-    init (tr ("Preferences"), sc_edit_edit_preferences);
-    init (tr ("Styles Preferences"), sc_edit_edit_styles_preferences);
-
-    // view
-    init (tr ("Show Line Numbers"), sc_edit_view_show_line_numbers);
-    init (tr ("Show Whitespace Characters"), sc_edit_view_show_white_spaces);
-    init (tr ("Show Line Endings"), sc_edit_view_show_eol_chars);
-    init (tr ("Show Indentation Guides"), sc_edit_view_show_ind_guides);
-    init (tr ("Show Long Line Marker"), sc_edit_view_show_long_line);
-    init (tr ("Show Toolbar"), sc_edit_view_show_toolbar);
-    init (tr ("Show Statusbar"), sc_edit_view_show_statusbar);
-    init (tr ("Show Horizontal Scrollbar"), sc_edit_view_show_hscrollbar);
-    init (tr ("Sort Tabs Alphabetically"), sc_edit_view_sort_tabs);
-
-    // debug
-    init (tr ("Toggle Breakpoint"), sc_edit_debug_toggle_breakpoint);
-    init (tr ("Next Breakpoint"), sc_edit_debug_next_breakpoint);
-    init (tr ("Previous Breakpoint"), sc_edit_debug_previous_breakpoint);
-    init (tr ("Remove All Breakpoints"), sc_edit_debug_remove_breakpoints);
-
-    // run
-    init (tr ("Run File"), sc_edit_run_run_file);
-    init (tr ("Run Selection"), sc_edit_run_run_selection);
-
-    // help
-    init (tr ("Help on Keyword"), sc_edit_help_help_keyword);
-    init (tr ("Document on Keyword"), sc_edit_help_doc_keyword);
-
-    // Documentation browser
-    init (tr ("Go to Homepage"), sc_doc_go_home);
-    init (tr ("Go Back one Page"), sc_doc_go_back);
-    init (tr ("Go Forward one Page"), sc_doc_go_next);
-    init (tr ("Bookmark this Page"), sc_doc_bookmark);
-  }
-
-  // write one or all actual shortcut set(s) into a settings file
-  void shortcut_manager::write_shortcuts (gui_settings& settings,
-                                          bool closing)
-  {
-    bool sc_ctrld = false;
-
-    QString sc_main = sc_main_file.mid (0, sc_main_file.indexOf ('_') + 1);
-
-    for (int i = 0; i < m_sc.count (); i++)  // loop over all shortcuts
-      {
-        settings.setValue (sc_group + "/" + m_sc.at (i).m_settings_key,
-                            m_sc.at (i).m_actual_sc.toString ());
-        // special: check main-window for Ctrl-D (Terminal)
-        if (m_sc.at (i).m_settings_key.startsWith (sc_main)
-            && m_sc.at (i).m_actual_sc == QKeySequence (Qt::ControlModifier+Qt::Key_D))
-          sc_ctrld = true;
-      }
-
-    settings.setValue (sc_main_ctrld.key, sc_ctrld);
-
-    if (closing)
-      {
-        delete m_dialog;     // the dialog for key sequences can be removed now
-        m_dialog = nullptr;  // make sure it is zero again
-      }
-
-    settings.sync ();      // sync the settings file
-  }
-
-  void shortcut_manager::fill_treewidget (QTreeWidget *tree_view)
-  {
-    m_dialog = nullptr;
-
-    QHash <QString, QTreeWidgetItem *> level_hash;
-
-    tree_view->header ()->setSectionResizeMode (QHeaderView::ResizeToContents);
-
-    QTreeWidgetItem *main = new QTreeWidgetItem (tree_view);
-    main->setText (0, tr ("Global"));
-    main->setExpanded (true);
-    QTreeWidgetItem *main_file = new QTreeWidgetItem (main);
-    main_file->setText (0, tr ("File Menu"));
-    QTreeWidgetItem *main_edit = new QTreeWidgetItem (main);
-    main_edit->setText (0, tr ("Edit Menu"));
-    QTreeWidgetItem *main_debug = new QTreeWidgetItem (main);
-    main_debug->setText (0, tr ("Debug Menu"));
-    QTreeWidgetItem *main_tools = new QTreeWidgetItem (main);
-    main_tools->setText (0, tr ("Tools Menu"));
-    QTreeWidgetItem *main_window = new QTreeWidgetItem (main);
-    main_window->setText (0, tr ("Window Menu"));
-    QTreeWidgetItem *main_help = new QTreeWidgetItem (main);
-    main_help->setText (0, tr ("Help Menu"));
-    QTreeWidgetItem *main_news = new QTreeWidgetItem (main);
-    main_news->setText (0, tr ("News Menu"));
-    QTreeWidgetItem *main_dock_widgets = new QTreeWidgetItem (main);
-    main_dock_widgets->setText (0, tr ("Handling of Dock Widgets"));
-    QTreeWidgetItem *main_tabs = new QTreeWidgetItem (main);
-    main_tabs->setText (0, tr ("Tab Handling in Dock Widgets"));
-    QTreeWidgetItem *main_find = new QTreeWidgetItem (main);
-    main_find->setText (0, tr ("Find & Replace in Dock Widgets"));
-    QTreeWidgetItem *main_zoom = new QTreeWidgetItem (main);
-    main_zoom->setText (0, tr ("Zooming in Editor and Documentation"));
-
-    level_hash[sc_main_file] = main_file;
-    level_hash[sc_main_edit] = main_edit;
-    level_hash[sc_main_debug] = main_debug;
-    level_hash[sc_main_tools] = main_tools;
-    level_hash[sc_main_window] = main_window;
-    level_hash[sc_main_help] = main_help;
-    level_hash[sc_main_news] = main_news;
-    level_hash[sc_dock_widget] = main_dock_widgets;
-    level_hash[sc_edit_tabs] = main_tabs;
-    level_hash[sc_edit_find] = main_find;
-    level_hash[sc_edit_zoom] = main_zoom;
-
-    QTreeWidgetItem *editor = new QTreeWidgetItem (tree_view);
-    editor->setText (0, tr ("Editor"));
-    editor->setExpanded (true);
-    QTreeWidgetItem *editor_file = new QTreeWidgetItem (editor);
-    editor_file->setText (0, tr ("File Menu"));
-    QTreeWidgetItem *editor_edit = new QTreeWidgetItem (editor);
-    editor_edit->setText (0, tr ("Edit Menu"));
-    QTreeWidgetItem *editor_view = new QTreeWidgetItem (editor);
-    editor_view->setText (0, tr ("View Menu"));
-    QTreeWidgetItem *editor_debug = new QTreeWidgetItem (editor);
-    editor_debug->setText (0, tr ("Debug Menu"));
-    QTreeWidgetItem *editor_run = new QTreeWidgetItem (editor);
-    editor_run->setText (0, tr ("Run Menu"));
-    QTreeWidgetItem *editor_help = new QTreeWidgetItem (editor);
-    editor_help->setText (0, tr ("Help Menu"));
-
-    level_hash[sc_edit_file] = editor_file;
-    level_hash[sc_edit_edit] = editor_edit;
-    level_hash[sc_edit_view] = editor_view;
-    level_hash[sc_edit_debug] = editor_debug;
-    level_hash[sc_edit_run] = editor_run;
-    level_hash[sc_edit_help] = editor_help;
-
-    QTreeWidgetItem *doc = new QTreeWidgetItem (tree_view);
-    doc->setText (0, tr ("Documentation Viewer"));
-    doc->setExpanded (true);
-
-    QTreeWidgetItem *doc_browser = new QTreeWidgetItem (doc);
-    doc_browser->setText (0, tr ("Browser"));
-
-    level_hash[sc_doc] = doc_browser;
-
-    connect (tree_view, &QTreeWidget::itemDoubleClicked,
-             this, &shortcut_manager::handle_double_clicked);
-
-    for (int i = 0; i < m_sc.count (); i++)
-      {
-        shortcut_t sc = m_sc.at (i);
-
-        QTreeWidgetItem *section = level_hash[sc.m_settings_key.section (':', 0, 0)];
-
-        // handle sections which have changed and do not correspond to the
-        // previously defined keyname
-        if (section == editor_file)
-          {
-            // Closing tabs now in global tab handling section
-            if (sc.m_settings_key.contains (sc_edit_file_cl))
-              section = main_tabs;
-          }
-        if (section == editor_edit)
-          {
-            // Find & replace now in global file & replace handling section
-            if (sc.m_settings_key.contains (sc_edit_edit_find))
-              section = main_find;
-          }
-        if (section == editor_view)
-          {
-            // Zooming now in global zoom handling section
-            if (sc.m_settings_key.contains (sc_edit_view_zoom))
-              section = main_zoom;
-          }
-
-        QTreeWidgetItem *tree_item = new QTreeWidgetItem (section);
-
-        // set a slightly transparent foreground for default columns
-        QColor fg = QColor (tree_item->foreground (1).color ());
-        fg.setAlpha (128);
-        tree_item->setForeground (1, QBrush (fg));
-
-        // write the shortcuts
-        tree_item->setText (0, sc.m_description);
-        tree_item->setText (1, sc.m_default_sc.toString ());
-        tree_item->setText (2, sc.m_actual_sc.toString ());
-
-        m_item_index_hash[tree_item] = i + 1; // index+1 to avoid 0
-        m_index_item_hash[i] = tree_item;
-      }
-  }
-
-  // import or export of shortcut sets,
-  // called from settings dialog when related buttons are clicked;
-  // returns true on success, false otherwise
-  bool
-  shortcut_manager::import_export (int action)
-  {
-    // ask to save the current shortcuts, maybe abort import
-    if (action == OSC_DEFAULT || action == OSC_IMPORT)
-      {
-        if (! overwrite_all_shortcuts ())
-          return false;
-      }
-
-    // get the filename to read or write the shortcuts,
-    // the default extension is .osc (octave shortcuts)
-    if (action != OSC_DEFAULT)
-      {
-        QString file;
-
-        // FIXME: Remove, if for all common KDE versions (bug #54607) is resolved.
-        int opts = 0;  // No options by default.
-
-        gui_settings settings;
-
-        if (! settings.value (global_use_native_dialogs).toBool ())
-          opts = QFileDialog::DontUseNativeDialog;
-
-        if (action == OSC_IMPORT)
-          file = QFileDialog::getOpenFileName (this,
-                                               tr ("Import shortcuts from file..."), QString (),
-                                               tr ("Octave Shortcut Files (*.osc);;All Files (*)"),
-                                               nullptr, QFileDialog::Option (opts));
-        else if (action == OSC_EXPORT)
-          file = QFileDialog::getSaveFileName (this,
-                                               tr ("Export shortcuts to file..."), QString (),
-                                               tr ("Octave Shortcut Files (*.osc);;All Files (*)"),
-                                               nullptr, QFileDialog::Option (opts));
-
-        if (file.isEmpty ())
-          return false;
-
-        gui_settings osc_settings (file, QSettings::IniFormat);
-
-        if (osc_settings.status () !=  QSettings::NoError)
-          {
-            qWarning () << tr ("Failed to open %1 as Octave shortcut file")
-                        .arg (file);
-            return false;
-          }
-        else
-          {
-            if (action == OSC_IMPORT)
-              import_shortcuts (osc_settings);   // import (special action)
-            else if (action == OSC_EXPORT)
-              write_shortcuts (osc_settings, false); // export, (save settings)
-          }
-      }
-    else
-      reset_default_shortcuts ();
-
-    return true;
-  }
-
-  void shortcut_manager::handle_double_clicked (QTreeWidgetItem *item, int col)
-  {
-    if (col != 2)
-      return;
-
-    int i = m_item_index_hash[item];
-    if (i == 0)
-      return;  // top-level-item clicked
-
-    shortcut_dialog (i-1); // correct to index starting at 0
-  }
-
-  void shortcut_manager::shortcut_dialog_finished (int result)
-  {
-    if (result == QDialog::Rejected)
-      return;
-
-    // check for duplicate
-    int double_index = m_shortcut_hash[m_edit_actual->text ()] - 1;
-
-    if (double_index >= 0 && double_index != m_handled_index)
-      {
-        int ret = QMessageBox::warning (this, tr ("Double Shortcut"),
-                                        tr ("The chosen shortcut\n  \"%1\"\n"
-                                            "is already used for the action\n  \"%2\".\n"
-                                            "Do you want to use the shortcut anyhow removing it "
-                                            "from the previous action?")
-                                        .arg (m_edit_actual->text ())
-                                        .arg (m_sc.at (double_index).m_description),
-                                        QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
-
-        if (ret == QMessageBox::Yes)
-          {
-            shortcut_t double_shortcut = m_sc.at (double_index);
-            double_shortcut.m_actual_sc = QKeySequence ();
-            m_sc.replace (double_index, double_shortcut);
-            m_index_item_hash[double_index]->setText (2, QString ());
-          }
-        else
-          return;
-      }
-
-    shortcut_t shortcut = m_sc.at (m_handled_index);
-    if (! shortcut.m_actual_sc.isEmpty ())
-      m_shortcut_hash.remove (shortcut.m_actual_sc.toString ());
-    shortcut.m_actual_sc = m_edit_actual->text ();
-    m_sc.replace (m_handled_index, shortcut);
-
-    m_index_item_hash[m_handled_index]->setText (2, shortcut.m_actual_sc.toString ());
-
-    if (! shortcut.m_actual_sc.isEmpty ())
-      m_shortcut_hash[shortcut.m_actual_sc.toString ()] = m_handled_index + 1;
-  }
-
-  void shortcut_manager::shortcut_dialog_set_default (void)
-  {
-    m_edit_actual->setText (m_label_default->text ());
-  }
-
-  void shortcut_manager::init (const QString& description, const sc_pref& sc)
-  {
-    gui_settings settings;
-
-    QKeySequence actual = QKeySequence (settings.sc_value (sc));
-
-    // append the new shortcut to the list
-    shortcut_t shortcut_info;
-    shortcut_info.m_description = description;
-    shortcut_info.m_settings_key = sc.settings_key ();
-    shortcut_info.m_actual_sc = actual;
-    shortcut_info.m_default_sc = settings.sc_def_value (sc);
-    m_sc << shortcut_info;
-
-    // insert shortcut in order to check for duplicates later
-    if (! actual.isEmpty ())
-      m_shortcut_hash[actual.toString ()] = m_sc.count ();
-
-    // check whether ctrl+d is used from main window, i.e. is a global shortcut
-    QString main_group_prefix
-      = sc_main_file.mid (0, sc_main_file.indexOf ('_') + 1);
-    if (sc.settings_key ().startsWith (main_group_prefix)
-        && actual == QKeySequence (Qt::ControlModifier+Qt::Key_D))
-      settings.setValue (sc_main_ctrld.key, true);
-  }
-
-  void shortcut_manager::shortcut_dialog (int index)
-  {
-    if (! m_dialog)
-      {
-        m_dialog = new QDialog (this);
-
-        m_dialog->setWindowTitle (tr ("Enter new Shortcut"));
-
-        QVBoxLayout *box = new QVBoxLayout (m_dialog);
-        box->setSpacing (2);
-        box->setContentsMargins (12, 12, 12, 12);
-
-        QLabel *help = new QLabel (tr ("Apply the desired shortcut or click "
-                                       "on the right button to reset the "
-                                       "shortcut to its default."));
-        help->setWordWrap (true);
-        box->addWidget (help);
-
-        QCheckBox *direct
-          = new QCheckBox (tr ("Enter shortcut directly by performing it"));
-
-        QCheckBox *shift
-          = new QCheckBox (tr ("Add Shift modifier\n"
-                               "(allows one to enter number keys)"));
-
-        shift->setStyleSheet
-          ("QCheckBox::indicator { subcontrol-position: left top; }");
-
-        connect (direct, &QCheckBox::clicked, shift, &QCheckBox::setEnabled);
-
-        direct->setCheckState (Qt::Checked);
-
-        box->addWidget (direct);
-        box->addWidget (shift);
-
-        box->addSpacing (15);
-
-        QGridLayout *grid = new QGridLayout ();
-
-        QLabel *actual = new QLabel (tr ("Actual shortcut"));
-        m_edit_actual = new enter_shortcut (m_dialog);
-        m_edit_actual->setAlignment (Qt::AlignHCenter);
-        grid->addWidget (actual, 0, 0);
-        grid->addWidget (m_edit_actual, 0, 1);
-
-        QLabel *def = new QLabel (tr ("Default shortcut"));
-        m_label_default = new QLabel (m_dialog);
-        m_label_default->setAlignment (Qt::AlignHCenter);
-        grid->addWidget (def, 1, 0);
-        grid->addWidget (m_label_default, 1, 1);
-
-        QPushButton *set_default = new QPushButton (tr ("Set to default"));
-        grid->addWidget (set_default, 0, 2);
-        connect (set_default, &QPushButton::clicked,
-                 this, &shortcut_manager::shortcut_dialog_set_default);
-
-        box->addLayout (grid);
-
-        box->addSpacing (18);
-
-        QDialogButtonBox *button_box = new QDialogButtonBox (QDialogButtonBox::Ok
-                                                             | QDialogButtonBox::Cancel);
-        QList<QAbstractButton *> buttons = button_box->buttons ();
-        for (int i = 0; i < buttons.count (); i++)
-          buttons.at (i)->setShortcut (QKeySequence ());
-        connect (button_box, &QDialogButtonBox::accepted,
-                 m_dialog, &QDialog::accept);
-        connect (button_box, &QDialogButtonBox::rejected,
-                 m_dialog, &QDialog::reject);
-        box->addWidget (button_box);
-
-        m_dialog->setLayout (box);
-
-        connect (direct, &QCheckBox::stateChanged,
-                 m_edit_actual, &enter_shortcut::handle_direct_shortcut);
-        connect (shift, &QCheckBox::stateChanged,
-                 m_edit_actual, &enter_shortcut::handle_shift_modifier);
-        connect (m_dialog, &QDialog::finished,
-                 this, &shortcut_manager::shortcut_dialog_finished);
-
-      }
-
-    m_edit_actual->setText (m_sc.at (index).m_actual_sc.toString ());
-    m_label_default->setText (m_sc.at (index).m_default_sc.toString ());
-    m_handled_index = index;
-
-    m_edit_actual->setFocus ();
-    m_dialog->setFocusProxy (m_edit_actual);
-    m_dialog->exec ();
-  }
-
-  // import a shortcut set from a given settings file and refresh the
-  // tree view
-  void shortcut_manager::import_shortcuts (gui_settings& settings)
-  {
-    for (int i = 0; i < m_sc.count (); i++)
-      {
-        // update the list of all shortcuts
-
-        // make a copy
-        shortcut_t sc = m_sc.at (i);
-
-        // get new shortcut from settings and use the old one as default
-        sc.m_actual_sc = QKeySequence (settings.value (sc_group + sc.m_settings_key, sc.m_actual_sc).toString ());
-
-        // replace the old with the new one
-        m_sc.replace (i, sc);
-
-        // update the tree view
-        // get related tree item
-        QTreeWidgetItem *tree_item = m_index_item_hash[i];
-
-        // display new shortcut
-        tree_item->setText (2, sc.m_actual_sc.toString ());
-      }
-  }
-
-  // reset to the defaults and refresh the tree view
-  void shortcut_manager::reset_default_shortcuts (void)
-  {
-    for (int i = 0; i < m_sc.count (); i++)
-      {
-        // update the list of all shortcuts
-
-        // make a copy
-        shortcut_t sc = m_sc.at (i);
-
-        // get default shortcut
-        sc.m_actual_sc = QKeySequence (sc.m_default_sc);
-
-        // replace the old with the new one
-        m_sc.replace (i, sc);
-
-        // update the tree view
-        // get related tree item
-        QTreeWidgetItem *tree_item = m_index_item_hash[i];
-
-        // display new shortcut
-        tree_item->setText (2, sc.m_actual_sc.toString ());
-      }
-  }
-
-  // ask the user whether to save the current shortcut set;
-  // returns true to proceed with import action, false to abort it
-  bool shortcut_manager::overwrite_all_shortcuts (void)
-  {
-    QMessageBox msg_box;
-    msg_box.setWindowTitle (tr ("Overwriting Shortcuts"));
-    msg_box.setIcon (QMessageBox::Warning);
-    msg_box.setText (tr ("You are about to overwrite all shortcuts.\n"
-                         "Would you like to save the current shortcut set or cancel the action?"));
-    msg_box.setStandardButtons (QMessageBox::Save | QMessageBox::Cancel);
-    QPushButton *discard = msg_box.addButton (tr ("Don't save"),
-                                              QMessageBox::DestructiveRole);
-    msg_box.setDefaultButton (QMessageBox::Save);
-
-    int ret = msg_box.exec ();
-
-    if (msg_box.clickedButton () == discard)
-      return true;  // do not save and go ahead
-
-    if (ret == QMessageBox::Save)
-      {
-        if (import_export (OSC_EXPORT))
-          return true;  // go ahead
-      }
-
-    return false; // abort the import
-  }
-
-OCTAVE_END_NAMESPACE(octave)
--- a/libgui/src/shortcut-manager.h	Sun Dec 25 20:37:53 2022 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,168 +0,0 @@
-////////////////////////////////////////////////////////////////////////
-//
-// Copyright (C) 2014-2022 The Octave Project Developers
-//
-// See the file COPYRIGHT.md in the top-level directory of this
-// distribution or <https://octave.org/copyright/>.
-//
-// This file is part of Octave.
-//
-// Octave 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.
-//
-// Octave 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 Octave; see the file COPYING.  If not, see
-// <https://www.gnu.org/licenses/>.
-//
-////////////////////////////////////////////////////////////////////////
-
-#if ! defined (octave_shortcut_manager_h)
-#define octave_shortcut_manager_h 1
-
-#include <QKeyEvent>
-#include <QLabel>
-#include <QLineEdit>
-#include <QShortcut>
-#include <QTreeWidget>
-#include <QWidget>
-
-#include "gui-preferences.h"
-
-OCTAVE_BEGIN_NAMESPACE(octave)
-
-  class enter_shortcut : public QLineEdit
-  {
-    Q_OBJECT
-
-  public:
-
-    enter_shortcut (QWidget *p = nullptr);
-
-    ~enter_shortcut (void) = default;
-
-    virtual void keyPressEvent (QKeyEvent *e);
-
-  public slots:
-
-    void handle_direct_shortcut (int);
-    void handle_shift_modifier (int);
-
-  private:
-
-    bool m_direct_shortcut;
-    bool m_shift_modifier;
-
-  };
-
-  class gui_settings;
-
-  class shortcut_manager : public QWidget
-  {
-    Q_OBJECT
-
-  public:
-
-    enum
-    {
-      OSC_IMPORT  = 0,
-      OSC_EXPORT  = 1,
-      OSC_DEFAULT = 2
-    };
-
-    shortcut_manager (void);
-
-    // No copying!
-
-    shortcut_manager (const shortcut_manager&) = delete;
-
-    shortcut_manager& operator = (const shortcut_manager&) = delete;
-
-    ~shortcut_manager (void) = default;
-
-    void init_data (void);
-
-    void write_shortcuts (gui_settings& settings, bool closing);
-
-    void fill_treewidget (QTreeWidget *tree_view);
-
-    bool import_export (int action);
-
-  protected slots:
-
-    void handle_double_clicked (QTreeWidgetItem *, int);
-    void shortcut_dialog_finished (int);
-    void shortcut_dialog_set_default ();
-
-  private:
-
-    void init (const QString&, const sc_pref& scpref);
-    void shortcut_dialog (int);
-    void import_shortcuts (gui_settings& settings);
-    void reset_default_shortcuts (void);
-    bool overwrite_all_shortcuts (void);
-
-    class shortcut_t
-    {
-    public:
-
-      shortcut_t (void)
-        : m_tree_item (nullptr), m_description (), m_settings_key (),
-          m_actual_sc (QKeySequence ()), m_default_sc (QKeySequence ())
-      { }
-
-      shortcut_t (const shortcut_t& x)
-        : m_tree_item (x.m_tree_item), m_description (x.m_description),
-          m_settings_key (x.m_settings_key)
-      {
-        m_actual_sc = x.m_actual_sc;
-        m_default_sc = x.m_default_sc;
-      }
-
-      shortcut_t& operator = (const shortcut_t& x)
-      {
-        if (&x != this)
-          {
-            m_tree_item = x.m_tree_item;
-            m_description = x.m_description;
-            m_settings_key = x.m_settings_key;
-
-            m_actual_sc = QKeySequence ();
-            m_default_sc = QKeySequence ();
-
-            m_actual_sc = x.m_actual_sc;
-            m_default_sc = x.m_default_sc;
-          }
-
-        return *this;
-      }
-
-      ~shortcut_t (void) = default;
-
-      QTreeWidgetItem *m_tree_item;
-      QString m_description;
-      QString m_settings_key;
-      QKeySequence m_actual_sc;
-      QKeySequence m_default_sc;
-    };
-
-    QList<shortcut_t> m_sc;
-    QHash<QString, int> m_shortcut_hash;
-    QHash<int, QTreeWidgetItem *> m_index_item_hash;
-    QHash<QTreeWidgetItem *, int> m_item_index_hash;
-
-    QDialog *m_dialog;
-    enter_shortcut *m_edit_actual;
-    QLabel *m_label_default;
-    int m_handled_index;
-  };
-
-OCTAVE_END_NAMESPACE(octave)
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/shortcuts-tree-widget.cc	Mon Dec 26 17:29:59 2022 -0500
@@ -0,0 +1,708 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2014-2022 The Octave Project Developers
+//
+// See the file COPYRIGHT.md in the top-level directory of this
+// distribution or <https://octave.org/copyright/>.
+//
+// This file is part of Octave.
+//
+// Octave 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.
+//
+// Octave 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 Octave; see the file COPYING.  If not, see
+// <https://www.gnu.org/licenses/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#if defined (HAVE_CONFIG_H)
+#  include "config.h"
+#endif
+
+#include <QApplication>
+#include <QDialog>
+#include <QDialogButtonBox>
+#include <QGridLayout>
+#include <QHeaderView>
+#include <QKeyEvent>
+#include <QLabel>
+#include <QMessageBox>
+#include <QPushButton>
+#include <QVBoxLayout>
+
+#include "gui-preferences-sc.h"
+#include "gui-settings.h"
+#include "shortcuts-tree-widget.h"
+
+OCTAVE_BEGIN_NAMESPACE(octave)
+
+// enter_shortcut:
+// class derived from QLineEdit for directly entering key sequences which
+
+enter_shortcut::enter_shortcut (QWidget *p) : QLineEdit (p)
+{
+  m_direct_shortcut = true;      // the shortcut is directly entered
+  m_shift_modifier = false;      // the shift modifier is not added
+}
+
+// new keyPressEvent
+void enter_shortcut::keyPressEvent (QKeyEvent *e)
+{
+  if (! m_direct_shortcut)
+    {
+      QLineEdit::keyPressEvent (e);
+      return;
+    }
+
+  if (e->type () == QEvent::KeyPress)
+    {
+      int key = e->key ();
+
+      if (key == Qt::Key_unknown || key == 0)
+        return;
+
+      Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers (); //e->modifiers ();
+
+      if (m_shift_modifier || (modifiers & Qt::ShiftModifier))
+        key += Qt::SHIFT;
+      if (modifiers & Qt::ControlModifier)
+        key += Qt::CTRL;
+      if (modifiers & Qt::AltModifier)
+        key += Qt::ALT;
+      if (modifiers & Qt::MetaModifier)
+        key += Qt::META;
+
+      setText (QKeySequence (key).toString ());
+    }
+}
+
+// slot for checkbox whether the shortcut is directly entered or not
+void enter_shortcut::handle_direct_shortcut (int state)
+{
+  if (state)
+    m_direct_shortcut = true;  // the shortcut is directly entered
+  else
+    m_direct_shortcut = false; // the shortcut has to be written as text
+}
+
+// slot for checkbox whether the shift modifier should be added
+void enter_shortcut::handle_shift_modifier (int state)
+{
+  if (state)
+    m_shift_modifier = true;  // the shortcut is directly entered
+  else
+    m_shift_modifier = false; // the shortcut has to be written as text
+}
+
+tree_widget_shortcut_item::tree_widget_shortcut_item
+(QTreeWidgetItem *parent, const sc_pref& scpref, const QString& actual_text)
+  : QTreeWidgetItem (parent), m_settings_key (scpref.settings_key ())
+{
+  // set a slightly transparent foreground for default columns
+  QColor fg = QColor (foreground (DEFAULT_COLUMN).color ());
+  fg.setAlpha (128);
+  setForeground (DEFAULT_COLUMN, QBrush (fg));
+
+  // write the shortcuts
+  set_description (scpref.description ());
+  set_default_text (scpref.def_text ());
+  set_actual_text (actual_text);
+}
+
+QString tree_widget_shortcut_item::settings_key (void) const
+{
+  return m_settings_key;
+}
+
+QString tree_widget_shortcut_item::description (void) const
+{
+  return text (DESCRIPTION_COLUMN);
+}
+
+void tree_widget_shortcut_item::set_description (const QString& text)
+{
+  setText (DESCRIPTION_COLUMN, text);
+}
+
+QString tree_widget_shortcut_item::default_text (void) const
+{
+  return text (DEFAULT_COLUMN);
+}
+
+void tree_widget_shortcut_item::set_default_text (const QString& text)
+{
+  setText (DEFAULT_COLUMN, text);
+}
+
+QString tree_widget_shortcut_item::actual_text (void) const
+{
+  return text (ACTUAL_COLUMN);
+}
+
+void tree_widget_shortcut_item::set_actual_text (const QString& text)
+{
+  setText (ACTUAL_COLUMN, text);
+}
+
+shortcut_edit_dialog::shortcut_edit_dialog
+  (tree_widget_shortcut_item *shortcut_item, QWidget *parent)
+  : QDialog (parent), m_shortcut_item (shortcut_item),
+    m_settings_key (shortcut_item->settings_key ())
+{
+  setAttribute (Qt::WA_DeleteOnClose);
+
+  setWindowTitle (tr ("Enter new Shortcut"));
+
+  QVBoxLayout *box = new QVBoxLayout (this);
+
+  box->setSpacing (2);
+  box->setContentsMargins (12, 12, 12, 12);
+
+  QLabel *help = new QLabel (tr ("Apply the desired shortcut or click "
+                                 "on the right button to reset the "
+                                 "shortcut to its default. (%1)")
+                             .arg (m_settings_key));
+
+  help->setWordWrap (true);
+
+  box->addWidget (help);
+
+  QCheckBox *direct
+    = new QCheckBox (tr ("Enter shortcut directly by performing it"));
+
+  QCheckBox *shift
+    = new QCheckBox (tr ("Add Shift modifier\n"
+                         "(allows one to enter number keys)"));
+
+  shift->setStyleSheet
+    ("QCheckBox::indicator { subcontrol-position: left top; }");
+
+  connect (direct, &QCheckBox::clicked, shift, &QCheckBox::setEnabled);
+
+  direct->setCheckState (Qt::Checked);
+
+  box->addWidget (direct);
+  box->addWidget (shift);
+
+  box->addSpacing (15);
+
+  QGridLayout *grid = new QGridLayout ();
+
+  QLabel *actual = new QLabel (tr ("Actual shortcut"));
+
+  m_edit_actual = new enter_shortcut (this);
+  m_edit_actual->setAlignment (Qt::AlignHCenter);
+
+  grid->addWidget (actual, 0, 0);
+  grid->addWidget (m_edit_actual, 0, 1);
+
+  QLabel *def = new QLabel (tr ("Default shortcut"));
+
+  QLabel *label_default = new QLabel (this);
+  label_default->setAlignment (Qt::AlignHCenter);
+
+  grid->addWidget (def, 1, 0);
+  grid->addWidget (label_default, 1, 1);
+
+  QPushButton *set_default = new QPushButton (tr ("Set to default"));
+
+  connect (set_default, &QPushButton::clicked,
+           this, &shortcut_edit_dialog::set_default_shortcut);
+
+  grid->addWidget (set_default, 0, 2);
+
+  box->addLayout (grid);
+  box->addSpacing (18);
+
+  QDialogButtonBox *button_box = new QDialogButtonBox (QDialogButtonBox::Ok
+                                                       | QDialogButtonBox::Cancel);
+  QList<QAbstractButton *> buttons = button_box->buttons ();
+  for (int i = 0; i < buttons.count (); i++)
+    buttons.at (i)->setShortcut (QKeySequence ());
+
+  connect (button_box, &QDialogButtonBox::accepted,
+           this, &QDialog::accept);
+
+  connect (button_box, &QDialogButtonBox::rejected,
+           this, &QDialog::reject);
+
+  box->addWidget (button_box);
+
+  setLayout (box);
+
+  connect (direct, &QCheckBox::stateChanged,
+           m_edit_actual, &enter_shortcut::handle_direct_shortcut);
+
+  connect (shift, &QCheckBox::stateChanged,
+           m_edit_actual, &enter_shortcut::handle_shift_modifier);
+
+  connect (this, &QDialog::finished,
+           this, &shortcut_edit_dialog::finished);
+
+  gui_settings settings;
+
+  const sc_pref scpref = all_shortcut_preferences::value (m_settings_key);
+
+  QString actual_text = settings.sc_value (scpref);
+
+  m_default_text = scpref.def_text ();
+
+  m_edit_actual->setText (actual_text);
+  label_default->setText (m_default_text);
+
+  m_edit_actual->setFocus ();
+
+  setFocusProxy (m_edit_actual);
+}
+
+void shortcut_edit_dialog::finished (int result)
+{
+  if (result == QDialog::Rejected)
+    return;
+
+  // Check whether the chosen shortcut is already in use either in the
+  // current context (section of the shortcut settings) or as a global
+  // (main_) shortcut.  This job might have been easier if we had
+  // organized the sections as child groups instead of using a colon in
+  // the settings key to separate the section from the shortcut name.
+
+ // Note that m_settings_key doesn't begin with the sc_group prefix.
+
+  QString my_section = get_shortcut_section (m_settings_key);
+  QString actual_text = m_edit_actual->text ();
+
+  bool conflict = false;
+  QString other_settings_key;
+
+  gui_settings settings;
+
+  settings.beginGroup (sc_group);
+  const QStringList shortcut_settings_keys = settings.allKeys ();
+  settings.endGroup ();
+
+  for (const auto& settings_key : shortcut_settings_keys)
+    {
+      if (settings_key == m_settings_key)
+        continue;
+
+      QString section = get_shortcut_section (settings_key);
+
+      if (section == my_section || section.startsWith ("main_"))
+        {
+          QString shortcut_text
+            = settings.value (sc_group + "/" + settings_key).toString ();
+
+          if (shortcut_text == actual_text)
+            {
+              other_settings_key = settings_key;
+              conflict = true;
+            }
+        }
+    }
+
+  if (conflict)
+    {
+      // We only need the description of the other shortcut, not the
+      // complete sc_pref info.
+
+      const sc_pref other_scpref
+        = all_shortcut_preferences::value (other_settings_key);
+
+      int ret = QMessageBox::warning (this, tr ("Double Shortcut"),
+                                      tr ("The chosen shortcut\n  \"%1\"\n"
+                                          "is already used for the action\n  \"%2\".\n"
+                                          "Do you want to use the shortcut and remove it "
+                                          "from the previous action?")
+                                      .arg (actual_text)
+                                      .arg (other_scpref.description ()),
+                                      QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
+
+      if (ret == QMessageBox::Yes)
+        emit set_shortcut (other_settings_key, "");
+      else
+        return;
+    }
+
+  m_shortcut_item->set_actual_text (actual_text);
+}
+
+void shortcut_edit_dialog::set_default_shortcut (void)
+{
+  // Just remove user-set value so that the default will be used.
+  m_edit_actual->setText ("");
+}
+
+shortcuts_tree_widget::shortcuts_tree_widget (QWidget *parent)
+  : QTreeWidget (parent)
+{
+  QHash <QString, QTreeWidgetItem *> level_hash;
+
+  header ()->setSectionResizeMode (QHeaderView::ResizeToContents);
+
+  int dsc_col = tree_widget_shortcut_item::DESCRIPTION_COLUMN;
+
+  QTreeWidgetItem *main = new QTreeWidgetItem (this);
+  main->setText (dsc_col, tr ("Global"));
+  main->setExpanded (true);
+
+  QTreeWidgetItem *main_file = new QTreeWidgetItem (main);
+  main_file->setText (dsc_col, tr ("File Menu"));
+
+  QTreeWidgetItem *main_edit = new QTreeWidgetItem (main);
+  main_edit->setText (dsc_col, tr ("Edit Menu"));
+
+  QTreeWidgetItem *main_debug = new QTreeWidgetItem (main);
+  main_debug->setText (dsc_col, tr ("Debug Menu"));
+
+  QTreeWidgetItem *main_tools = new QTreeWidgetItem (main);
+  main_tools->setText (dsc_col, tr ("Tools Menu"));
+
+  QTreeWidgetItem *main_window = new QTreeWidgetItem (main);
+  main_window->setText (dsc_col, tr ("Window Menu"));
+
+  QTreeWidgetItem *main_help = new QTreeWidgetItem (main);
+  main_help->setText (dsc_col, tr ("Help Menu"));
+
+  QTreeWidgetItem *main_news = new QTreeWidgetItem (main);
+  main_news->setText (dsc_col, tr ("News Menu"));
+
+  QTreeWidgetItem *main_dock_widgets = new QTreeWidgetItem (main);
+  main_dock_widgets->setText (dsc_col, tr ("Handling of Dock Widgets"));
+
+  QTreeWidgetItem *main_tabs = new QTreeWidgetItem (main);
+  main_tabs->setText (dsc_col, tr ("Tab Handling in Dock Widgets"));
+
+  QTreeWidgetItem *main_find = new QTreeWidgetItem (main);
+  main_find->setText (dsc_col, tr ("Find & Replace in Dock Widgets"));
+
+  QTreeWidgetItem *main_zoom = new QTreeWidgetItem (main);
+  main_zoom->setText (dsc_col, tr ("Zooming in Editor and Documentation"));
+
+  level_hash[sc_main_file] = main_file;
+  level_hash[sc_main_edit] = main_edit;
+  level_hash[sc_main_debug] = main_debug;
+  level_hash[sc_main_tools] = main_tools;
+  level_hash[sc_main_window] = main_window;
+  level_hash[sc_main_help] = main_help;
+  level_hash[sc_main_news] = main_news;
+  level_hash[sc_dock_widget] = main_dock_widgets;
+  level_hash[sc_edit_tabs] = main_tabs;
+  level_hash[sc_edit_find] = main_find;
+  level_hash[sc_edit_zoom] = main_zoom;
+
+  QTreeWidgetItem *editor = new QTreeWidgetItem (this);
+  editor->setText (dsc_col, tr ("Editor"));
+  editor->setExpanded (true);
+
+  QTreeWidgetItem *editor_file = new QTreeWidgetItem (editor);
+  editor_file->setText (dsc_col, tr ("File Menu"));
+
+  QTreeWidgetItem *editor_edit = new QTreeWidgetItem (editor);
+  editor_edit->setText (dsc_col, tr ("Edit Menu"));
+
+  QTreeWidgetItem *editor_view = new QTreeWidgetItem (editor);
+  editor_view->setText (dsc_col, tr ("View Menu"));
+
+  QTreeWidgetItem *editor_debug = new QTreeWidgetItem (editor);
+  editor_debug->setText (dsc_col, tr ("Debug Menu"));
+
+  QTreeWidgetItem *editor_run = new QTreeWidgetItem (editor);
+  editor_run->setText (dsc_col, tr ("Run Menu"));
+
+  QTreeWidgetItem *editor_help = new QTreeWidgetItem (editor);
+  editor_help->setText (dsc_col, tr ("Help Menu"));
+
+  level_hash[sc_edit_file] = editor_file;
+  level_hash[sc_edit_edit] = editor_edit;
+  level_hash[sc_edit_view] = editor_view;
+  level_hash[sc_edit_debug] = editor_debug;
+  level_hash[sc_edit_run] = editor_run;
+  level_hash[sc_edit_help] = editor_help;
+
+  QTreeWidgetItem *doc = new QTreeWidgetItem (this);
+  doc->setText (dsc_col, tr ("Documentation Viewer"));
+  doc->setExpanded (true);
+
+  QTreeWidgetItem *doc_browser = new QTreeWidgetItem (doc);
+  doc_browser->setText (dsc_col, tr ("Browser"));
+
+  level_hash[sc_doc] = doc_browser;
+
+  connect (this, &QTreeWidget::itemDoubleClicked,
+           this, &shortcuts_tree_widget::edit_selection);
+
+  const QList<QString> shortcut_settings_keys
+    = all_shortcut_preferences::keys ();
+
+  gui_settings settings;
+
+  settings.beginGroup (sc_group);
+
+  for (const auto& settings_key : shortcut_settings_keys)
+    {
+      QTreeWidgetItem *section = level_hash[settings_key.section (':', 0, 0)];
+
+      // handle sections which have changed and do not correspond to the
+      // previously defined keyname
+      if (section == editor_file)
+        {
+          // Closing tabs now in global tab handling section
+          if (settings_key.contains (sc_edit_file_cl))
+            section = main_tabs;
+        }
+      else if (section == editor_edit)
+        {
+          // Find & replace now in global file & replace handling section
+          if (settings_key.contains (sc_edit_edit_find))
+            section = main_find;
+        }
+      else if (section == editor_view)
+        {
+          // Zooming now in global zoom handling section
+          if (settings_key.contains (sc_edit_view_zoom))
+            section = main_zoom;
+        }
+
+      // We don't want to apply default value here.
+      QString actual_text = settings.value (settings_key).toString ();
+
+      const sc_pref scpref = all_shortcut_preferences::value (settings_key);
+
+      // Inserts itself in the tree widget in SECTION.  The parent
+      // object will delete it.
+      new tree_widget_shortcut_item (section, scpref, actual_text);
+    }
+
+  settings.endGroup ();
+}
+
+void
+shortcuts_tree_widget::edit_selection (QTreeWidgetItem *item, int col)
+{
+  if (col != 2)
+    return;
+
+  tree_widget_shortcut_item *shortcut_item
+    = dynamic_cast<tree_widget_shortcut_item *> (item);
+
+  if (! shortcut_item)
+    return;  // top-level-item clicked
+
+  shortcut_edit_dialog *dialog
+    = new shortcut_edit_dialog (shortcut_item);
+
+  connect (dialog, &shortcut_edit_dialog::set_shortcut,
+           this, &shortcuts_tree_widget::update_widget_value);
+
+  dialog->show ();
+}
+
+void shortcuts_tree_widget::update_widget_value (const QString& settings_key,
+                                                 const QString& sc_text)
+{
+  tree_widget_shortcut_item *item = get_item (settings_key);
+
+  if (item)
+    item->set_actual_text (sc_text);
+}
+
+tree_widget_shortcut_item *
+shortcuts_tree_widget::get_item (const QString& settings_key)
+{
+  // There aren't many shortcuts so iterating over all of them to find
+  // an individual item isn't a big performance issue.  If we had many
+  // more items we could use a QHash <settings_key, sc_pref> data member.
+
+  tree_widget_shortcut_item *item = nullptr;
+
+  QTreeWidgetItemIterator it (this, QTreeWidgetItemIterator::NoChildren);
+  while (*it)
+    {
+      tree_widget_shortcut_item *shortcut_item
+        = dynamic_cast<tree_widget_shortcut_item *> (*it);
+
+      if (settings_key == shortcut_item->settings_key ())
+        {
+          item = shortcut_item;
+          break;
+        }
+
+      it++;
+    }
+
+  // FIXME: Should it be an error to not find a match?
+
+  if (! item)
+    qWarning () << (tr ("item %1 not found in shortcut settings dialog")
+                    .arg (settings_key));
+
+  return item;
+}
+
+void shortcuts_tree_widget::update_settings_value (gui_settings& settings,
+                                                   const QString& settings_key)
+{
+  tree_widget_shortcut_item *item = get_item (settings_key);
+
+  if (item)
+    settings.setValue (settings_key, item->actual_text ());
+}
+
+// Refresh the tree view with values from the settings object.
+
+void shortcuts_tree_widget::import_shortcuts (gui_settings& settings)
+{
+  settings.beginGroup (sc_group);
+
+  const QStringList shortcut_settings_keys = settings.allKeys ();
+
+  for (const auto& settings_key : shortcut_settings_keys)
+    {
+      QString sc_text = settings.value (settings_key).toString ();
+
+      update_widget_value (settings_key, sc_text);
+    }
+
+  settings.endGroup ();
+
+  bool sc_ctrld = false;
+
+  QTreeWidgetItemIterator it (this, QTreeWidgetItemIterator::NoChildren);
+  while (*it)
+    {
+      tree_widget_shortcut_item *shortcut_item
+        = dynamic_cast<tree_widget_shortcut_item *> (*it);
+
+      if (! shortcut_item)
+        continue;
+
+      QString settings_key = shortcut_item->settings_key ();
+      QString sc_text = shortcut_item->actual_text ();
+
+      if (sc_text.isEmpty ())
+        sc_text = shortcut_item->default_text ();
+
+      QString section = get_shortcut_section (settings_key);
+
+      // special: check main-window for Ctrl-D (Terminal)
+      if (section.startsWith ("main_")
+          && QKeySequence (sc_text) == QKeySequence (Qt::ControlModifier+Qt::Key_D))
+
+        sc_ctrld = true;
+
+      it++;
+    }
+
+  settings.setValue (sc_main_ctrld.key, sc_ctrld);
+
+  settings.sync ();
+}
+
+// Export all shortcuts from the tree view to the settings object.
+
+void shortcuts_tree_widget::export_shortcuts (gui_settings& settings)
+{
+  settings.beginGroup (sc_group);
+
+  bool sc_ctrld = false;
+
+  QTreeWidgetItemIterator it (this, QTreeWidgetItemIterator::NoChildren);
+  while (*it)
+    {
+      tree_widget_shortcut_item *shortcut_item
+        = dynamic_cast<tree_widget_shortcut_item *> (*it);
+
+      if (! shortcut_item)
+        continue;
+
+      QString settings_key = shortcut_item->settings_key ();
+      QString sc_text = shortcut_item->actual_text ();
+
+      if (sc_text.isEmpty ())
+        sc_text = shortcut_item->default_text ();
+      else
+        settings.setValue (settings_key, sc_text);
+
+      QString section = get_shortcut_section (settings_key);
+
+      // special: check main-window for Ctrl-D (Terminal)
+      if (section.startsWith ("main_")
+          && QKeySequence (sc_text) == QKeySequence (Qt::ControlModifier+Qt::Key_D))
+
+        sc_ctrld = true;
+
+      it++;
+    }
+
+  settings.endGroup ();
+
+  settings.setValue (sc_main_ctrld.key, sc_ctrld);
+
+  settings.sync ();
+}
+
+// Clear all user-defined settings from the tree widget and the
+// application settings.
+
+void shortcuts_tree_widget::set_default_shortcuts (void)
+{
+  gui_settings settings;
+
+  settings.beginGroup (sc_group);
+
+  settings.remove ("");
+
+  settings.endGroup ();
+
+  bool sc_ctrld = false;
+
+  QTreeWidgetItemIterator it (this, QTreeWidgetItemIterator::NoChildren);
+  while (*it)
+    {
+      tree_widget_shortcut_item *shortcut_item
+        = dynamic_cast<tree_widget_shortcut_item *> (*it);
+
+      if (! shortcut_item)
+        continue;
+
+      QString settings_key = shortcut_item->settings_key ();
+
+      shortcut_item->set_actual_text ("");
+
+      QString sc_text = shortcut_item->default_text ();
+
+      QString section = get_shortcut_section (settings_key);
+
+      // special: check main-window for Ctrl-D (Terminal)
+      if (section.startsWith ("main_")
+          && QKeySequence (sc_text) == QKeySequence (Qt::ControlModifier+Qt::Key_D))
+
+        sc_ctrld = true;
+
+      it++;
+    }
+
+  settings.setValue (sc_main_ctrld.key, sc_ctrld);
+
+  settings.sync ();
+}
+
+// For each key found in application settings object, transfer
+// corresponding setting to the application settings object.
+
+void shortcuts_tree_widget::write_settings (void)
+{
+  gui_settings settings;
+
+  export_shortcuts (settings);
+}
+
+OCTAVE_END_NAMESPACE(octave)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/shortcuts-tree-widget.h	Mon Dec 26 17:29:59 2022 -0500
@@ -0,0 +1,158 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2014-2022 The Octave Project Developers
+//
+// See the file COPYRIGHT.md in the top-level directory of this
+// distribution or <https://octave.org/copyright/>.
+//
+// This file is part of Octave.
+//
+// Octave 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.
+//
+// Octave 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 Octave; see the file COPYING.  If not, see
+// <https://www.gnu.org/licenses/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#if ! defined (octave_shortcuts_tree_widget_h)
+#define octave_shortcuts_tree_widget_h 1
+
+#include <QCheckBox>
+#include <QDialog>
+#include <QKeyEvent>
+#include <QLineEdit>
+#include <QString>
+#include <QTreeWidget>
+#include <QtCore>
+
+#include "gui-preferences.h"
+#include "gui-settings.h"
+
+OCTAVE_BEGIN_NAMESPACE(octave)
+
+class enter_shortcut : public QLineEdit
+{
+  Q_OBJECT
+
+public:
+
+  enter_shortcut (QWidget *p = nullptr);
+
+  ~enter_shortcut (void) = default;
+
+  virtual void keyPressEvent (QKeyEvent *e);
+
+public slots:
+
+  void handle_direct_shortcut (int);
+  void handle_shift_modifier (int);
+
+private:
+
+  bool m_direct_shortcut;
+  bool m_shift_modifier;
+};
+
+class tree_widget_shortcut_item : public QTreeWidgetItem
+{
+public:
+
+  enum
+  {
+    DESCRIPTION_COLUMN = 0,
+    DEFAULT_COLUMN,
+    ACTUAL_COLUMN
+  };
+
+  tree_widget_shortcut_item (QTreeWidgetItem *parent, const sc_pref& scpref,
+                             const QString& actual_text);
+
+  QString settings_key (void) const;
+
+  QString description (void) const;
+  void set_description (const QString& text);
+
+  QString default_text (void) const;
+  void set_default_text (const QString& text);
+
+  QString actual_text (void) const;
+  void set_actual_text (const QString& text);
+
+private:
+
+  QString m_settings_key;
+};
+
+class shortcut_edit_dialog : public QDialog
+{
+  Q_OBJECT
+
+public:
+
+  shortcut_edit_dialog (tree_widget_shortcut_item *shortcut_item,
+                        QWidget *parent = nullptr);
+
+public slots:
+
+  void finished (int result);
+
+  void set_default_shortcut (void);
+
+signals:
+
+  void set_shortcut (const QString& settings_key,
+                     const QString& settings_value);
+
+private:
+
+  tree_widget_shortcut_item *m_shortcut_item;
+
+  enter_shortcut *m_edit_actual;
+
+  QString m_settings_key;
+  QString m_default_text;
+};
+
+class shortcuts_tree_widget : public QTreeWidget
+{
+  Q_OBJECT
+
+public:
+
+  shortcuts_tree_widget (QWidget *parent);
+
+  void import_shortcuts (gui_settings& settings);
+
+  void export_shortcuts (gui_settings& settings);
+
+  void set_default_shortcuts (void);
+
+  void write_settings (void);
+
+public slots:
+
+  void edit_selection (QTreeWidgetItem *item, int col);
+
+  void update_widget_value (const QString& settings_key,
+                            const QString& sc_text);
+
+private:
+
+  tree_widget_shortcut_item * get_item (const QString& settings_key);
+
+  void update_settings_value (gui_settings& settings,
+                              const QString& settings_key);
+};
+
+OCTAVE_END_NAMESPACE(octave)
+
+#endif
--- a/libgui/src/terminal-dock-widget.cc	Sun Dec 25 20:37:53 2022 -0500
+++ b/libgui/src/terminal-dock-widget.cc	Mon Dec 26 17:29:59 2022 -0500
@@ -39,6 +39,8 @@
 
 #include "gui-preferences-cs.h"
 #include "gui-preferences-global.h"
+#include "gui-preferences-sc.h"
+#include "gui-settings.h"
 
 #include "octave-qobject.h"
 #include "terminal-dock-widget.h"
@@ -50,6 +52,8 @@
     : octave_dock_widget ("TerminalDockWidget", p, oct_qobj),
       m_experimental_terminal_widget (oct_qobj.experimental_terminal_widget ())
   {
+    init_control_d_shortcut_behavior ();
+
     // FIXME: we could do this in a better way, but improving it doesn't
     // matter much if we will eventually be removing the old terminal.
     if (m_experimental_terminal_widget)
@@ -168,4 +172,39 @@
       }
   }
 
+  void terminal_dock_widget::init_control_d_shortcut_behavior (void)
+  {
+    gui_settings settings;
+
+    // Reset use of Ctrl-D.  Do this before the call to beginGroup
+    // because sc_main_ctrld.key already begins with the sc_group
+    // prefix.
+    settings.setValue (sc_main_ctrld.key, false);
+
+    settings.beginGroup (sc_group);
+    const QStringList shortcut_settings_keys = settings.allKeys ();
+    settings.endGroup ();
+
+    for (const auto& settings_key : shortcut_settings_keys)
+      {
+        // Check whether Ctrl+D is used from main window, i.e. is a
+        // global shortcut.
+
+        QString section = get_shortcut_section (settings_key);
+
+        if (section.startsWith ("main_"))
+          {
+            sc_pref scpref = all_shortcut_preferences::value (settings_key);
+
+            QKeySequence actual = QKeySequence (settings.sc_value (scpref));
+
+            if (actual == QKeySequence (Qt::ControlModifier+Qt::Key_D))
+              {
+                settings.setValue (sc_main_ctrld.key, true);
+                break;
+              }
+          }
+     }
+  }
+
 OCTAVE_END_NAMESPACE(octave)
--- a/libgui/src/terminal-dock-widget.h	Sun Dec 25 20:37:53 2022 -0500
+++ b/libgui/src/terminal-dock-widget.h	Mon Dec 26 17:29:59 2022 -0500
@@ -51,6 +51,8 @@
 
     void init_command_prompt ();
 
+    void init_control_d_shortcut_behavior ();
+
     // FIXME: The next two functions could be eliminated (or combined)
     // if we had a common interface for the old and new terminal
     // widgets.