Repository: spyder-ide/spyder

Issue:
Fix menus in macOS
## Description of your problem

**What steps will reproduce the problem?**
1. Install Spyder 3 on Mac os 10.11, PyQt5

**What is the expected output? What do you see instead?**

There are icons on menu. However, they are disable by defaults, from issue  #2289 
<img width="209" alt="screenshot 2016-09-29 14 28 28" src="https://cloud.githubusercontent.com/assets/9072983/18963852/e080722e-8676-11e6-9239-d6e8574616f8.png">
The question is what I do to bring back the icons on menu?
**Please provide any additional information below**
## Versions and main components
- Spyder Version: 3
- Python Version: 2.7.12
- Operating system: Mac 10.11
## Dependencies

jedi >=0.8.1     :  0.9.0 (OK)
matplotlib >=1.0 :  1.4.3 (OK)
nbconvert >=4.0  :  4.2.0 (OK)
numpy >=1.7      :  1.11.1 (OK)
pandas >=0.13.1  :  0.18.1 (OK)
pep8 >=0.6       :  0.6.1 (OK)
psutil >=0.3     :  4.3.0 (OK)
pyflakes >=0.5.0 :  0.5.0 (OK)
pygments >=2.0   :  2.1.3 (OK)
pylint >=0.25    :  1.5.4 (OK)
qtconsole >=4.2.0:  4.2.1 (OK)
rope >=0.9.4     :  0.9.4 (OK)
sphinx >=0.6.6   :  1.4.1 (OK)
sympy >=0.7.3    :  1.0 (OK)



Discussion:
@tienhv, it seems you're new to macOS. As all other Mac applications, Spyder tries to adhere to the [Human Interface Guidelines](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/OSXHIGuidelines/MenuAppearanceBehavior.html#//apple_ref/doc/uid/20000957-CH23-SW1) of Apple, which don't allow icons in menus. So please stop requesting that we do it because we want Spyder to look as well as all other Mac applications :-)

What we can do instead is fixing the odd placement of some menu entries. And that's what @goanpeca is going to do in a future release :-)


Resolving patch:
diff --git a/spyder/utils/qthelpers.py b/spyder/utils/qthelpers.py
--- a/spyder/utils/qthelpers.py
+++ b/spyder/utils/qthelpers.py
@@ -16,10 +16,10 @@
 from qtpy.compat import to_qvariant, from_qvariant
 from qtpy.QtCore import (QEvent, QLibraryInfo, QLocale, QObject, Qt, QTimer,
                          QTranslator, Signal, Slot)
-from qtpy.QtGui import QKeyEvent, QKeySequence, QPixmap
+from qtpy.QtGui import QIcon, QKeyEvent, QKeySequence, QPixmap
 from qtpy.QtWidgets import (QAction, QApplication, QHBoxLayout, QLabel,
-                            QLineEdit, QMenu, QStyle, QToolButton, QVBoxLayout,
-                            QWidget)
+                            QLineEdit, QMenu, QStyle, QToolBar, QToolButton,
+                            QVBoxLayout, QWidget)
 
 # Local imports
 from spyder.config.base import get_image_path, running_in_mac_app
@@ -237,7 +237,7 @@ def create_action(parent, text, shortcut=None, icon=None, tip=None,
                   toggled=None, triggered=None, data=None, menurole=None,
                   context=Qt.WindowShortcut):
     """Create a QAction"""
-    action = QAction(text, parent)
+    action = SpyderAction(text, parent)
     if triggered is not None:
         action.triggered.connect(triggered)
     if toggled is not None:
@@ -285,7 +285,7 @@ def add_shortcut_to_tooltip(action, context, name):
 
 
 def add_actions(target, actions, insert_before=None):
-    """Add actions to a menu"""
+    """Add actions to a QMenu or a QToolBar."""
     previous_action = None
     target_actions = list(target.actions())
     if target_actions:
@@ -303,7 +303,9 @@ def add_actions(target, actions, insert_before=None):
                 target.addMenu(action)
             else:
                 target.insertMenu(insert_before, action)
-        elif isinstance(action, QAction):
+        elif isinstance(action, SpyderAction):
+            if isinstance(target, QMenu) or not isinstance(target, QToolBar):
+                action = action.no_icon_action
             if insert_before is None:
                 target.addAction(action)
             else:
@@ -416,6 +418,48 @@ def get_filetype_icon(fname):
         ext = ext[1:]
     return get_icon( "%s.png" % ext, ima.icon('FileIcon') )
 
+    
+class SpyderAction(QAction):
+    """Spyder QAction class wrapper to handle cross platform patches."""
+
+    def __init__(self, *args, **kwargs):
+        """Spyder QAction class wrapper to handle cross platform patches."""
+        super(SpyderAction, self).__init__(*args, **kwargs)
+        self._action_no_icon = None
+
+        if sys.platform == 'darwin':
+            self._action_no_icon = QAction(*args, **kwargs)
+            self._action_no_icon.setIcon(QIcon())
+            self._action_no_icon.triggered.connect(self.triggered)
+            self._action_no_icon.toggled.connect(self.toggled)
+            self._action_no_icon.changed.connect(self.changed)
+            self._action_no_icon.hovered.connect(self.hovered)
+        else:
+            self._action_no_icon = self
+
+    def __getattribute__(self, name):
+        """Intercept method calls and apply to both actions, except signals."""
+        attr = super(SpyderAction, self).__getattribute__(name)
+
+        if hasattr(attr, '__call__') and name not in ['triggered', 'toggled',
+                                                      'changed', 'hovered']:
+            def newfunc(*args, **kwargs):
+                result = attr(*args, **kwargs)
+                if name not in ['setIcon']:
+                    action_no_icon = self.__dict__['_action_no_icon']
+                    attr_no_icon = super(QAction,
+                                         action_no_icon).__getattribute__(name)
+                    attr_no_icon(*args, **kwargs)
+                return result
+            return newfunc
+        else:
+            return attr
+
+    @property
+    def no_icon_action(self):
+        """Return the action without an Icon."""
+        return self._action_no_icon
+
 
 class ShowStdIcons(QWidget):
     """


Test patch: