Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Fix layout and missing entries in main menus #16066

Merged
merged 14 commits into from
Jul 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions spyder/api/plugin_registration/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,22 @@ def is_plugin_enabled(self, plugin_name: str) -> bool:
"""
return plugin_name in self.enabled_plugins

def is_plugin_available(self, plugin_name: str) -> bool:
"""
Determine if a given plugin was loaded and is available.

Parameters
----------
plugin_name: str
Name of the plugin to query.

Returns
-------
plugin_available: bool
True if the plugin is available and False if not.
"""
return self.plugin_availability.get(plugin_name, False)

def reset(self):
"""Reset and empty the plugin registry."""
# Dictionary that maps a plugin name to a list of the plugin names
Expand Down
4 changes: 4 additions & 0 deletions spyder/api/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,10 @@ def is_plugin_enabled(self, plugin_name):
"""Determine if a given plugin is going to be loaded."""
return self._main.is_plugin_enabled(plugin_name)

def is_plugin_available(self, plugin_name):
"""Determine if a given plugin is available."""
return self._main.is_plugin_available(plugin_name)

def get_dockable_plugins(self):
"""
Return a list of the required plugin instances.
Expand Down
90 changes: 75 additions & 15 deletions spyder/api/widgets/menus.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ def __init__(self, parent=None, title=None, dynamic=True):
self._title = title
self._sections = []
self._actions = []
self._ordered_actions = []
self.unintroduced_actions = {}
self.unintroduced_sections = []
self._dirty = False

if title is None:
Expand All @@ -73,39 +74,68 @@ def clear_actions(self):
self.clear()
self._sections = []
self._actions = []
self.ordered_actions = []
self.unintroduced_actions = {}
self.unintroduced_sections = []

def add_action(self, action, section=None, before=None,
before_section=None):
before_section=None, check_before=True):
"""
Add action to a given menu section.

Parameters
----------
action: SpyderAction
The action to add.
section: str or None
The section id in which to insert the `action`.
before: SpyderAction or None
Make the action appear before another given action.
before_section: Section or None
Make the item section (if provided) appear before another
given section.
check_before: bool
Check if the `before` action is part of the menu. This is
necessary to avoid an infinite recursion when adding
unintroduced actions with this method again.
"""
if before is None:
self._actions.append((section, action))
else:
new_actions = []
added = False
for sec, act in self._actions:
if act == before:
added = True
new_actions.append((section, action))

new_actions.append((sec, act))

# Actions can't be added to the menu if the `before` action is
# not part of it yet. That's why we need to save them in the
# `unintroduced_actions` dict, so we can add them again when
# the menu is rendered.
if not added and check_before:
before_actions = self.unintroduced_actions.get(before, [])
before_actions.append((section, action))
self.unintroduced_actions[before] = before_actions

self._actions = new_actions

if before_section is not None and before_section in self._sections:
new_sections = []
for sec in self._sections:
if sec == before_section:
new_sections.append(section)
if sec != section:
new_sections.append(sec)
self._sections = new_sections
if before_section is not None:
if before_section in self._sections:
self._update_sections(section, before_section)
else:
# If `before_section` has not been introduced yet to the menu,
# we save `section` to introduce it when the menu is rendered.
if (section, before_section) not in self.unintroduced_sections:
self.unintroduced_sections.append(
(section, before_section)
)
elif section not in self._sections:
self._sections.append(section)

# Track state of menu to avoid re-rendering if menu has not changed
self._dirty = True
self._ordered_actions = []

def get_title(self):
"""
Expand Down Expand Up @@ -141,11 +171,43 @@ def _render(self):
"""
if self._dirty:
self.clear()

# Iterate over unintroduced sections until all of them have been
# introduced.
iter_sections = iter(self.unintroduced_sections)
while len(self.unintroduced_sections) > 0:
section, before_section = next(iter_sections)
self._update_sections(section, before_section)

# If section was introduced, remove it from the list and
# update iterator.
if section in self._sections:
self.unintroduced_sections.remove(
(section, before_section)
)
iter_sections = iter(self.unintroduced_sections)

# Update actions with those that were not introduced because
# a `before` action they required was not part of the menu yet.
for before, actions in self.unintroduced_actions.items():
for section, action in actions:
self.add_action(action, section=section, before=before,
check_before=False)

actions = self.get_actions()
add_actions(self, actions)
self._ordered_actions = actions
self._dirty = False

def _update_sections(self, section, before_section):
"""Update sections ordering."""
new_sections = []
for sec in self._sections:
if sec == before_section:
new_sections.append(section)
if sec != section:
new_sections.append(sec)
self._sections = new_sections


