Repository: spyder-ide/spyder

Issue:
More easily override conflicted shortcuts
During changing keyboard shortcuts of a function (let's say `func`), after pressing a new (conflicted) shortcut, the "OK" button is disabled, and a message showed that: "The new shortcut conflicts with - func1: short1". Then you have to cancel and find `func1`, and change it to `short2`. Great chances that `short2` will be conflicted to `func2`, then `func2` -> `func3` -> `func4` all the way in.  And go through `func4` -> `func3` -> `func2` -> `func1` all the way out. And finally set the shortcut you like in the first place if you still remember it.

Or you can just go to `func1` and clear the shortcut, and go back to set `func` in the first place.

Which leads to this feature request: an `override` button to override the shortcut. By override I mean if there's `func1: short1`, I want `short1` to `func`, a button to force `short1` to `func` and clear shortcut of `func1`


Discussion:
@jnsebgosselin, what do you think about this one?
With the changes I've made to the shortcut manager in Spyder 4, it is now possible to clear a shortcut. So this:

> Or you can just go to func1 and clear the shortcut, and go back to set func in the first place.

Is now possible.

Being able to override in a single step would be even better. So yeah, I agree with this proposition.

![image](https://user-images.githubusercontent.com/10170372/48926844-59e21680-ee9f-11e8-9f33-4cd4943521ff.png)

Regarding the UI/UX part, I think we could simply change the text of the warning when there is a conflict and allow the override simply by pressing Ok. There is no need for additional button.

![image](https://user-images.githubusercontent.com/10170372/48948623-191be900-ef03-11e8-8152-9a8cbc6f0cd6.png)

The text could be something like:

_The new shortcut conflict with **file switcher**. This will unbind the shortcut from **file switcher** and assign it to **find text**_

Nice solution @jnsebgosselin! I like it!!

Resolving patch:
diff --git a/spyder/preferences/shortcuts.py b/spyder/preferences/shortcuts.py
--- a/spyder/preferences/shortcuts.py
+++ b/spyder/preferences/shortcuts.py
@@ -214,8 +214,6 @@ def setup(self):
         self.label_warning = QLabel()
         self.label_warning.setWordWrap(True)
         self.label_warning.setAlignment(Qt.AlignTop | Qt.AlignLeft)
-        self.label_warning.setMinimumHeight(
-            3 * self.label_warning.sizeHint().height())
 
         self.button_default = QPushButton(_('Default'))
         self.button_ok = QPushButton(_('Ok'))
@@ -281,7 +279,7 @@ def setup(self):
         self.setLayout(layout)
 
         # Signals
-        self.button_ok.clicked.connect(self.accept)
+        self.button_ok.clicked.connect(self.accept_override)
         self.button_clear.clicked.connect(self.unbind_shortcut)
         self.button_cancel.clicked.connect(self.reject)
         self.button_default.clicked.connect(self.set_sequence_to_default)
@@ -405,13 +403,16 @@ def update_warning(self):
             icon = QIcon()
         elif conflicts:
             warning = SEQUENCE_CONFLICT
-            template = '<i>{0}<b>{1}</b></i>'
+            template = '<i>{0}<b>{1}</b>{2}</i>'
             tip_title = _('The new shortcut conflicts with:') + '<br>'
             tip_body = ''
             for s in conflicts:
                 tip_body += ' - {0}: {1}<br>'.format(s.context, s.name)
             tip_body = tip_body[:-4]  # Removing last <br>
-            tip = template.format(tip_title, tip_body)
+            tip_override = '<br>Press <b>OK</b> to unbind '
+            tip_override += 'it' if len(conflicts) == 1 else 'them'
+            tip_override += ' and assign it to <b>{}</b>'.format(self.name)
+            tip = template.format(tip_title, tip_body, tip_override)
             icon = get_std_icon('MessageBoxWarning')
         elif new_sequence in BLACKLIST:
             warning = IN_BLACKLIST
@@ -437,8 +438,12 @@ def update_warning(self):
         self.conflicts = conflicts
 
         self.helper_button.setIcon(icon)
-        self.button_ok.setEnabled(self.warning == NO_WARNING)
+        self.button_ok.setEnabled(
+            self.warning in [NO_WARNING, SEQUENCE_CONFLICT])
         self.label_warning.setText(tip)
+        # Everytime after update warning message, update the label height
+        new_height = self.label_warning.sizeHint().height()
+        self.label_warning.setMaximumHeight(new_height)
 
     def set_sequence_from_str(self, sequence):
         """
@@ -470,6 +475,14 @@ def unbind_shortcut(self):
         self._qsequences = []
         self.accept()
 
+    def accept_override(self):
+        """Unbind all conflicted shortcuts, and accept the new one"""
+        conflicts = self.check_conflicts()
+        if conflicts:
+            for shortcut in conflicts:
+                shortcut.key = ''
+        self.accept()
+
 
 class Shortcut(object):
     """Shortcut convenience class for holding shortcut context, name,


Test patch:
diff --git a/spyder/preferences/tests/test_shorcuts.py b/spyder/preferences/tests/test_shorcuts.py
--- a/spyder/preferences/tests/test_shorcuts.py
+++ b/spyder/preferences/tests/test_shorcuts.py
@@ -114,13 +114,13 @@ def test_clear_back_new_sequence(create_shortcut_editor, qtbot):
     qtbot.mouseClick(shortcut_editor.button_back_sequence, Qt.LeftButton)
     assert shortcut_editor.new_sequence == 'Ctrl+X, A, Ctrl+B'
     assert shortcut_editor.warning == SEQUENCE_CONFLICT
-    assert not shortcut_editor.button_ok.isEnabled()
+    assert shortcut_editor.button_ok.isEnabled()
 
     # Remove second to last key sequence entered.
     qtbot.mouseClick(shortcut_editor.button_back_sequence, Qt.LeftButton)
     assert shortcut_editor.new_sequence == 'Ctrl+X, A'
     assert shortcut_editor.warning == SEQUENCE_CONFLICT
-    assert not shortcut_editor.button_ok.isEnabled()
+    assert shortcut_editor.button_ok.isEnabled()
 
     # Clear all entered key sequences.
     qtbot.mouseClick(shortcut_editor.btn_clear_sequence, Qt.LeftButton)
@@ -140,13 +140,13 @@ def test_sequence_conflict(create_shortcut_editor, qtbot):
     qtbot.keyClick(shortcut_editor, Qt.Key_X, modifier=Qt.ControlModifier)
     assert shortcut_editor.new_sequence == 'Ctrl+X'
     assert shortcut_editor.warning == SEQUENCE_CONFLICT
-    assert not shortcut_editor.button_ok.isEnabled()
+    assert shortcut_editor.button_ok.isEnabled()
 
     # Check that the conflict is detected for a compound of key sequences.
     qtbot.keyClick(shortcut_editor, Qt.Key_X)
     assert shortcut_editor.new_sequence == 'Ctrl+X, X'
     assert shortcut_editor.warning == SEQUENCE_CONFLICT
-    assert not shortcut_editor.button_ok.isEnabled()
+    assert shortcut_editor.button_ok.isEnabled()
 
 
 def test_sequence_single_key(create_shortcut_editor, qtbot):