1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2014-2021 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING.  If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #if defined (HAVE_CONFIG_H)
27 #  include "config.h"
28 #endif
29 
30 #include <QAction>
31 #include <QApplication>
32 #include <QCheckBox>
33 #include <QDebug>
34 #include <QDialogButtonBox>
35 #include <QFileDialog>
36 #include <QGridLayout>
37 #include <QHeaderView>
38 #include <QKeySequence>
39 #include <QLineEdit>
40 #include <QMessageBox>
41 #include <QPushButton>
42 #include <QVBoxLayout>
43 #include <QtCore>
44 
45 #include "octave-qobject.h"
46 #include "shortcut-manager.h"
47 #include "gui-preferences-global.h"
48 #include "gui-preferences-sc.h"
49 #include "error.h"
50 
51 namespace octave
52 {
53   // enter_shortcut:
54   // class derived from QLineEdit for directly entering key sequences which
55 
enter_shortcut(QWidget * p)56   enter_shortcut::enter_shortcut (QWidget *p) : QLineEdit (p)
57   {
58     m_direct_shortcut = true;      // the shortcut is directly entered
59     m_shift_modifier = false;      // the shift modifier is not added
60   }
61 
62   // new keyPressEvent
keyPressEvent(QKeyEvent * e)63   void enter_shortcut::keyPressEvent (QKeyEvent *e)
64   {
65     if (! m_direct_shortcut)
66       {
67         QLineEdit::keyPressEvent (e);
68         return;
69       }
70 
71     if (e->type () == QEvent::KeyPress)
72       {
73         int key = e->key ();
74 
75         if (key == Qt::Key_unknown || key == 0)
76           return;
77 
78 #if defined (HAVE_QGUIAPPLICATION)
79         Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers (); //e->modifiers ();
80 #else
81         Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers (); //e->modifiers ();
82 #endif
83 
84         if (m_shift_modifier || (modifiers & Qt::ShiftModifier))
85           key += Qt::SHIFT;
86         if (modifiers & Qt::ControlModifier)
87           key += Qt::CTRL;
88         if (modifiers & Qt::AltModifier)
89           key += Qt::ALT;
90         if (modifiers & Qt::MetaModifier)
91           key += Qt::META;
92 
93         setText (QKeySequence (key).toString ());
94       }
95   }
96 
97   // slot for checkbox whether the shortcut is directly entered or not
handle_direct_shortcut(int state)98   void enter_shortcut::handle_direct_shortcut (int state)
99   {
100     if (state)
101       m_direct_shortcut = true;  // the shortcut is directly entered
102     else
103       m_direct_shortcut = false; // the shortcut has to be written as text
104   }
105 
106   // slot for checkbox whether the shift modifier should be added
handle_shift_modifier(int state)107   void enter_shortcut::handle_shift_modifier (int state)
108   {
109     if (state)
110       m_shift_modifier = true;  // the shortcut is directly entered
111     else
112       m_shift_modifier = false; // the shortcut has to be written as text
113   }
114 
115 
shortcut_manager(base_qobject & oct_qobj)116   shortcut_manager::shortcut_manager (base_qobject& oct_qobj)
117     : m_octave_qobj (oct_qobj)
118   {
119     setObjectName ("Shortcut_Manager");
120 
121     // Mac: don't let Qt interpret CMD key ("Meta" in Qt terminology) as Ctrl
122 #if defined (Q_OS_MAC)
123     QCoreApplication::setAttribute (Qt::AA_MacDontSwapCtrlAndMeta, true);
124 #endif
125   }
126 
init_data(void)127   void shortcut_manager::init_data (void)
128   {
129     resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
130     gui_settings *settings = rmgr.get_settings ();
131 
132     settings->setValue (sc_main_ctrld.key, false); // reset use fo ctrl-d
133 
134     // actions not related to specific menus or widgets
135 
136     // dock widgets
137     init (tr ("Undock/Dock Widget"), sc_dock_widget_dock);
138     init (tr ("Close Widget"), sc_dock_widget_close);
139 
140     // actions of the main window
141 
142     // file
143     init (tr ("New File"), sc_main_file_new_file);
144     init (tr ("New Function"), sc_main_file_new_function);
145     init (tr ("New Figure"), sc_main_file_new_figure);
146     init (tr ("Open File"), sc_main_file_open_file);
147     init (tr ("Load Workspace"), sc_main_file_load_workspace);
148     init (tr ("Save Workspace As"), sc_main_file_save_workspace);
149     init (tr ("Exit Octave"), sc_main_file_exit);
150 
151     // edit
152     init (tr ("Copy"), sc_main_edit_copy);
153     init (tr ("Paste"), sc_main_edit_paste);
154     init (tr ("Undo"), sc_main_edit_undo);
155     init (tr ("Select All"), sc_main_edit_select_all);
156     init (tr ("Clear Clipboard"), sc_main_edit_clear_clipboard);
157     init (tr ("Find in Files"), sc_main_edit_find_in_files);
158     init (tr ("Clear Command Window"), sc_main_edit_clear_command_window);
159     init (tr ("Clear Command History"), sc_main_edit_clear_history);
160     init (tr ("Clear Workspace"), sc_main_edit_clear_workspace);
161     init (tr ("Set Path"), sc_main_edit_set_path);
162     init (tr ("Preferences"), sc_main_edit_preferences);
163 
164     // debug
165     init (tr ("Step"), sc_main_debug_step_over);
166     init (tr ("Step Into"), sc_main_debug_step_into);
167     init (tr ("Step Out"), sc_main_debug_step_out);
168     init (tr ("Continue"), sc_main_debug_continue);
169     init (tr ("Quit Debug Mode"), sc_main_debug_quit);
170 
171     // window
172     init (tr ("Show Command Window"), sc_main_window_show_command);
173     init (tr ("Show Command History"), sc_main_window_show_history);
174     init (tr ("Show File Browser"), sc_main_window_show_file_browser);
175     init (tr ("Show Workspace"), sc_main_window_show_workspace);
176     init (tr ("Show Editor"), sc_main_window_show_editor);
177     init (tr ("Show Documentation"), sc_main_window_show_doc);
178     init (tr ("Show Variable Editor"), sc_main_window_show_variable_editor);
179     init (tr ("Command Window"), sc_main_window_command);
180     init (tr ("Command History"), sc_main_window_history);
181     init (tr ("File Browser"), sc_main_window_file_browser);
182     init (tr ("Workspace"), sc_main_window_workspace);
183     init (tr ("Editor"), sc_main_window_editor);
184     init (tr ("Documentation"), sc_main_window_doc);
185     init (tr ("Variable Editor"), sc_main_window_variable_editor);
186     init (tr ("Previous Widget"), sc_main_window_previous_dock);
187     init (tr ("Reset Default Window Layout"), sc_main_window_reset);
188 
189     // help
190     init (tr ("Show Ondisk Documentation"), sc_main_help_ondisk_doc);
191     init (tr ("Show Online Documentation"), sc_main_help_online_doc);
192     init (tr ("Report Bug"), sc_main_help_report_bug);
193     init (tr ("Octave Packages"), sc_main_help_packages);
194     init (tr ("Contribute to Octave"), sc_main_help_contribute);
195     init (tr ("Octave Developer Resources"), sc_main_help_developer);
196     init (tr ("About Octave"), sc_main_help_about);
197 
198     // news
199     init (tr ("Release Notes"), sc_main_news_release_notes);
200     init (tr ("Community News"), sc_main_news_community_news);
201 
202     // Tab handling
203     // The following shortcuts are moved into a separate tab.  The key names
204     // are not changed, to preserve compatibility with older versions.
205     init (tr ("Close Tab"), sc_edit_file_close);
206     init (tr ("Close All Tabs"), sc_edit_file_close_all);
207     init (tr ("Close Other Tabs"), sc_edit_file_close_other);
208     init (tr ("Switch to Left Tab"), sc_edit_tabs_switch_left_tab);
209     init (tr ("Switch to Right Tab"), sc_edit_tabs_switch_right_tab);
210     init (tr ("Move Tab Left"), sc_edit_tabs_move_tab_left);
211     init (tr ("Move Tab Right"), sc_edit_tabs_move_tab_right);
212 
213     // Zooming
214     init (tr ("Zoom In"), sc_edit_view_zoom_in);
215     init (tr ("Zoom Out"), sc_edit_view_zoom_out);
216 #if defined (Q_OS_MAC)
217     init (tr ("Zoom Normal"), sc_edit_view_zoom_normal);
218 #else
219     init (tr ("Zoom Normal"), sc_edit_view_zoom_normal);
220 #endif
221 
222     // actions of the editor
223 
224     // file
225     init (tr ("Edit Function"), sc_edit_file_edit_function);
226     init (tr ("Save File"), sc_edit_file_save);
227     init (tr ("Save File As"), sc_edit_file_save_as);
228     init (tr ("Print"), sc_edit_file_print);
229 
230     // edit
231     init (tr ("Redo"), sc_edit_edit_redo);
232     init (tr ("Cut"), sc_edit_edit_cut);
233     init (tr ("Find and Replace"), sc_edit_edit_find_replace);
234     init (tr ("Find Next"), sc_edit_edit_find_next);
235     init (tr ("Find Previous"), sc_edit_edit_find_previous);
236     init (tr ("Delete to Start of Word"), sc_edit_edit_delete_start_word);
237     init (tr ("Delete to End of Word"), sc_edit_edit_delete_end_word);
238     init (tr ("Delete to Start of Line"), sc_edit_edit_delete_start_line);
239     init (tr ("Delete to End of Line"), sc_edit_edit_delete_end_line);
240     init (tr ("Delete Line"), sc_edit_edit_delete_line);
241     init (tr ("Copy Line"), sc_edit_edit_copy_line);
242     init (tr ("Cut Line"), sc_edit_edit_cut_line);
243     init (tr ("Duplicate Selection/Line"), sc_edit_edit_duplicate_selection);
244     init (tr ("Transpose Line"), sc_edit_edit_transpose_line);
245     init (tr ("Show Completion List"), sc_edit_edit_completion_list);
246 
247     init (tr ("Comment Selection"), sc_edit_edit_comment_selection);
248     init (tr ("Uncomment Selection"), sc_edit_edit_uncomment_selection);
249     init (tr ("Comment Selection (Choosing String)"), sc_edit_edit_comment_var_selection);
250     init (tr ("Uppercase Selection"), sc_edit_edit_upper_case);
251     init (tr ("Lowercase Selection"), sc_edit_edit_lower_case);
252 
253 #if defined (Q_OS_MAC)
254     init (tr ("Indent Selection Rigidly"), sc_edit_edit_indent_selection);
255     init (tr ("Unindent Selection Rigidly"), sc_edit_edit_unindent_selection);
256 #else
257     init (tr ("Indent Selection Rigidly"), sc_edit_edit_indent_selection);
258     init (tr ("Unindent Selection Rigidly"), sc_edit_edit_unindent_selection);
259 #endif
260     init (tr ("Indent Code"), sc_edit_edit_smart_indent_line_or_selection);
261 
262     init (tr ("Convert Line Endings to Windows"), sc_edit_edit_conv_eol_winows);
263     init (tr ("Convert Line Endings to Unix"), sc_edit_edit_conv_eol_unix);
264     init (tr ("Convert Line Endings to Mac"), sc_edit_edit_conv_eol_mac);
265 
266     init (tr ("Goto Line"), sc_edit_edit_goto_line);
267     init (tr ("Move to Matching Brace"), sc_edit_edit_move_to_brace);
268     init (tr ("Select to Matching Brace"), sc_edit_edit_select_to_brace);
269     init (tr ("Toggle Bookmark"), sc_edit_edit_toggle_bookmark);
270     init (tr ("Next Bookmark"), sc_edit_edit_next_bookmark);
271     init (tr ("Previous Bookmark"), sc_edit_edit_previous_bookmark);
272     init (tr ("Remove All Bookmark"), sc_edit_edit_remove_bookmark);
273 
274     init (tr ("Preferences"), sc_edit_edit_preferences);
275     init (tr ("Styles Preferences"), sc_edit_edit_styles_preferences);
276 
277     // view
278     init (tr ("Show Line Numbers"), sc_edit_view_show_line_numbers);
279     init (tr ("Show Whitespace Characters"), sc_edit_view_show_white_spaces);
280     init (tr ("Show Line Endings"), sc_edit_view_show_eol_chars);
281     init (tr ("Show Indentation Guides"), sc_edit_view_show_ind_guides);
282     init (tr ("Show Long Line Marker"), sc_edit_view_show_long_line);
283     init (tr ("Show Toolbar"), sc_edit_view_show_toolbar);
284     init (tr ("Show Statusbar"), sc_edit_view_show_statusbar);
285     init (tr ("Show Horizontal Scrollbar"), sc_edit_view_show_hscrollbar);
286     init (tr ("Sort Tabs Alphabetically"), sc_edit_view_sort_tabs);
287 
288     // debug
289     init (tr ("Toggle Breakpoint"), sc_edit_debug_toggle_breakpoint);
290     init (tr ("Next Breakpoint"), sc_edit_debug_next_breakpoint);
291     init (tr ("Previous Breakpoint"), sc_edit_debug_previous_breakpoint);
292     init (tr ("Remove All Breakpoints"), sc_edit_debug_remove_breakpoints);
293 
294     // run
295     init (tr ("Run File"), sc_edit_run_run_file);
296     init (tr ("Run Selection"), sc_edit_run_run_selection);
297 
298     // help
299     init (tr ("Help on Keyword"), sc_edit_help_help_keyword);
300     init (tr ("Document on Keyword"), sc_edit_help_doc_keyword);
301 
302 
303     // Documentation browser
304     init (tr ("Go to Homepage"), sc_doc_go_home);
305     init (tr ("Go Back one Page"), sc_doc_go_back);
306     init (tr ("Go Forward one Page"), sc_doc_go_next);
307   }
308 
309   // write one or all actual shortcut set(s) into a settings file
write_shortcuts(gui_settings * settings,bool closing)310   void shortcut_manager::write_shortcuts (gui_settings *settings,
311                                           bool closing)
312   {
313     bool sc_ctrld = false;
314 
315     QString sc_main = sc_main_file.mid (0, sc_main_file.indexOf ('_') + 1);
316 
317     for (int i = 0; i < m_sc.count (); i++)  // loop over all shortcuts
318       {
319         settings->setValue (sc_group + "/" + m_sc.at (i).m_settings_key,
320                             m_sc.at (i).m_actual_sc.toString ());
321         // special: check main-window for Ctrl-D (Terminal)
322         if (m_sc.at (i).m_settings_key.startsWith (sc_main)
323             && m_sc.at (i).m_actual_sc == QKeySequence (Qt::ControlModifier+Qt::Key_D))
324           sc_ctrld = true;
325       }
326 
327     settings->setValue (sc_main_ctrld.key, sc_ctrld);
328 
329     if (closing)
330       {
331         delete m_dialog;     // the dialog for key sequences can be removed now
332         m_dialog = nullptr;  // make sure it is zero again
333       }
334 
335     settings->sync ();      // sync the settings file
336   }
337 
set_shortcut(QAction * action,const sc_pref & scpref)338   void shortcut_manager::set_shortcut (QAction *action, const sc_pref& scpref)
339   {
340     int index;
341 
342     index = m_action_hash[scpref.key] - 1;
343 
344     if (index > -1 && index < m_sc.count ())
345       {
346         resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
347         gui_settings *settings = rmgr.get_settings ();
348         action->setShortcut (QKeySequence (settings->sc_value (scpref)));
349       }
350     else
351       qDebug () << "Key: " << scpref.key << " not found in m_action_hash";
352   }
353 
shortcut(QShortcut * sc,const sc_pref & scpref)354   void shortcut_manager::shortcut (QShortcut *sc, const sc_pref& scpref)
355   {
356     int index;
357 
358     index = m_action_hash[scpref.key] - 1;
359 
360     if (index > -1 && index < m_sc.count ())
361       {
362         resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
363         gui_settings *settings = rmgr.get_settings ();
364         sc->setKey (QKeySequence (settings->sc_value (scpref)));
365       }
366     else
367       qDebug () << "Key: " << scpref.key << " not found in m_action_hash";
368   }
369 
fill_treewidget(QTreeWidget * tree_view)370   void shortcut_manager::fill_treewidget (QTreeWidget *tree_view)
371   {
372     m_dialog = nullptr;
373     m_level_hash.clear ();
374 
375 #if defined (HAVE_QHEADERVIEW_SETSECTIONRESIZEMODE)
376     tree_view->header ()->setSectionResizeMode (QHeaderView::ResizeToContents);
377 #else
378     tree_view->header ()->setResizeMode (QHeaderView::ResizeToContents);
379 #endif
380 
381     QTreeWidgetItem *main = new QTreeWidgetItem (tree_view);
382     main->setText (0, tr ("Global"));
383     main->setExpanded (true);
384     QTreeWidgetItem *main_file = new QTreeWidgetItem (main);
385     main_file->setText (0, tr ("File Menu"));
386     QTreeWidgetItem *main_edit = new QTreeWidgetItem (main);
387     main_edit->setText (0, tr ("Edit Menu"));
388     QTreeWidgetItem *main_debug = new QTreeWidgetItem (main);
389     main_debug->setText (0, tr ("Debug Menu"));
390     QTreeWidgetItem *main_window = new QTreeWidgetItem (main);
391     main_window->setText (0, tr ("Window Menu"));
392     QTreeWidgetItem *main_help = new QTreeWidgetItem (main);
393     main_help->setText (0, tr ("Help Menu"));
394     QTreeWidgetItem *main_news = new QTreeWidgetItem (main);
395     main_news->setText (0, tr ("News Menu"));
396     QTreeWidgetItem *main_dock_widgets = new QTreeWidgetItem (main);
397     main_dock_widgets->setText (0, tr ("Handling of Dock Widgets"));
398     QTreeWidgetItem *main_tabs = new QTreeWidgetItem (main);
399     main_tabs->setText (0, tr ("Tab Handling in Dock Widgets"));
400     QTreeWidgetItem *main_find = new QTreeWidgetItem (main);
401     main_find->setText (0, tr ("Find & Replace in Dock Widgets"));
402     QTreeWidgetItem *main_zoom = new QTreeWidgetItem (main);
403     main_zoom->setText (0, tr ("Zooming in Editor and Documentation"));
404 
405     m_level_hash[sc_main_file]   = main_file;
406     m_level_hash[sc_main_edit]   = main_edit;
407     m_level_hash[sc_main_debug]   = main_debug;
408     m_level_hash[sc_main_window]   = main_window;
409     m_level_hash[sc_main_help]   = main_help;
410     m_level_hash[sc_main_news]   = main_news;
411     m_level_hash[sc_dock_widget] = main_dock_widgets;
412     m_level_hash[sc_edit_tabs]   = main_tabs;
413     m_level_hash[sc_edit_find]   = main_find;
414     m_level_hash[sc_edit_zoom]   = main_zoom;
415 
416     QTreeWidgetItem *editor = new QTreeWidgetItem (tree_view);
417     editor->setText (0, tr ("Editor"));
418     editor->setExpanded (true);
419     QTreeWidgetItem *editor_file = new QTreeWidgetItem (editor);
420     editor_file->setText (0, tr ("File Menu"));
421     QTreeWidgetItem *editor_edit = new QTreeWidgetItem (editor);
422     editor_edit->setText (0, tr ("Edit Menu"));
423     QTreeWidgetItem *editor_view = new QTreeWidgetItem (editor);
424     editor_view->setText (0, tr ("View Menu"));
425     QTreeWidgetItem *editor_debug = new QTreeWidgetItem (editor);
426     editor_debug->setText (0, tr ("Debug Menu"));
427     QTreeWidgetItem *editor_run = new QTreeWidgetItem (editor);
428     editor_run->setText (0, tr ("Run Menu"));
429     QTreeWidgetItem *editor_help = new QTreeWidgetItem (editor);
430     editor_help->setText (0, tr ("Help Menu"));
431 
432     m_level_hash[sc_edit_file] = editor_file;
433     m_level_hash[sc_edit_edit] = editor_edit;
434     m_level_hash[sc_edit_view] = editor_view;
435     m_level_hash[sc_edit_debug] = editor_debug;
436     m_level_hash[sc_edit_run] = editor_run;
437     m_level_hash[sc_edit_help] = editor_help;
438 
439     QTreeWidgetItem *doc = new QTreeWidgetItem (tree_view);
440     doc->setText (0, tr ("Documentation Viewer"));
441     doc->setExpanded (true);
442 
443     QTreeWidgetItem *doc_browser = new QTreeWidgetItem (doc);
444     doc_browser->setText (0, tr ("Browser"));
445 
446     m_level_hash[sc_doc] = doc_browser;
447 
448     connect (tree_view, SIGNAL (itemDoubleClicked (QTreeWidgetItem*, int)),
449              this, SLOT (handle_double_clicked (QTreeWidgetItem*, int)));
450 
451     for (int i = 0; i < m_sc.count (); i++)
452       {
453         shortcut_t sc = m_sc.at (i);
454 
455         QTreeWidgetItem *section = m_level_hash[sc.m_settings_key.section (':',0,0)];
456 
457         // handle sections which have changed and do not correspond to the
458         // previously defined keyname
459         if (section == editor_file)
460           {
461             // Closing tabs now in global tab handling section
462             if (sc.m_settings_key.contains (sc_edit_file_cl))
463               section = main_tabs;
464           }
465         if (section == editor_edit)
466           {
467             // Find & replace now in global file & replace handling section
468             if (sc.m_settings_key.contains (sc_edit_edit_find))
469               section = main_find;
470           }
471         if (section == editor_view)
472           {
473             // Zooming now in global zoom handling section
474             if (sc.m_settings_key.contains (sc_edit_view_zoom))
475               section = main_zoom;
476           }
477 
478         QTreeWidgetItem *tree_item = new QTreeWidgetItem (section);
479 
480         // set a slightly transparent foreground for default columns
481         QColor fg = QColor (tree_item->foreground (1).color ());
482         fg.setAlpha (128);
483         tree_item->setForeground (1, QBrush (fg));
484 
485         // write the shortcuts
486         tree_item->setText (0, sc.m_description);
487         tree_item->setText (1, sc.m_default_sc.toString ());
488         tree_item->setText (2, sc.m_actual_sc.toString ());
489 
490         m_item_index_hash[tree_item] = i + 1; // index+1 to avoid 0
491         m_index_item_hash[i] = tree_item;
492       }
493   }
494 
495   // import or export of shortcut sets,
496   // called from settings dialog when related buttons are clicked;
497   // returns true on success, false otherwise
498   bool
import_export(int action)499   shortcut_manager::import_export (int action)
500   {
501     // ask to save the current shortcuts, maybe abort import
502     if (action == OSC_DEFAULT || action == OSC_IMPORT)
503       {
504         if (! overwrite_all_shortcuts ())
505           return false;
506       }
507 
508     // get the filename to read or write the shortcuts,
509     // the default extension is .osc (octave shortcuts)
510     if (action != OSC_DEFAULT)
511       {
512         QString file;
513 
514         // FIXME: Remove, if for all common KDE versions (bug #54607) is resolved.
515         int opts = 0;  // No options by default.
516         resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
517         gui_settings *settings = rmgr.get_settings ();
518         if (! settings->value (global_use_native_dialogs).toBool ())
519           opts = QFileDialog::DontUseNativeDialog;
520 
521         if (action == OSC_IMPORT)
522           file = QFileDialog::getOpenFileName (this,
523                                                tr ("Import shortcuts from file..."), QString (),
524                                                tr ("Octave Shortcut Files (*.osc);;All Files (*)"),
525                                                nullptr, QFileDialog::Option (opts));
526         else if (action == OSC_EXPORT)
527           file = QFileDialog::getSaveFileName (this,
528                                                tr ("Export shortcuts to file..."), QString (),
529                                                tr ("Octave Shortcut Files (*.osc);;All Files (*)"),
530                                                nullptr, QFileDialog::Option (opts));
531 
532         if (file.isEmpty ())
533           return false;
534 
535         gui_settings osc_settings (file, QSettings::IniFormat);
536 
537         if (osc_settings.status () !=  QSettings::NoError)
538           {
539             qWarning () << tr ("Failed to open %1 as Octave shortcut file")
540                         .arg (file);
541             return false;
542           }
543         else
544           {
545             if (action == OSC_IMPORT)
546               import_shortcuts (&osc_settings);   // import (special action)
547             else if (action == OSC_EXPORT)
548               write_shortcuts (&osc_settings, false); // export, (save settings)
549           }
550       }
551     else
552       {
553         import_shortcuts (nullptr);
554       }
555 
556     return true;
557   }
558 
handle_double_clicked(QTreeWidgetItem * item,int col)559   void shortcut_manager::handle_double_clicked (QTreeWidgetItem *item, int col)
560   {
561     if (col != 2)
562       return;
563 
564     int i = m_item_index_hash[item];
565     if (i == 0)
566       return;  // top-level-item clicked
567 
568     shortcut_dialog (i-1); // correct to index starting at 0
569   }
570 
shortcut_dialog_finished(int result)571   void shortcut_manager::shortcut_dialog_finished (int result)
572   {
573     if (result == QDialog::Rejected)
574       return;
575 
576     // check for duplicate
577     int double_index = m_shortcut_hash[m_edit_actual->text ()] - 1;
578 
579     if (double_index >= 0 && double_index != m_handled_index)
580       {
581         int ret = QMessageBox::warning (this, tr ("Double Shortcut"),
582                                         tr ("The chosen shortcut\n  \"%1\"\n"
583                                             "is already used for the action\n  \"%2\".\n"
584                                             "Do you want to use the shortcut anyhow removing it "
585                                             "from the previous action?")
586                                         .arg (m_edit_actual->text ())
587                                         .arg (m_sc.at (double_index).m_description),
588                                         QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
589 
590         if (ret == QMessageBox::Yes)
591           {
592             shortcut_t double_shortcut = m_sc.at (double_index);
593             double_shortcut.m_actual_sc = QKeySequence ();
594             m_sc.replace (double_index, double_shortcut);
595             m_index_item_hash[double_index]->setText (2, QString ());
596           }
597         else
598           return;
599       }
600 
601     shortcut_t shortcut = m_sc.at (m_handled_index);
602     if (! shortcut.m_actual_sc.isEmpty ())
603       m_shortcut_hash.remove (shortcut.m_actual_sc.toString ());
604     shortcut.m_actual_sc = m_edit_actual->text ();
605     m_sc.replace (m_handled_index, shortcut);
606 
607     m_index_item_hash[m_handled_index]->setText (2, shortcut.m_actual_sc.toString ());
608 
609     if (! shortcut.m_actual_sc.isEmpty ())
610       m_shortcut_hash[shortcut.m_actual_sc.toString ()] = m_handled_index + 1;
611   }
612 
shortcut_dialog_set_default(void)613   void shortcut_manager::shortcut_dialog_set_default (void)
614   {
615     m_edit_actual->setText (m_label_default->text ());
616   }
617 
init(const QString & description,const sc_pref & sc)618   void shortcut_manager::init (const QString& description, const sc_pref& sc)
619   {
620     resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
621     gui_settings *settings = rmgr.get_settings ();
622 
623     QKeySequence actual = QKeySequence (settings->sc_value (sc));
624 
625     // append the new shortcut to the list
626     shortcut_t shortcut_info;
627     shortcut_info.m_description = description;
628     shortcut_info.m_settings_key = sc.key;
629     shortcut_info.m_actual_sc = actual;
630     shortcut_info.m_default_sc = settings->sc_def_value (sc);
631     m_sc << shortcut_info;
632 
633     // insert shortcut in order to check for duplicates later
634     if (! actual.isEmpty ())
635       m_shortcut_hash[actual.toString ()] = m_sc.count ();
636     m_action_hash[sc.key] = m_sc.count ();
637 
638     // check whether ctrl+d is used from main window, i.e. is a global shortcut
639     QString main_group_prefix
640       = sc_main_file.mid (0, sc_main_file.indexOf ('_') + 1);
641     if (sc.key.startsWith (main_group_prefix)
642         && actual == QKeySequence (Qt::ControlModifier+Qt::Key_D))
643       settings->setValue (sc_main_ctrld.key, true);
644   }
645 
shortcut_dialog(int index)646   void shortcut_manager::shortcut_dialog (int index)
647   {
648     if (! m_dialog)
649       {
650         m_dialog = new QDialog (this);
651 
652         m_dialog->setWindowTitle (tr ("Enter new Shortcut"));
653 
654         QVBoxLayout *box = new QVBoxLayout (m_dialog);
655         box->setSpacing (2);
656         box->setContentsMargins (12, 12, 12, 12);
657 
658         QLabel *help = new QLabel (tr ("Apply the desired shortcut or click "
659                                        "on the right button to reset the "
660                                        "shortcut to its default."));
661         help->setWordWrap (true);
662         box->addWidget (help);
663 
664         QCheckBox *direct
665           = new QCheckBox (tr ("Enter shortcut directly by performing it"));
666 
667         QCheckBox *shift
668           = new QCheckBox (tr ("Add Shift modifier\n"
669                                "(allows to enter number keys)"));
670 
671         shift->setStyleSheet
672           ("QCheckBox::indicator { subcontrol-position: left top; }");
673 
674         connect (direct, SIGNAL (clicked (bool)),
675                  shift, SLOT (setEnabled (bool)));
676 
677         direct->setCheckState (Qt::Checked);
678 
679         box->addWidget (direct);
680         box->addWidget (shift);
681 
682         box->addSpacing (15);
683 
684         QGridLayout *grid = new QGridLayout ();
685 
686         QLabel *actual = new QLabel (tr ("Actual shortcut"));
687         m_edit_actual = new enter_shortcut (m_dialog);
688         m_edit_actual->setAlignment (Qt::AlignHCenter);
689         grid->addWidget (actual, 0, 0);
690         grid->addWidget (m_edit_actual, 0, 1);
691 
692         QLabel *def = new QLabel (tr ("Default shortcut"));
693         m_label_default = new QLabel (m_dialog);
694         m_label_default->setAlignment (Qt::AlignHCenter);
695         grid->addWidget (def, 1, 0);
696         grid->addWidget (m_label_default, 1, 1);
697 
698         QPushButton *set_default = new QPushButton (tr ("Set to default"));
699         grid->addWidget (set_default, 0, 2);
700         connect (set_default, SIGNAL (clicked ()),
701                  this, SLOT (shortcut_dialog_set_default ()));
702 
703         box->addLayout (grid);
704 
705         box->addSpacing (18);
706 
707         QDialogButtonBox *button_box = new QDialogButtonBox (QDialogButtonBox::Ok
708                                                              | QDialogButtonBox::Cancel);
709         QList<QAbstractButton *> buttons = button_box->buttons ();
710         for (int i = 0; i < buttons.count (); i++)
711           buttons.at (i)->setShortcut (QKeySequence ());
712         connect (button_box, SIGNAL (accepted ()), m_dialog, SLOT (accept ()));
713         connect (button_box, SIGNAL (rejected ()), m_dialog, SLOT (reject ()));
714         box->addWidget (button_box);
715 
716         m_dialog->setLayout (box);
717 
718         connect (direct, SIGNAL (stateChanged (int)),
719                  m_edit_actual, SLOT (handle_direct_shortcut (int)));
720         connect (shift, SIGNAL (stateChanged (int)),
721                  m_edit_actual, SLOT (handle_shift_modifier (int)));
722         connect (m_dialog, SIGNAL (finished (int)),
723                  this, SLOT (shortcut_dialog_finished (int)));
724 
725       }
726 
727     m_edit_actual->setText (m_sc.at (index).m_actual_sc.toString ());
728     m_label_default->setText (m_sc.at (index).m_default_sc.toString ());
729     m_handled_index = index;
730 
731     m_edit_actual->setFocus ();
732     m_dialog->setFocusProxy (m_edit_actual);
733     m_dialog->exec ();
734   }
735 
736   // import a shortcut set from a given settings file or reset to
737   // the defaults (settings = 0) and refresh the tree view
import_shortcuts(gui_settings * settings)738   void shortcut_manager::import_shortcuts (gui_settings *settings)
739   {
740     for (int i = 0; i < m_sc.count (); i++)
741       {
742         // update the list of all shortcuts
743         shortcut_t sc = m_sc.at (i);           // make a copy
744 
745         if (settings)
746           sc.m_actual_sc = QKeySequence (         // get new shortcut from settings
747                                          settings->value (sc_group + sc.m_settings_key,sc.m_actual_sc).
748                                          toString ());       // and use the old one as default
749         else
750           sc.m_actual_sc = QKeySequence (sc.m_default_sc); // get default shortcut
751 
752         m_sc.replace (i,sc);                   // replace the old with the new one
753 
754         // update the tree view
755         QTreeWidgetItem *tree_item = m_index_item_hash[i]; // get related tree item
756         tree_item->setText (2, sc.m_actual_sc.toString ()); // display new shortcut
757       }
758   }
759 
760   // ask the user whether to save the current shortcut set;
761   // returns true to proceed with import action, false to abort it
overwrite_all_shortcuts(void)762   bool shortcut_manager::overwrite_all_shortcuts (void)
763   {
764     QMessageBox msg_box;
765     msg_box.setWindowTitle (tr ("Overwriting Shortcuts"));
766     msg_box.setIcon (QMessageBox::Warning);
767     msg_box.setText (tr ("You are about to overwrite all shortcuts.\n"
768                          "Would you like to save the current shortcut set or cancel the action?"));
769     msg_box.setStandardButtons (QMessageBox::Save | QMessageBox::Cancel);
770     QPushButton *discard = msg_box.addButton (tr ("Don't save"),
771                                               QMessageBox::DestructiveRole);
772     msg_box.setDefaultButton (QMessageBox::Save);
773 
774     int ret = msg_box.exec ();
775 
776     if (msg_box.clickedButton () == discard)
777       return true;  // do not save and go ahead
778 
779     if (ret == QMessageBox::Save)
780       {
781         if (import_export (OSC_EXPORT))
782           return true;  // go ahead
783       }
784 
785     return false; // abort the import
786   }
787 }
788