diff --git a/spyder/api/plugins.py b/spyder/api/plugins.py index 80f62f4d3e3..5945d29eb0c 100644 --- a/spyder/api/plugins.py +++ b/spyder/api/plugins.py @@ -24,6 +24,7 @@ # Standard library imports from collections import OrderedDict import inspect +import logging import os # Third party imports @@ -51,6 +52,7 @@ # Localization _ = get_translation('spyder') +logger = logging.getLogger(__name__) # ============================================================================= @@ -816,8 +818,9 @@ def __init__(self, parent, configuration=None): # ---------------------------------------------------------------- try: container._setup(options=options) - except AttributeError: - pass + except Exception as error: + logger.debug( + "Running `_setup` on {0}: {1}".format(self, error)) if isinstance(container, SpyderWidgetMixin): container.setup(options=options) @@ -1602,14 +1605,10 @@ def __init__(self, parent, configuration): widget.set_icon(self.get_icon()) widget.set_name(self.NAME) - # TODO: Streamline this by moving to postvisible setup # Render all toolbars as a final separate step on the main window # in case some plugins want to extend a toolbar. Since the rendering # can only be done once! - widget.get_main_toolbar()._render() - for __, toolbars in widget._aux_toolbars.items(): - for toolbar in toolbars: - toolbar._render() + widget.render_toolbars() # Default Signals # -------------------------------------------------------------------- diff --git a/spyder/api/widgets/__init__.py b/spyder/api/widgets/__init__.py index 0c8ea0ccceb..a77f29391c3 100644 --- a/spyder/api/widgets/__init__.py +++ b/spyder/api/widgets/__init__.py @@ -20,6 +20,7 @@ # Standard library imports from collections import OrderedDict +import os import sys import textwrap import uuid @@ -27,17 +28,18 @@ # Third party imports from qtpy.QtCore import QSize, Qt, Signal, Slot from qtpy.QtGui import QIcon -from qtpy.QtWidgets import QMainWindow, QSizePolicy, QToolButton, QWidget +from qtpy.QtWidgets import (QHBoxLayout, QSizePolicy, QToolButton, QVBoxLayout, + QWidget) import qdarkstyle # Local imports from spyder.api.exceptions import SpyderAPIError from spyder.api.translations import get_translation -from spyder.api.widgets.mixins import SpyderToolBarMixin, SpyderWidgetMixin from spyder.api.widgets.auxiliary_widgets import (MainCornerWidget, SpyderWindowWidget) from spyder.api.widgets.menus import (MainWidgetMenu, OptionsMenuSections, PluginMainWidgetMenus, SpyderMenu) +from spyder.api.widgets.mixins import SpyderToolBarMixin, SpyderWidgetMixin from spyder.api.widgets.toolbars import MainWidgetToolbar from spyder.config.gui import is_dark_interface from spyder.utils.qthelpers import (add_actions, create_waitspinner, @@ -45,7 +47,6 @@ from spyder.widgets.dock import SpyderDockWidget from spyder.widgets.tabs import Tabs - # Localization _ = get_translation('spyder') @@ -191,7 +192,7 @@ def on_option_update(self, option, value): 'method!') -class PluginMainWidget(QMainWindow, SpyderWidgetMixin, SpyderToolBarMixin): +class PluginMainWidget(QWidget, SpyderWidgetMixin, SpyderToolBarMixin): """ Spyder plugin main widget class. @@ -212,6 +213,27 @@ class PluginMainWidget(QMainWindow, SpyderWidgetMixin, SpyderToolBarMixin): """ DEFAULT_OPTIONS = {} + # --- Attributes + # ------------------------------------------------------------------------ + ENABLE_SPINNER = False + """ + This attribute enables/disables showing a spinner on the top right to the + left of the corner menu widget (Hamburguer menu). + + Plugins that provide actions that take time should make this `True` and + use accoringly with the `start_spinner`/`stop_spinner` methods. + + The Find in files plugin is an example of a core plugin that uses it. + + Parameters + ---------- + ENABLE_SPINNER: bool + If `True` an extra space will be added to the toolbar (even if the + spinner is not moving) to avoid items jumping to the left/right when + the spinner appears. If `False` no extra space will be added. Default + is False. + """ + # --- Signals # ------------------------------------------------------------------------ sig_free_memory_requested = Signal() @@ -318,27 +340,67 @@ def __init__(self, name, plugin, parent=None, options=DEFAULT_OPTIONS): self.dock_action = None self.undock_action = None self.close_action = None + self._toolbars_already_rendered = False # We create our toggle action instead of using the one that comes with # dockwidget because it was not possible to raise and focus the plugin self.toggle_view_action = None - self._widgets = {} - self._toolbars = {} + self._toolbars = OrderedDict() + self._auxiliary_toolbars = OrderedDict() # Widgets # -------------------------------------------------------------------- self.windowwidget = None self.dockwidget = None - self._central_widget = QWidget(self) self._icon = QIcon() - self._spinner = create_waitspinner(size=16, parent=self) + self._spinner = None + + if self.ENABLE_SPINNER: + self._spinner = create_waitspinner(size=16, parent=self) + self._corner_widget = MainCornerWidget( parent=self, name=PluginMainWidgetWidgets.CornerWidget, ) - # --- Private Methods --------------------------------------------- - # ----------------------------------------------------------------- + self._main_toolbar = MainWidgetToolbar( + parent=self, + title=_("Main widget toolbar"), + ) + self._main_toolbar.ID = 'main_toolbar' + self._corner_toolbar = MainWidgetToolbar( + parent=self, + title=_("Main widget corner toolbar"), + ) + self._corner_toolbar.ID = 'corner_toolbar', + self._corner_toolbar.setSizePolicy(QSizePolicy.Minimum, + QSizePolicy.Expanding) + self._options_menu = self.create_menu( + PluginMainWidgetMenus.Options, + title=_('Options menu'), + ) + + # Layout + # -------------------------------------------------------------------- + self._main_layout = QVBoxLayout() + self._toolbars_layout = QVBoxLayout() + self._main_toolbar_layout = QHBoxLayout() + + self._toolbars_layout.setContentsMargins(0, 0, 0, 0) + self._toolbars_layout.setSpacing(0) + self._main_toolbar_layout.setContentsMargins(0, 0, 0, 0) + self._main_toolbar_layout.setSpacing(0) + self._main_layout.setContentsMargins(0, 0, 0, 0) + self._main_layout.setSpacing(0) + + # Add inititals layouts + self._main_toolbar_layout.addWidget(self._main_toolbar, stretch=10000) + self._main_toolbar_layout.addWidget(self._corner_toolbar, stretch=1) + self._toolbars_layout.addLayout(self._main_toolbar_layout) + self._main_layout.addLayout(self._toolbars_layout, stretch=1) + + # --- Private Methods + # ------------------------------------------------------------------------ @staticmethod def _find_children(obj, all_children): """ @@ -367,28 +429,22 @@ def _setup(self, options=DEFAULT_OPTIONS): if children: for child in children: self._is_tab = True + # For widgets that use tabs, we add the corner widget using + # the setCornerWidget method. child.setCornerWidget(self._corner_widget) - break - if self._is_tab: - corner_widget = None - else: - corner_widget = self._corner_widget + # This is needed to ensure the corner ToolButton (hamburguer + # menu) is aligned with plugins that use ToolBars vs + # CornerWidgets + # See: spyder-ide/spyder#13600 + # left, top, right, bottom + if os.name == "nt": + self._corner_widget.setContentsMargins(0, 0, 2, 0) + else: + self._corner_widget.setContentsMargins(0, 2, 2, 0) - self._main_toolbar = MainWidgetToolbar( - parent=self, - title='widget_toolbar', - corner_widget=corner_widget, - ) - - if self._is_tab: - # This disables the toolbar on all tabbed plugins - self.get_main_toolbar().setVisible(not self._is_tab) + break - self._options_menu = self.create_menu( - PluginMainWidgetMenus.Options, - text='', - ) self._options_button = self.create_toolbutton( PluginMainWidgetWidgets.OptionsToolButton, text=_('Options'), @@ -399,23 +455,17 @@ def _setup(self, options=DEFAULT_OPTIONS): PluginMainWidgetWidgets.OptionsToolButton, self._options_button, ) - self.add_corner_widget( - PluginMainWidgetWidgets.Spinner, - self._spinner, - ) - - self._toolbars['main'] = self._main_toolbar - - # Setup the a dictionary in which pointers to additional toolbars - # added to the plugin interface are going to be saved. - self._aux_toolbars = {Qt.TopToolBarArea: [], Qt.BottomToolBarArea: []} + if self.ENABLE_SPINNER: + self.add_corner_widget( + PluginMainWidgetWidgets.Spinner, + self._spinner, + ) # Widget setup # -------------------------------------------------------------------- + self._main_toolbar.setVisible(not self._is_tab) + self._corner_toolbar.setVisible(not self._is_tab) self._options_button.setPopupMode(QToolButton.InstantPopup) - self.setWindowFlags(Qt.Widget) - self.addToolBar(self._main_toolbar) - self.addToolBarBreak(Qt.TopToolBarArea) # Create default widget actions self.dock_action = self.create_action( @@ -447,7 +497,6 @@ def _setup(self, options=DEFAULT_OPTIONS): context=Qt.WidgetWithChildrenShortcut, shortcut_context='_', ) - bottom_section = OptionsMenuSections.Bottom for item in [self.undock_action, self.close_action, self.dock_action]: self.add_item_to_menu( @@ -456,7 +505,7 @@ def _setup(self, options=DEFAULT_OPTIONS): section=bottom_section, ) - self._options_button.setMenu(self._options_menu,) + self._options_button.setMenu(self._options_menu) self._options_menu.aboutToShow.connect(self._update_actions) # Hide icons in Mac plugin menus @@ -464,6 +513,15 @@ def _setup(self, options=DEFAULT_OPTIONS): self._options_menu.aboutToHide.connect( lambda menu=self._options_menu: set_menu_icons(menu, False)) + # For widgets that do not use tabs, we add the corner widget to the + # corner toolbar + if not self._is_tab: + self.add_item_to_toolbar( + self._corner_widget, + toolbar=self._corner_toolbar, + section="corner", + ) + # Update title self.setWindowTitle(self.get_title()) self._update_style() @@ -494,6 +552,8 @@ def _update_actions(self): if sys.platform == 'darwin': set_menu_icons(self.get_menu(PluginMainWidgetMenus.Options), True) + # Widget setup + # -------------------------------------------------------------------- self.update_actions() self._update_style() @@ -508,23 +568,13 @@ def _on_top_level_change(self, top_level): # ------------------------------------------------------------------------ def setLayout(self, layout): """ - Set layout of the central widget. - - Notes - ----- - Convenience to use this widget as a normal QWidget. + Set layout of the main widget of this plugin. """ - self._central_widget.setLayout(layout) - self.setCentralWidget(self._central_widget) + self._main_layout.addLayout(layout, stretch=1000000) + super().setLayout(self._main_layout) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) - def layout(self): - """ - Return the central widget's layout. - """ - return self._central_widget.layout() - # --- Public methods to use # ------------------------------------------------------------------------ def get_plugin(self): @@ -590,13 +640,13 @@ def get_actions(self, filter_actions=True): return actions - def add_corner_widget(self, name, widget, before=None): + def add_corner_widget(self, widget_id, widget, before=None): """ Add widget to corner, that is to the left of the last added widget. Parameters ---------- - name: str + widget_id: str Unique name of the widget. widget: QWidget Any QWidget to add in the corner widget. @@ -611,8 +661,7 @@ def add_corner_widget(self, name, widget, before=None): additional widgets will be placed to the left of the spinner, if visible. """ - if self._corner_widget is not None: - self._corner_widget.add_widget(name, widget) + self._corner_widget.add_widget(widget_id, widget) def get_corner_widget(self, name): """ @@ -629,27 +678,24 @@ def start_spinner(self): """ Start default status spinner. """ - self._spinner.setVisible(True) - self._spinner.start() + if self.ENABLE_SPINNER: + self._spinner.start() def stop_spinner(self): """ Stop default status spinner. """ - self._spinner.stop() - self._spinner.setVisible(False) + if self.ENABLE_SPINNER: + self._spinner.stop() - def create_toolbar(self, name, location='top'): + def create_toolbar(self, toolbar_id): """ - Create and add an auxiliary toolbar to the top or the bottom of - the plugin. + Create and add an auxiliary toolbar to the top of the plugin. Parameters ---------- - location: str - A string whose value is used to determine where to add the - toolbar in the plugin interface. The toolbar can be added either - at the 'top' or at the 'bottom' of the plugin. + toolbar_id: str + Unique toolbar string identifier. Returns ------- @@ -657,64 +703,47 @@ def create_toolbar(self, name, location='top'): The auxiliary toolbar that was created and added to the plugin interface. """ - if name in self._toolbars: + if toolbar_id in self._toolbars: raise SpyderAPIError('Toolbar "{}" already exists!'.format(name)) - if location not in ['top', 'bottom']: - raise SpyderAPIError('Invalid location "{}" for toolbar!'.format( - location)) - toolbar = MainWidgetToolbar(parent=self) - self._toolbars[name] = toolbar - self._add_auxiliary_toolbar(toolbar, location) + toolbar.ID = toolbar_id + self._toolbars[toolbar_id] = toolbar + self._auxiliary_toolbars[toolbar_id] = toolbar + self._toolbars_layout.addWidget(toolbar) return toolbar - def create_menu(self, name, text=''): + def create_menu(self, menu_id, title=''): """ Override SpyderMenuMixin method to use a different menu class. + + Parameters + ---------- + toolbar_id: str + Unique toolbar string identifier. + title: str + Toolbar localized title. + + Returns + ------- + MainWidgetMenu + The main widget menu. """ menus = getattr(self, '_menus', None) if menus is None: self._menus = OrderedDict() - if name in self._menus: + if menu_id in self._menus: raise SpyderAPIError( - 'Menu name "{}" already in use!'.format(name) + 'Menu name "{}" already in use!'.format(menu_id) ) - menu = MainWidgetMenu(parent=self, title=text) - self._menus[name] = menu + menu = MainWidgetMenu(parent=self, title=title) + menu.ID = menu_id + self._menus[menu_id] = menu return menu - def _add_auxiliary_toolbar(self, toolbar, location): - """ - Add the given toolbar to the top or the bottom of the plugin. - - Parameters - ---------- - toolbar: QToolBar - The SpyderPluginToolbar that needs to be added to the plugin - interface. - location: str - A string whose value is used to determine where to add the given - toolbar in the plugin interface. The toolbar can be added either - to the 'top' or the 'bottom' of the plugin. - """ - if location == 'top': - area = Qt.TopToolBarArea - elif location == 'bottom': - area = Qt.BottomToolBarArea - else: - raise SpyderAPIError('Invalid location "{}"!'.format(location)) - - if self._aux_toolbars[area]: - self.addToolBarBreak(area) - - toolbar.setAllowedAreas(area) - self.addToolBar(toolbar) - self._aux_toolbars[area].append(toolbar) - def get_toolbar(self, name): """ Return one of plugin's toolbars by name. @@ -735,6 +764,12 @@ def get_options_menu(self): """ return self._options_menu + def get_options_menu_button(self): + """ + Return the main options button of the widget. + """ + return self._options_button + def get_main_toolbar(self): """ Return the main toolbar of the plugin. @@ -746,6 +781,18 @@ def get_main_toolbar(self): """ return self._main_toolbar + def get_auxiliary_toolbars(self): + """ + Return the auxiliary toolbars of the plugin. + + Returns + ------- + OrderedDict + A dictionary of wirh toolbar IDs as keys and auxiliary toolbars as + values. + """ + return self._auxiliary_toolbars + def set_icon_size(self, icon_size): """ Set the icon size of the plugin's toolbars. @@ -786,7 +833,7 @@ def update_margins(self, margin=None): """ Update central widget margins. """ - layout = self._central_widget.layout() + layout = self.layout() if self._default_margins is None: self._default_margins = layout.getContentsMargins() @@ -832,6 +879,22 @@ def get_icon(self): """ return self._icon + def render_toolbars(self): + """ + Render all the toolbars of the widget. + + Notes + ----- + This action can only be performed once. + """ + # if not self._toolbars_already_rendered: + self._main_toolbar._render() + self._corner_toolbar._render() + for __, toolbar in self._auxiliary_toolbars.items(): + toolbar._render() + + # self._toolbars_already_rendered = True + # --- SpyderDockwidget handling ------------------------------------------ # ------------------------------------------------------------------------ @Slot() diff --git a/spyder/api/widgets/auxiliary_widgets.py b/spyder/api/widgets/auxiliary_widgets.py index 3ddabfeb988..80b68d9bb6b 100644 --- a/spyder/api/widgets/auxiliary_widgets.py +++ b/spyder/api/widgets/auxiliary_widgets.py @@ -67,22 +67,23 @@ def __init__(self, parent, name): self._layout.setContentsMargins(spacing, 0, spacing, spacing) self.setContentsMargins(0, 0, 0, 0) - def add_widget(self, name, widget): + def add_widget(self, widget_id, widget): """ Add a widget to the left of the last widget added to the corner. """ - if name in self._widgets: + if widget_id in self._widgets: raise SpyderAPIError( 'Wigdet with name "{}" already added. Current names are: {}' - ''.format(name, list(self._widgets.keys())) + ''.format(widget_id, list(self._widgets.keys())) ) - self._widgets[name] = widget + widget.ID = widget_id + self._widgets[widget_id] = widget self._layout.insertWidget(0, widget) - def get_widget(self, name): + def get_widget(self, widget_id): """ - Return a widget by name. + Return a widget by unique id.. """ - if name in self._widgets: - return self._widgets[name] + if widget_id in self._widgets: + return self._widgets[widget_id] diff --git a/spyder/api/widgets/toolbars.py b/spyder/api/widgets/toolbars.py index 4fd4560fb81..1931857fa9a 100644 --- a/spyder/api/widgets/toolbars.py +++ b/spyder/api/widgets/toolbars.py @@ -139,10 +139,13 @@ class MainWidgetToolbar(SpyderToolBar): to their interface. """ - def __init__(self, parent=None, title=None, location=ToolBarLocation.Top, - corner_widget=None): + ID = None + """ + Unique string toolbar identifier. + """ + + def __init__(self, parent=None, title=None): super().__init__(parent, title=title or '') - self._set_corner_widget(corner_widget) self._icon_size = QSize(16, 16) # Setup @@ -150,9 +153,9 @@ def __init__(self, parent=None, title=None, location=ToolBarLocation.Top, str(uuid.uuid4())[:8])) self.setFloatable(False) self.setMovable(False) - self.setAllowedAreas(location) self.setContextMenuPolicy(Qt.PreventContextMenu) self.setIconSize(self._icon_size) + self._setup_style() self._filter = ToolTipFilter() @@ -160,42 +163,6 @@ def set_icon_size(self, icon_size): self._icon_size = icon_size self.setIconSize(icon_size) - def addWidget(self, widget): - """ - Override Qt method. - - Take into account the existence of a corner widget when adding a new - widget to this toolbar. - """ - if self._corner_widget is not None: - super().insertWidget(self._corner_separator_action, widget) - else: - super().addWidget(widget) - - def create_toolbar_stretcher(self): - """ - Create a stretcher widget to be used in a Qt toolbar. - """ - stretcher = QWidget() - stretcher.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - return stretcher - - def _set_corner_widget(self, corner_widget): - """ - Add the given corner widget to this toolbar. - - A stretcher widget is added before the corner widget so that - its position is forced to the right side of the toolbar when the - toolbar is resized. - """ - self._corner_widget = corner_widget - if corner_widget is not None: - stretcher = self.create_toolbar_stretcher() - self._corner_separator = super().addWidget(stretcher) - super().addWidget(self._corner_widget) - else: - self._corner_separator = None - def _render(self): """ Create the toolbar taking into account the sections and locations. @@ -217,15 +184,10 @@ def _render(self): for (sec, item) in sec_items: if isinstance(item, QAction): add_method = super().addAction - insert_method = super().insertAction else: add_method = super().addWidget - insert_method = super().insertWidget - if self._corner_widget is not None: - insert_method(self._corner_separator, item) - else: - add_method(item) + add_method(item) if isinstance(item, QAction): widget = self.widgetForAction(item) diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py index 04ff127fee9..d595e3cac04 100644 --- a/spyder/app/mainwindow.py +++ b/spyder/app/mainwindow.py @@ -2832,7 +2832,8 @@ def create_plugins_menu(self): # Old API action = plugin._toggle_view_action - action.setChecked(plugin.dockwidget.isVisible()) + if action: + action.setChecked(plugin.dockwidget.isVisible()) try: name = plugin.CONF_SECTION diff --git a/spyder/plugins/findinfiles/widgets.py b/spyder/plugins/findinfiles/widgets.py index f303de2bf7a..cd1f2afdfa8 100644 --- a/spyder/plugins/findinfiles/widgets.py +++ b/spyder/plugins/findinfiles/widgets.py @@ -59,6 +59,9 @@ MAX_PATH_LENGTH = 60 MAX_PATH_HISTORY = 15 +# These additional pixels account for operating system spacing differences +EXTRA_BUTTON_PADDING = 10 + class FindInFilesWidgetActions: # Triggers @@ -813,6 +816,7 @@ class FindInFilesWidget(PluginMainWidget): 'supported_encodings': ("utf-8", "iso-8859-1", "cp1252"), 'text_color': MAIN_TEXT_COLOR, } + ENABLE_SPINNER = True REGEX_INVALID = "background-color:rgb(255, 80, 80);" REGEX_ERROR = _("Regular expression error") @@ -895,9 +899,6 @@ def __init__(self, name=None, plugin=None, parent=None, fm = self.search_label.fontMetrics() base_size = int(fm.width(_('Location:')) * 1.2) - self.search_text_edit.setMinimumWidth(base_size * 6) - self.exclude_pattern_edit.setMinimumWidth(base_size * 6) - self.path_selection_combo.setMinimumWidth(base_size * 6) self.search_label.setMinimumWidth(base_size) self.search_in_label.setMinimumWidth(base_size) self.exclude_label.setMinimumWidth(base_size) @@ -927,6 +928,7 @@ def __init__(self, name=None, plugin=None, parent=None, self.sig_max_results_reached) self.result_browser.sig_max_results_reached.connect( self._stop_and_reset_thread) + self.search_text_edit.sig_resized.connect(self._update_size) # --- PluginMainWidget API # ------------------------------------------------------------------------ @@ -1032,21 +1034,37 @@ def setup(self, options=DEFAULT_OPTIONS): ) def update_actions(self): + stop_text = _('Stop') + search_text = _('Search') if self.running: - icon_text = _('Stop') + icon_text = stop_text icon = self.create_icon('stop') else: - icon_text = _('Search') + icon_text = search_text icon = self.create_icon('find') self.find_action.setIconText(icon_text) self.find_action.setIcon(icon) + widget = self.get_main_toolbar().widgetForAction(self.find_action) + if widget: + w1 = widget.fontMetrics().width(stop_text) + w2 = widget.fontMetrics().width(search_text) + + # Ensure the search/stop button has the same size independent on + # the length of the words. + width = (self.get_options_menu_button().width() + max([w1, w2]) + + EXTRA_BUTTON_PADDING) + widget.setMinimumWidth(width) + if self.extras_toolbar and self.more_options_action: self.extras_toolbar.setVisible( self.more_options_action.isChecked()) def on_option_update(self, option, value): if option == 'more_options': + self.exclude_pattern_edit.setMinimumWidth( + self.search_text_edit.width()) + if value: icon = self.create_icon('options_less') tip = _('Hide advanced options') @@ -1066,6 +1084,9 @@ def on_option_update(self, option, value): # --- Private API # ------------------------------------------------------------------------ + def _update_size(self, size, old_size): + self.exclude_pattern_edit.setMinimumWidth(size.width()) + def _get_options(self): """ Get search options. diff --git a/spyder/plugins/help/widgets.py b/spyder/plugins/help/widgets.py index e8f3835c3d8..82569ac4138 100644 --- a/spyder/plugins/help/widgets.py +++ b/spyder/plugins/help/widgets.py @@ -278,6 +278,7 @@ class HelpWidget(PluginMainWidget): # Shortcut CONF 'console_shortcut': 'Ctrl+I', } + ENABLE_SPINNER = True # Signals sig_item_found = Signal() diff --git a/spyder/plugins/onlinehelp/widgets.py b/spyder/plugins/onlinehelp/widgets.py index 4bc249290e8..bd5504d09c2 100644 --- a/spyder/plugins/onlinehelp/widgets.py +++ b/spyder/plugins/onlinehelp/widgets.py @@ -131,6 +131,7 @@ class PydocBrowser(PluginMainWidget): 'max_history_entries': 10, 'zoom_factor': 1, } + ENABLE_SPINNER = True # --- Signals # ------------------------------------------------------------------------ diff --git a/spyder/plugins/profiler/widgets/main_widget.py b/spyder/plugins/profiler/widgets/main_widget.py index 24b64bcd640..209cb0deb00 100644 --- a/spyder/plugins/profiler/widgets/main_widget.py +++ b/spyder/plugins/profiler/widgets/main_widget.py @@ -125,6 +125,7 @@ class ProfilerWidget(PluginMainWidget): DEFAULT_OPTIONS = { 'text_color': MAIN_TEXT_COLOR, } + ENABLE_SPINNER = True DATAPATH = get_conf_path('profiler.results') # --- Signals diff --git a/spyder/widgets/comboboxes.py b/spyder/widgets/comboboxes.py index 56c5c90069c..2fbdb7195b4 100644 --- a/spyder/widgets/comboboxes.py +++ b/spyder/widgets/comboboxes.py @@ -19,7 +19,7 @@ # Third party imports import qdarkstyle -from qtpy.QtCore import QEvent, Qt, QTimer, QUrl, Signal +from qtpy.QtCore import QEvent, Qt, QTimer, QUrl, Signal, QSize from qtpy.QtGui import QFont from qtpy.QtWidgets import (QComboBox, QCompleter, QLineEdit, QSizePolicy, QToolTip) @@ -36,6 +36,18 @@ class BaseComboBox(QComboBox): valid = Signal(bool, bool) sig_tab_pressed = Signal(bool) + sig_resized = Signal(QSize, QSize) + """ + This signal is emitted to inform the widget has been resized. + + Parameters + ---------- + size: QSize + The new size of the widget. + old_size: QSize + The previous size of the widget. + """ + def __init__(self, parent): QComboBox.__init__(self, parent) self.setEditable(True) @@ -68,6 +80,13 @@ def keyPressEvent(self, event): else: QComboBox.keyPressEvent(self, event) + def resizeEvent(self, event): + """ + Emit a resize signal for widgets that need to adapt its size. + """ + super().resizeEvent(event) + self.sig_resized.emit(event.size(), event.oldSize()) + # --- Own methods def is_valid(self, qstr): """ @@ -124,6 +143,7 @@ def hide_completer(self): class PatternComboBox(BaseComboBox): """Search pattern combo box""" + def __init__(self, parent, items=None, tip=None, adjust_to_minimum=True): BaseComboBox.__init__(self, parent) diff --git a/spyder/widgets/tabs.py b/spyder/widgets/tabs.py index 0d4eade1b26..aab7c792110 100644 --- a/spyder/widgets/tabs.py +++ b/spyder/widgets/tabs.py @@ -12,6 +12,7 @@ # pylint: disable=R0201 # Standard library imports +import os import os.path as osp import sys @@ -256,9 +257,6 @@ def __init__(self, parent, actions=None, menu=None, self.corner_widgets = {} self.menu_use_tooltips = menu_use_tooltips - self.setStyleSheet("QTabWidget::tab-bar {" - "alignment: left;}") - if menu is None: self.menu = QMenu(self) if actions: @@ -266,6 +264,21 @@ def __init__(self, parent, actions=None, menu=None, else: self.menu = menu + # QTabBar forces the corner widgets to be smaller than they should on + # some plugins, like History. The top margin added allows the + # toolbuttons to expand to their normal size. + # See: spyder-ide/spyder#13600 + top_margin = 9 if os.name == "nt" else 5 + self.setStyleSheet( + f""" + QTabBar::tab {{ + margin-top: {top_margin}px; + }} + QTabWidget::tab-bar {{ + alignment: left; + }} + """) + # Corner widgets if corner_widgets is None: corner_widgets = {} diff --git a/spyder/widgets/waitingspinner.py b/spyder/widgets/waitingspinner.py index f719265545a..37101ae281c 100644 --- a/spyder/widgets/waitingspinner.py +++ b/spyder/widgets/waitingspinner.py @@ -73,8 +73,12 @@ def __init__(self, parent, centerOnParent=True, self.setWindowModality(modality) self.setAttribute(Qt.WA_TranslucentBackground) + self.show() def paintEvent(self, QPaintEvent): + if not self._isSpinning: + return + self.updatePosition() painter = QPainter(self) painter.fillRect(self.rect(), Qt.transparent) @@ -112,7 +116,6 @@ def paintEvent(self, QPaintEvent): def start(self): self.updatePosition() self._isSpinning = True - self.show() if self.parentWidget and self._disableParentWhenSpinning: self.parentWidget().setEnabled(False) @@ -121,9 +124,10 @@ def start(self): self._timer.start() self._currentCounter = 0 + self.show() + def stop(self): self._isSpinning = False - self.hide() if self.parentWidget() and self._disableParentWhenSpinning: self.parentWidget().setEnabled(True) @@ -132,6 +136,9 @@ def stop(self): self._timer.stop() self._currentCounter = 0 + self.show() + self.repaint() + def setNumberOfLines(self, lines): self._numberOfLines = lines self._currentCounter = 0