class MainWidgetMenu(SpyderMenu):
"""
Expand Down Expand Up @@ -174,6 +236,4 @@ def _render(self):
actions.append(action)

add_actions(self, actions)

self._ordered_actions = actions
self._dirty = False
4 changes: 2 additions & 2 deletions spyder/api/widgets/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def get_menu(self, name: str, context: Optional[str] = None,
------
KeyError
If either of `name`, `context` or `plugin` keys do not exist in the
toolbar registry.
menu registry.
"""
plugin = self.PLUGIN_NAME if plugin is None else plugin
context = self.CONTEXT_NAME if context is None else context
Expand Down Expand Up @@ -495,7 +495,7 @@ def get_action(self, name: str, context: Optional[str] = None,
------
KeyError
If either of `name`, `context` or `plugin` keys do not exist in the
toolbar registry.
action registry.
"""
plugin = self.PLUGIN_NAME if plugin is None else plugin
context = self.CONTEXT_NAME if context is None else context
Expand Down
4 changes: 4 additions & 0 deletions spyder/app/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ def is_plugin_enabled(self, plugin_name):
"""Determine if a given plugin is going to be loaded."""
return PLUGIN_REGISTRY.is_plugin_enabled(plugin_name)

def is_plugin_available(self, plugin_name):
"""Determine if a given plugin is going to be loaded."""
return PLUGIN_REGISTRY.is_plugin_available(plugin_name)

def show_status_message(self, message, timeout):
"""
Show a status message in Spyder Main Window.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
# (see spyder/__init__.py for details)

"""
Spyder preference dialogs and widgets.
Spyder application API.
"""

from spyder.plugins.application.container import ApplicationActions
1 change: 1 addition & 0 deletions spyder/plugins/application/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from spyder.widgets.helperwidgets import MessageCheckBox
from spyder.workers.updates import WorkerUpdates


WinUserEnvDialog = None
if os.name == 'nt':
from spyder.utils.environ import WinUserEnvDialog
Expand Down
18 changes: 6 additions & 12 deletions spyder/plugins/application/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,16 @@ def get_description(self):
return _('Provide main application base actions.')

def on_initialize(self):
self.console_available = False
self.main_menu_available = False
self.shortcuts_available = False
pass

@on_plugin_available(plugin=Plugins.Shortcuts)
def on_shortcuts_available(self):
self.shortcuts_available = True
if self.main_menu_available:
if self.is_plugin_available(Plugins.MainMenu):
self._populate_help_menu()

@on_plugin_available(plugin=Plugins.Console)
def on_console_available(self):
self.console_available = True

if self.main_menu_available:
if self.is_plugin_available(Plugins.MainMenu):
report_action = self.get_action(ConsoleActions.SpyderReportAction)
report_action.setVisible(True)

Expand All @@ -81,13 +76,12 @@ def on_preferences_available(self):
@on_plugin_available(plugin=Plugins.MainMenu)
def on_main_menu_available(self):
main_menu = self.get_plugin(Plugins.MainMenu)
self.main_menu_available = True

self._populate_file_menu()
self._populate_tools_menu()

if self.is_plugin_enabled(Plugins.Shortcuts):
if self.shortcuts_available:
if self.is_plugin_available(Plugins.Shortcuts):
self._populate_help_menu()
else:
self._populate_help_menu()
Expand All @@ -102,7 +96,7 @@ def on_main_menu_available(self):
icon=self.create_icon('bug'),
triggered=self.report_issue)

if not self.console_available:
if not self.is_plugin_available(Plugins.Console):
report_action.setVisible(False)

main_menu.add_item_to_application_menu(
Expand Down Expand Up @@ -225,7 +219,7 @@ def get_application_context_menu(self, parent=None):
return menu

def report_issue(self):
if self.console_available:
if self.is_plugin_available(Plugins.Console):
console = self.get_plugin(Plugins.Console)
console.report_issue()

Expand Down
29 changes: 23 additions & 6 deletions spyder/plugins/completion/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,13 @@ def __init__(self, parent, configuration=None):
# Timeout limit for a response to be received
self.wait_for_ms = self.get_conf('completions_wait_for_ms')

# Save application menus to create if/when MainMenu is available.
self.application_menus_to_create = []

# Save items to add to application menus if/when MainMenu is
# available.
self.items_to_add_to_application_menus = []

# Find and instantiate all completion providers registered via
# entrypoints
for entry_point in iter_entry_points(COMPLETION_ENTRYPOINT):
Expand Down Expand Up @@ -285,6 +292,18 @@ def on_statusbar_available(self):
for sb in container.all_statusbar_widgets():
self.statusbar.add_status_widget(sb)

@on_plugin_available(plugin=Plugins.MainMenu)
def on_mainmenu_available(self):
main_menu = self.get_plugin(Plugins.MainMenu)

# Create requested application menus.
for args, kwargs in self.application_menus_to_create:
main_menu.create_application_menu(*args, **kwargs)

# Add items to application menus.
for args, kwargs in self.items_to_add_to_application_menus:
main_menu.add_item_to_application_menu(*args, **kwargs)

def unregister(self):
"""Stop all running completion providers."""
for provider_name in self.providers:
Expand Down Expand Up @@ -862,6 +881,8 @@ def create_action(self, *args, **kwargs):
return container.create_action(*args, **kwargs)

def get_application_menu(self, *args, **kwargs):
# TODO: Check if this method makes sense with the new plugin
# registration mechanism.
main_menu = self.get_plugin(Plugins.MainMenu)
if main_menu:
return main_menu.get_application_menu(*args, **kwargs)
Expand All @@ -871,18 +892,14 @@ def get_menu(self, *args, **kwargs):
return container.get_menu(*args, **kwargs)

def create_application_menu(self, *args, **kwargs):
main_menu = self.get_plugin(Plugins.MainMenu)
if main_menu:
return main_menu.create_application_menu(*args, **kwargs)
self.application_menus_to_create.append((args, kwargs))

def create_menu(self, *args, **kwargs):
container = self.get_container()
return container.create_menu(*args, **kwargs)

def add_item_to_application_menu(self, *args, **kwargs):
main_menu = self.get_plugin(Plugins.MainMenu)
if main_menu:
main_menu.add_item_to_application_menu(*args, **kwargs)
self.items_to_add_to_application_menus.append((args, kwargs))

def add_item_to_menu(self, *args, **kwargs):
container = self.get_container()
Expand Down
Loading