Repository: spyder-ide/spyder

Issue:
Array editor should have a toolbar
The array editor should have a toolbar, for consistency with the collection editor, object editor and dataframe editor (after PR #20546). As buttons in the toolbar, we could have Edit, Copy, Format, Resize and Toggle Background, which are current actions in the array editor. This would also allow us to remove the buttons for the last three actions at the bottom of the editor.

This was suggested during the weekly meeting.


Discussion:

Resolving patch:
diff --git a/spyder/plugins/variableexplorer/widgets/arrayeditor.py b/spyder/plugins/variableexplorer/widgets/arrayeditor.py
--- a/spyder/plugins/variableexplorer/widgets/arrayeditor.py
+++ b/spyder/plugins/variableexplorer/widgets/arrayeditor.py
@@ -21,24 +21,35 @@
 from qtpy.QtCore import (QAbstractTableModel, QItemSelection, QLocale,
                          QItemSelectionRange, QModelIndex, Qt, Slot)
 from qtpy.QtGui import QColor, QCursor, QDoubleValidator, QKeySequence
-from qtpy.QtWidgets import (QAbstractItemDelegate, QApplication, QCheckBox,
-                            QComboBox, QDialog, QGridLayout, QHBoxLayout,
-                            QInputDialog, QItemDelegate, QLabel, QLineEdit,
-                            QMenu, QMessageBox, QPushButton, QSpinBox,
-                            QStackedWidget, QTableView, QVBoxLayout,
-                            QWidget)
+from qtpy.QtWidgets import (
+    QAbstractItemDelegate, QApplication, QComboBox, QDialog, QGridLayout,
+    QHBoxLayout, QInputDialog, QItemDelegate, QLabel, QLineEdit, QMenu,
+    QMessageBox, QPushButton, QSpinBox, QStackedWidget, QTableView,
+    QVBoxLayout, QWidget)
 from spyder_kernels.utils.nsview import value_to_display
 from spyder_kernels.utils.lazymodules import numpy as np
 
 # Local imports
 from spyder.api.config.fonts import SpyderFontsMixin, SpyderFontType
+from spyder.api.widgets.mixins import SpyderWidgetMixin
+from spyder.api.widgets.toolbars import SpyderToolbar
 from spyder.config.base import _
 from spyder.config.manager import CONF
+from spyder.plugins.variableexplorer.widgets.basedialog import BaseDialog
 from spyder.py3compat import (is_binary_string, is_string, is_text_string,
                               to_binary_string, to_text_string)
 from spyder.utils.icon_manager import ima
 from spyder.utils.qthelpers import add_actions, create_action, keybinding
-from spyder.plugins.variableexplorer.widgets.basedialog import BaseDialog
+from spyder.utils.stylesheet import PANES_TOOLBAR_STYLESHEET
+
+
+class ArrayEditorActions:
+    Copy = 'copy_action'
+    Edit = 'edit_action'
+    Format = 'format_action'
+    Resize = 'resize_action'
+    ToggleBackgroundColor = 'toggle_background_color_action'
+
 
 # Note: string and unicode data types will be formatted with 's' (see below)
 SUPPORTED_FORMATS = {
@@ -581,6 +592,12 @@ def copy(self):
         clipboard = QApplication.clipboard()
         clipboard.setText(cliptxt)
 
+    def edit_item(self):
+        """Edit item"""
+        index = self.currentIndex()
+        if index.isValid():
+            self.edit(index)
+
 
 class ArrayEditorWidget(QWidget):
 
@@ -634,8 +651,11 @@ def change_format(self):
             self.model.set_format_spec(format_spec)
 
 
-class ArrayEditor(BaseDialog):
+class ArrayEditor(BaseDialog, SpyderWidgetMixin):
     """Array Editor Dialog"""
+
+    CONF_SECTION = 'variable_explorer'
+
     def __init__(self, parent=None):
         super().__init__(parent)
 
@@ -755,13 +775,60 @@ def setup_and_check(self, data, title='', readonly=False,
         self.stack.currentChanged.connect(self.current_widget_changed)
         self.layout.addWidget(self.stack, 1, 0)
 
-        # ---- Top row of buttons
-        btn_layout_top = None
+        # ---- Toolbar and actions
+        toolbar = SpyderToolbar(parent=self, title='Editor toolbar')
+        toolbar.setStyleSheet(str(PANES_TOOLBAR_STYLESHEET))
+
+        self.copy_action = self.create_action(
+            ArrayEditorActions.Copy,
+            text=_('Copy'),
+            icon=self.create_icon('editcopy'),
+            triggered=self.arraywidget.view.copy)
+        toolbar.add_item(self.copy_action)
+
+        self.edit_action = self.create_action(
+            ArrayEditorActions.Edit,
+            text=_('Edit'),
+            icon=self.create_icon('edit'),
+            triggered=self.arraywidget.view.edit_item)
+        toolbar.add_item(self.edit_action)
+
+        self.format_action = self.create_action(
+            ArrayEditorActions.Format,
+            text=_('Format'),
+            icon=self.create_icon('format_float'),
+            tip=_('Set format of floating-point numbers'),
+            triggered=self.arraywidget.change_format)
+        self.format_action.setEnabled(is_float(self.arraywidget.data.dtype))
+        toolbar.add_item(self.format_action)
+
+        self.resize_action = self.create_action(
+            ArrayEditorActions.Resize,
+            text=_('Resize'),
+            icon=self.create_icon('collapse_column'),
+            tip=_('Resize columns to contents'),
+            triggered=self.arraywidget.view.resize_to_contents)
+        toolbar.add_item(self.resize_action)
+
+        self.toggle_bgcolor_action = self.create_action(
+            ArrayEditorActions.ToggleBackgroundColor,
+            text=_('Background color'),
+            icon=self.create_icon('background_color'),
+            toggled=lambda state: self.arraywidget.model.bgcolor(state),
+            initial=self.arraywidget.model.bgcolor_enabled)
+        self.toggle_bgcolor_action.setEnabled(
+            self.arraywidget.model.bgcolor_enabled)
+        toolbar.add_item(self.toggle_bgcolor_action)
+
+        toolbar._render()
+        self.layout.addWidget(toolbar, 0, 0)
+
+        # ---- Buttons in bottom left, if any
+        btn_layout = QHBoxLayout()
         if is_record_array or is_masked_array or data.ndim == 3:
-            btn_layout_top = QHBoxLayout()
 
             if is_record_array:
-                btn_layout_top.addWidget(QLabel(_("Record array fields:")))
+                btn_layout.addWidget(QLabel(_("Record array fields:")))
                 names = []
                 for name in data.dtype.names:
                     field = data.dtype.fields[name]
@@ -784,20 +851,20 @@ def setup_and_check(self, data, title='', readonly=False,
 
                 # Adding the widgets to layout
                 label = QLabel(_("Axis:"))
-                btn_layout_top.addWidget(label)
-                btn_layout_top.addWidget(ra_combo)
-                btn_layout_top.addWidget(self.shape_label)
+                btn_layout.addWidget(label)
+                btn_layout.addWidget(ra_combo)
+                btn_layout.addWidget(self.shape_label)
 
                 label = QLabel(_("Index:"))
-                btn_layout_top.addWidget(label)
-                btn_layout_top.addWidget(self.index_spin)
+                btn_layout.addWidget(label)
+                btn_layout.addWidget(self.index_spin)
 
-                btn_layout_top.addWidget(self.slicing_label)
+                btn_layout.addWidget(self.slicing_label)
             else:
                 ra_combo = QComboBox(self)
                 ra_combo.currentIndexChanged.connect(self.stack.setCurrentIndex)
                 ra_combo.addItems(names)
-                btn_layout_top.addWidget(ra_combo)
+                btn_layout.addWidget(ra_combo)
 
             if is_masked_array:
                 label = QLabel(
@@ -806,52 +873,26 @@ def setup_and_check(self, data, title='', readonly=False,
                 label.setToolTip(_("For performance reasons, changes applied "
                                    "to masked arrays won't be reflected in "
                                    "array's data (and vice-versa)."))
-                btn_layout_top.addWidget(label)
-
-            btn_layout_top.addStretch()
-
-        # ---- Bottom row of buttons
-        btn_layout_bottom = QHBoxLayout()
-
-        btn_format = QPushButton(_("Format"))
-        # disable format button for int type
-        btn_format.setEnabled(is_float(self.arraywidget.data.dtype))
-        btn_layout_bottom.addWidget(btn_format)
-        btn_format.clicked.connect(self.arraywidget.change_format)
+                btn_layout.addWidget(label)
 
-        btn_resize = QPushButton(_("Resize"))
-        btn_layout_bottom.addWidget(btn_resize)
-        btn_resize.clicked.connect(self.arraywidget.view.resize_to_contents)
-
-        self.bgcolor = QCheckBox(_('Background color'))
-        self.bgcolor.setEnabled(self.arraywidget.model.bgcolor_enabled)
-        self.bgcolor.setChecked(self.arraywidget.model.bgcolor_enabled)
-        self.bgcolor.stateChanged.connect(
-            lambda state: self.arraywidget.model.bgcolor(state))
-        btn_layout_bottom.addWidget(self.bgcolor)
-
-        btn_layout_bottom.addStretch()
+        # ---- Buttons on bottom right
+        btn_layout.addStretch()
 
         if not readonly:
             self.btn_save_and_close = QPushButton(_('Save and Close'))
             self.btn_save_and_close.setDisabled(True)
             self.btn_save_and_close.clicked.connect(self.accept)
-            btn_layout_bottom.addWidget(self.btn_save_and_close)
+            btn_layout.addWidget(self.btn_save_and_close)
 
         self.btn_close = QPushButton(_('Close'))
         self.btn_close.setAutoDefault(True)
         self.btn_close.setDefault(True)
         self.btn_close.clicked.connect(self.reject)
-        btn_layout_bottom.addWidget(self.btn_close)
+        btn_layout.addWidget(self.btn_close)
 
         # ---- Final layout
-        btn_layout_bottom.setContentsMargins(4, 4, 4, 4)
-        if btn_layout_top is not None:
-            btn_layout_top.setContentsMargins(4, 4, 4, 4)
-            self.layout.addLayout(btn_layout_top, 2, 0)
-            self.layout.addLayout(btn_layout_bottom, 3, 0)
-        else:
-            self.layout.addLayout(btn_layout_bottom, 2, 0)
+        btn_layout.setContentsMargins(4, 4, 4, 4)
+        self.layout.addLayout(btn_layout, 2, 0)
 
         # Set minimum size
         self.setMinimumSize(500, 300)
@@ -872,7 +913,8 @@ def save_and_close_enable(self, left_top, bottom_right):
     def current_widget_changed(self, index):
         self.arraywidget = self.stack.widget(index)
         self.arraywidget.model.dataChanged.connect(self.save_and_close_enable)
-        self.bgcolor.setChecked(self.arraywidget.model.bgcolor_enabled)
+        self.toggle_bgcolor_action.setChecked(
+            self.arraywidget.model.bgcolor_enabled)
 
     def change_active_widget(self, index):
         """
diff --git a/spyder/utils/icon_manager.py b/spyder/utils/icon_manager.py
--- a/spyder/utils/icon_manager.py
+++ b/spyder/utils/icon_manager.py
@@ -213,6 +213,8 @@ def __init__(self):
             'collapse_column':         [('mdi.arrow-collapse-horizontal',), {'color': self.MAIN_FG_COLOR}],
             'collapse_row':            [('mdi.arrow-collapse-vertical',), {'color': self.MAIN_FG_COLOR}],
             'edit_remove':             [('mdi.minus',), {'color': self.MAIN_FG_COLOR}],
+            'format_float':            [('mdi.decimal-increase',), {'color': self.MAIN_FG_COLOR}],
+            'background_color':        [('mdi.format-color-fill',), {'color': self.MAIN_FG_COLOR}],
             'browse_tab':              [('mdi.tab',), {'color': self.MAIN_FG_COLOR}],
             'filelist':                [('mdi.view-list',), {'color': self.MAIN_FG_COLOR}],
             'newwindow':               [('mdi.window-maximize',), {'color': self.MAIN_FG_COLOR}],


Test patch: