feat(globalmenu): got a prototype of working globalmenu for some apps
This commit is contained in:
10 files changed
+930
-123
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import weakref
|
||||
|
||||
from fabric.utils import bulk_connect, logger
|
||||
from fabric.utils import GLib, bulk_connect, logger
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.image import Image
|
||||
@@ -279,6 +279,8 @@ class PlayerBox(Box):
|
||||
self._signal_connections = []
|
||||
self._property_bindings = []
|
||||
self._seekbar_signal_ids = []
|
||||
self._init_metadata_polled = False
|
||||
self._cached_duration = 0
|
||||
|
||||
self.album_cover = Box(style_classes="album-image-c")
|
||||
self.album_cover.set_style(
|
||||
@@ -492,11 +494,49 @@ class PlayerBox(Box):
|
||||
self._signal_connections.append((self.player, handler_id))
|
||||
|
||||
self.seek_bar.connect("realize", self._on_seek_bar_realized)
|
||||
if self.seek_bar.get_realized():
|
||||
self._on_seek_bar_realized(self.seek_bar)
|
||||
|
||||
result = apply_player_art(self.album_cover, self.player)
|
||||
if result:
|
||||
self.cover_path = result
|
||||
|
||||
self._load_initial_metadata()
|
||||
GLib.timeout_add(500, self._poll_initial_metadata)
|
||||
|
||||
def _load_initial_metadata(self):
|
||||
try:
|
||||
metadata = self.player._player.props.metadata
|
||||
if metadata:
|
||||
keys = metadata.keys()
|
||||
self.track_title.set_label(
|
||||
metadata["xesam:title"] if "xesam:title" in keys else "No Title"
|
||||
)
|
||||
self.track_artist.set_label(
|
||||
", ".join(metadata["xesam:artist"])
|
||||
if "xesam:artist" in keys and metadata["xesam:artist"]
|
||||
else "No Artist"
|
||||
)
|
||||
self.track_album.set_label(
|
||||
metadata["xesam:album"] if "xesam:album" in keys else "No Album"
|
||||
)
|
||||
self._update_duration_from_metadata()
|
||||
self.position_label.set_label(
|
||||
self.length_str(self.player.position or 0)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _poll_initial_metadata(self):
|
||||
if self.exit:
|
||||
return False
|
||||
if self._init_metadata_polled:
|
||||
return False
|
||||
self._init_metadata_polled = True
|
||||
self._load_initial_metadata()
|
||||
self.set_image()
|
||||
return False
|
||||
|
||||
def update_buttons(self, player_buttons, show_buttons):
|
||||
self.stack_buttons_box.children = []
|
||||
if show_buttons and len(player_buttons) > 1:
|
||||
@@ -532,24 +572,35 @@ class PlayerBox(Box):
|
||||
else f"{minutes}:{seconds:02d}"
|
||||
)
|
||||
|
||||
def _on_metadata(self, *_):
|
||||
def _on_metadata(self, *args):
|
||||
if self.exit or self.player is None:
|
||||
return
|
||||
self.track_title.set_label(self.player.title or "No Title")
|
||||
self.track_artist.set_label(
|
||||
", ".join(self.player.artist) if self.player.artist else "No Artist"
|
||||
)
|
||||
self.track_album.set_label(self.player.album or "No Album")
|
||||
self.set_image()
|
||||
duration = self.player.length or 0
|
||||
if duration > 0:
|
||||
self.length_label.set_label(self.length_str(duration))
|
||||
if self.seek_bar.get_realized():
|
||||
metadata = args[1] if len(args) >= 2 else None
|
||||
if metadata is not None:
|
||||
keys = metadata.keys()
|
||||
self.track_title.set_label(
|
||||
metadata["xesam:title"] if "xesam:title" in keys else "No Title"
|
||||
)
|
||||
self.track_artist.set_label(
|
||||
", ".join(metadata["xesam:artist"])
|
||||
if "xesam:artist" in keys and metadata["xesam:artist"]
|
||||
else "No Artist"
|
||||
)
|
||||
self.track_album.set_label(
|
||||
metadata["xesam:album"] if "xesam:album" in keys else "No Album"
|
||||
)
|
||||
if "mpris:length" in keys:
|
||||
duration = int(metadata["mpris:length"])
|
||||
self.length_label.set_label(self.length_str(duration))
|
||||
self.seek_bar.set_range(0, min(2147483647, duration))
|
||||
self.position_label.set_label(self.length_str(self.player.position or 0))
|
||||
else:
|
||||
self.length_label.set_label("0:00")
|
||||
if self.seek_bar.get_realized():
|
||||
self.seek_bar.set_range(0, 100)
|
||||
self.track_title.set_label(self.player.title or "No Title")
|
||||
self.track_artist.set_label(
|
||||
", ".join(self.player.artist) if self.player.artist else "No Artist"
|
||||
)
|
||||
self.track_album.set_label(self.player.album or "No Album")
|
||||
self.set_image()
|
||||
|
||||
def suspend(self):
|
||||
pass
|
||||
@@ -612,24 +663,25 @@ class PlayerBox(Box):
|
||||
self.cover_path = result
|
||||
|
||||
def _on_track_position(self, service, pos: float, dur: float):
|
||||
if self.exit or self._user_seeking:
|
||||
if self.exit:
|
||||
return
|
||||
try:
|
||||
position_micros = int(pos * 1_000_000)
|
||||
duration_micros = int(dur * 1_000_000)
|
||||
if duration_micros <= 0:
|
||||
duration_micros = self.player.length or 0
|
||||
|
||||
if duration_micros > 0:
|
||||
self._cached_duration = duration_micros
|
||||
self.length_label.set_label(self.length_str(duration_micros))
|
||||
|
||||
self.position_label.set_label(self.length_str(position_micros))
|
||||
|
||||
if duration_micros > 0:
|
||||
self.length_label.set_label(self.length_str(duration_micros))
|
||||
|
||||
if self.seek_bar.get_realized() and self.seek_bar.get_adjustment():
|
||||
if (
|
||||
duration_micros > 0
|
||||
and self.seek_bar.get_adjustment().get_upper() <= 100
|
||||
):
|
||||
adj = self.seek_bar.get_adjustment()
|
||||
if duration_micros > 0:
|
||||
self.seek_bar.set_range(0, min(2147483647, duration_micros))
|
||||
if self.seek_bar.get_adjustment().get_upper() > 0:
|
||||
if not self._user_seeking and adj.get_upper() > 0:
|
||||
self.seek_bar.set_value(min(2147483647, position_micros))
|
||||
except Exception as e:
|
||||
logger.error(f"[_on_track_position] Error: {e}")
|
||||
@@ -640,26 +692,72 @@ class PlayerBox(Box):
|
||||
|
||||
def _on_seek_end(self, widget, event):
|
||||
self._user_seeking = False
|
||||
if not self.player or self.exit:
|
||||
return False
|
||||
try:
|
||||
adj = widget.get_adjustment()
|
||||
upper = adj.get_upper()
|
||||
value = max(0, int(widget.get_value()))
|
||||
duration = self._cached_duration or self.player.length or 0
|
||||
if duration <= 0 and upper > 100:
|
||||
duration = int(upper)
|
||||
if upper > 100:
|
||||
new_position = value
|
||||
elif duration > 0 and upper > 0:
|
||||
new_position = int(value / upper * duration)
|
||||
else:
|
||||
new_position = value
|
||||
self.player.set_position(new_position / 1_000_000)
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {e}")
|
||||
return False
|
||||
|
||||
def _update_duration_from_metadata(self):
|
||||
try:
|
||||
duration = self._cached_duration or self.player.length or 0
|
||||
if duration <= 0 and self.seek_bar.get_realized():
|
||||
adj = self.seek_bar.get_adjustment()
|
||||
if adj.get_upper() > 100:
|
||||
duration = int(adj.get_upper())
|
||||
if duration > 0:
|
||||
self.seek_bar.set_range(0, min(2147483647, duration))
|
||||
self.length_label.set_label(self.length_str(duration))
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
def _on_seek_bar_realized(self, widget):
|
||||
try:
|
||||
if not self.exit and self.player:
|
||||
duration = self.player.length or 0
|
||||
self.seek_bar.set_range(
|
||||
0, min(2147483647, duration) if duration > 0 else 100
|
||||
self._update_duration_from_metadata()
|
||||
self.position_label.set_label(
|
||||
self.length_str(self.player.position or 0)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {e}")
|
||||
|
||||
def _on_scale_value_changed(self, scale: Scale):
|
||||
if self.player and not self.exit and self._user_seeking:
|
||||
try:
|
||||
new_position = max(-2147483648, min(2147483647, int(scale.get_value())))
|
||||
self.player.position = new_position
|
||||
self.position_label.set_label(self.length_str(new_position))
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {e}")
|
||||
if not self.player or self.exit:
|
||||
return
|
||||
try:
|
||||
adj = scale.get_adjustment()
|
||||
upper = adj.get_upper()
|
||||
value = max(0, int(scale.get_value()))
|
||||
|
||||
duration = self._cached_duration or self.player.length or 0
|
||||
if duration <= 0 and upper > 100:
|
||||
duration = int(upper)
|
||||
if upper > 100:
|
||||
new_position = value
|
||||
elif duration > 0 and upper > 0:
|
||||
new_position = int(value / upper * duration)
|
||||
else:
|
||||
new_position = value
|
||||
|
||||
self.position_label.set_label(self.length_str(new_position))
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {e}")
|
||||
|
||||
|
||||
class ExpandedPlayer(Window):
|
||||
|
||||
@@ -429,17 +429,44 @@ class PlayerBox(Box):
|
||||
if result:
|
||||
self.cover_path = result
|
||||
|
||||
try:
|
||||
metadata = self.player._player.props.metadata
|
||||
if metadata:
|
||||
keys = metadata.keys()
|
||||
self.track_title.set_label(
|
||||
metadata["xesam:title"] if "xesam:title" in keys else "No Title"
|
||||
)
|
||||
self.track_artist.set_label(
|
||||
", ".join(metadata["xesam:artist"])
|
||||
if "xesam:artist" in keys and metadata["xesam:artist"]
|
||||
else "No Artist"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _on_outer_box_clicked(self, *_):
|
||||
if self.control_center and hasattr(self.control_center, "open_expanded_player"):
|
||||
self.control_center.open_expanded_player()
|
||||
|
||||
def _on_metadata(self, *_):
|
||||
def _on_metadata(self, *args):
|
||||
if self.exit or self.player is None:
|
||||
return
|
||||
self.track_title.set_label(self.player.title or "No Title")
|
||||
self.track_artist.set_label(
|
||||
", ".join(self.player.artist) if self.player.artist else "No Artist"
|
||||
)
|
||||
metadata = args[1] if len(args) >= 2 else None
|
||||
if metadata is not None:
|
||||
keys = metadata.keys()
|
||||
self.track_title.set_label(
|
||||
metadata["xesam:title"] if "xesam:title" in keys else "No Title"
|
||||
)
|
||||
self.track_artist.set_label(
|
||||
", ".join(metadata["xesam:artist"])
|
||||
if "xesam:artist" in keys and metadata["xesam:artist"]
|
||||
else "No Artist"
|
||||
)
|
||||
else:
|
||||
self.track_title.set_label(self.player.title or "No Title")
|
||||
self.track_artist.set_label(
|
||||
", ".join(self.player.artist) if self.player.artist else "No Artist"
|
||||
)
|
||||
self.set_image()
|
||||
|
||||
def refresh_icon(self):
|
||||
|
||||
@@ -7,6 +7,7 @@ from fabric.widgets.label import Label
|
||||
|
||||
from shared.dialogs.about import AboutApp, get_about_window
|
||||
from shared.window.dropdown import ModusDropdown, dropdown_divider, dropdowns
|
||||
from services.globalmenu import get_global_menu_service
|
||||
from utils.app_name_resolver import format_window
|
||||
from utils.roam import modus_service
|
||||
from utils.utils import setup_cursor_hover
|
||||
@@ -45,7 +46,7 @@ def create_dropdown_with_capture(dropdown_id, parent, dropdown_children, layer="
|
||||
|
||||
def manage_button_style_classes(buttons, active_button=None, style_class="active"):
|
||||
for button in buttons:
|
||||
if button: # Check if button exists (some might be None)
|
||||
if button:
|
||||
button.remove_style_class(style_class)
|
||||
|
||||
if active_button:
|
||||
@@ -103,6 +104,32 @@ def dropdown_option(
|
||||
return btn
|
||||
|
||||
|
||||
def _make_menu_item_click(item_id):
|
||||
"""Create a click handler for a menu item."""
|
||||
|
||||
def handler(_, __=None):
|
||||
global_menu_svc = get_global_menu_service()
|
||||
if global_menu_svc:
|
||||
global_menu_svc.click_item(item_id)
|
||||
|
||||
return handler
|
||||
|
||||
|
||||
def _menu_item_to_dropdown(item):
|
||||
"""Convert a DBusMenuItem to a dropdown_option widget."""
|
||||
if item.type == "separator":
|
||||
return dropdown_divider("---------------------")
|
||||
|
||||
keybind = item.shortcut if item.shortcut else ""
|
||||
|
||||
return dropdown_option(
|
||||
item.label if item.label else "",
|
||||
keybind,
|
||||
on_click=None,
|
||||
on_clicked=_make_menu_item_click(item.id),
|
||||
)
|
||||
|
||||
|
||||
class SystemDropdown(ModusDropdown):
|
||||
def __init__(self, parent, **kwargs):
|
||||
super().__init__(
|
||||
@@ -131,17 +158,15 @@ class SystemDropdown(ModusDropdown):
|
||||
|
||||
|
||||
class GlobalMenuDropdowns:
|
||||
def __init__(self, parent):
|
||||
def __init__(self, parent, menu_box=None):
|
||||
self.parent = parent
|
||||
self._menu_box = menu_box # The GlobalMenu Box widget to update
|
||||
|
||||
# Get the global menu service
|
||||
self._global_menu_svc = get_global_menu_service()
|
||||
self._has_extracted_menu = False
|
||||
|
||||
self.system_dropdown = SystemDropdown(parent=parent)
|
||||
self.menu_button_dropdown = self.system_dropdown
|
||||
self.menu_button = Button(
|
||||
label="Modus",
|
||||
name="global-menu",
|
||||
on_clicked=lambda _: self.menu_button_dropdown.toggle(),
|
||||
)
|
||||
self.menu_button_dropdown.set_pointing_to(self.menu_button)
|
||||
|
||||
self.global_title_menu_about = dropdown_option(
|
||||
f"About {modus_service.current_active_app_name}",
|
||||
@@ -153,6 +178,10 @@ class GlobalMenuDropdowns:
|
||||
[self.global_title_menu_about],
|
||||
)
|
||||
|
||||
# Dynamic menu dropdowns (populated from extracted app menus)
|
||||
self._dynamic_dropdowns: list[ModusDropdown] = []
|
||||
|
||||
# Fallback static Hyprland menu
|
||||
self.global_menu_view = create_dropdown_with_capture(
|
||||
"global-menu-view",
|
||||
parent,
|
||||
@@ -237,6 +266,8 @@ class GlobalMenuDropdowns:
|
||||
"current-active-app-name-changed", self._on_active_app_changed
|
||||
)
|
||||
|
||||
# Connect to global menu service signals (Moved to the bottom of __init__)
|
||||
|
||||
self.global_menu_button_title = Button(
|
||||
child=ActiveWindow(
|
||||
formatter=FormattedString(
|
||||
@@ -249,11 +280,8 @@ class GlobalMenuDropdowns:
|
||||
)
|
||||
|
||||
self.global_menu_title.set_pointing_to(self.global_menu_button_title)
|
||||
# File, Edit and Go buttons are placeholders - no dropdowns implemented yet
|
||||
self.global_menu_button_file = create_menu_button("File")
|
||||
self.global_menu_button_edit = create_menu_button("Edit")
|
||||
self.global_menu_button_go = create_menu_button("Go")
|
||||
|
||||
# Fallback buttons (shown when no app menu is extracted)
|
||||
self.global_menu_button_view = create_menu_button(
|
||||
"View", lambda _: self.global_menu_view.toggle()
|
||||
)
|
||||
@@ -267,24 +295,14 @@ class GlobalMenuDropdowns:
|
||||
)
|
||||
self.global_menu_help.set_pointing_to(self.global_menu_button_help)
|
||||
|
||||
# Start with only title
|
||||
self.all_menu_buttons = [
|
||||
self.menu_button,
|
||||
self.global_menu_button_title,
|
||||
self.global_menu_button_file,
|
||||
self.global_menu_button_edit,
|
||||
self.global_menu_button_view,
|
||||
self.global_menu_button_go,
|
||||
self.global_menu_button_window,
|
||||
self.global_menu_button_help,
|
||||
]
|
||||
|
||||
self.dropdown_button_map = {
|
||||
"os-menu": self.menu_button,
|
||||
"global-menu-title": self.global_menu_button_title,
|
||||
"global-menu-file": self.global_menu_button_file,
|
||||
"global-menu-edit": self.global_menu_button_edit,
|
||||
"global-menu-view": self.global_menu_button_view,
|
||||
"global-menu-go": self.global_menu_button_go,
|
||||
"global-menu-window": self.global_menu_button_window,
|
||||
"global-menu-help": self.global_menu_button_help,
|
||||
}
|
||||
@@ -292,13 +310,148 @@ class GlobalMenuDropdowns:
|
||||
modus_service.connect("current-dropdown-changed", self.changed_dropdown)
|
||||
modus_service.connect("dropdowns-hide-changed", self.hide_dropdowns)
|
||||
|
||||
# Connect to global menu service signals
|
||||
if self._global_menu_svc:
|
||||
self._global_menu_svc.connect("menu-changed", self._on_menu_changed)
|
||||
|
||||
def _on_title_button_clicked(self, _):
|
||||
if has_active_window():
|
||||
self.global_menu_title.toggle()
|
||||
|
||||
def _on_active_app_changed(self, _, value):
|
||||
logger.info(f"[GlobalMenu] Active app changed: {value}")
|
||||
self.global_title_menu_about.set_property("label", f"About {value}")
|
||||
|
||||
# Notify the global menu service about the active window change
|
||||
if self._global_menu_svc:
|
||||
wm_class = getattr(modus_service, "current_active_wm_class", "")
|
||||
logger.info(
|
||||
f"[GlobalMenu] Calling update_active_window('{value}', '{wm_class}')"
|
||||
)
|
||||
self._global_menu_svc.update_active_window(value, wm_class)
|
||||
|
||||
def _on_menu_changed(self, _, menu_items: list):
|
||||
"""Handle menu changes from the GlobalMenuService."""
|
||||
logger.info(
|
||||
f"[GlobalMenu] _on_menu_changed called with {len(menu_items)} items"
|
||||
)
|
||||
self._rebuild_dynamic_menus(menu_items)
|
||||
|
||||
def _rebuild_dynamic_menus(self, menu_items: list):
|
||||
"""Rebuild the menu bar buttons based on extracted menu items.
|
||||
|
||||
If the app has a menu bar → show extracted menus (File, Edit, etc.)
|
||||
If not → show fallback Hyprland window controls (View, Window, Help)
|
||||
"""
|
||||
logger.info(f"[GlobalMenu] _rebuild_dynamic_menus: {len(menu_items)} items")
|
||||
|
||||
# Clean up any previous dynamic dropdowns
|
||||
for dd in self._dynamic_dropdowns:
|
||||
try:
|
||||
dd.destroy()
|
||||
except Exception:
|
||||
pass
|
||||
self._dynamic_dropdowns.clear()
|
||||
|
||||
# Filter to top-level menu items only
|
||||
top_level = [
|
||||
item for item in menu_items if hasattr(item, "label") and item.label
|
||||
]
|
||||
logger.info(f"[GlobalMenu] top_level after filter: {len(top_level)} items")
|
||||
|
||||
if not top_level:
|
||||
# No extracted menu → show only title button
|
||||
self._has_extracted_menu = False
|
||||
self.all_menu_buttons = [
|
||||
self.global_menu_button_title,
|
||||
]
|
||||
else:
|
||||
# Has extracted menu → build dynamic buttons
|
||||
self._has_extracted_menu = True
|
||||
new_buttons = [
|
||||
self.global_menu_button_title,
|
||||
]
|
||||
|
||||
for item in top_level:
|
||||
is_action = not item.has_submenu and not item.children
|
||||
|
||||
if is_action:
|
||||
# Top-level item without children (e.g., VLC Play/Stop)
|
||||
btn = create_menu_button(
|
||||
item.label,
|
||||
_make_menu_item_click(item.id),
|
||||
)
|
||||
new_buttons.append(btn)
|
||||
else:
|
||||
# Build initial children for this dropdown
|
||||
dropdown_children = []
|
||||
for child in item.children if item.children else []:
|
||||
dropdown_children.append(_menu_item_to_dropdown(child))
|
||||
|
||||
if not dropdown_children:
|
||||
dropdown_children = [
|
||||
dropdown_option(f"No items in {item.label}")
|
||||
]
|
||||
|
||||
dropdown_id = f"global-menu-{item.label.lower()}"
|
||||
dropdown = create_dropdown_with_capture(
|
||||
dropdown_id, self.parent, dropdown_children
|
||||
)
|
||||
|
||||
btn = create_menu_button(
|
||||
item.label,
|
||||
lambda _, dd=dropdown: dd.toggle(),
|
||||
)
|
||||
dropdown.set_pointing_to(btn)
|
||||
|
||||
# Dynamically fetch submenu on hover
|
||||
def make_on_enter(menu_item, dd):
|
||||
def on_enter(widget, event):
|
||||
if not menu_item.has_submenu:
|
||||
return False
|
||||
svc = get_global_menu_service()
|
||||
if svc:
|
||||
svc.about_to_show(menu_item.id)
|
||||
new_menu = svc.refresh_menu_sync()
|
||||
if new_menu:
|
||||
# find the updated item
|
||||
new_item = next(
|
||||
(i for i in new_menu if i.id == menu_item.id),
|
||||
None,
|
||||
)
|
||||
if new_item:
|
||||
children = []
|
||||
for child in (
|
||||
new_item.children
|
||||
if new_item.children
|
||||
else []
|
||||
):
|
||||
children.append(
|
||||
_menu_item_to_dropdown(child)
|
||||
)
|
||||
if not children:
|
||||
children = [
|
||||
dropdown_option(
|
||||
f"No items in {new_item.label}"
|
||||
)
|
||||
]
|
||||
dd.dropdown.children = children
|
||||
return False
|
||||
|
||||
return on_enter
|
||||
|
||||
btn.connect("enter-notify-event", make_on_enter(item, dropdown))
|
||||
|
||||
self._dynamic_dropdowns.append(dropdown)
|
||||
self.dropdown_button_map[dropdown_id] = btn
|
||||
new_buttons.append(btn)
|
||||
|
||||
self.all_menu_buttons = new_buttons
|
||||
|
||||
# Update the panel (the actual GTK Box widget)
|
||||
if self._menu_box is not None:
|
||||
self._menu_box.children = self.all_menu_buttons
|
||||
|
||||
def hide_dropdowns(self, *_):
|
||||
manage_button_style_classes(self.all_menu_buttons)
|
||||
|
||||
@@ -315,9 +468,24 @@ class GlobalMenuDropdowns:
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {e}")
|
||||
|
||||
# Destroy all dropdown captures
|
||||
# Disconnect global menu service
|
||||
if self._global_menu_svc:
|
||||
try:
|
||||
self._global_menu_svc.disconnect_by_func(self._on_menu_changed)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Destroy dynamic dropdowns
|
||||
for dd in self._dynamic_dropdowns:
|
||||
try:
|
||||
dd.destroy()
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {e}")
|
||||
self._dynamic_dropdowns.clear()
|
||||
|
||||
# Destroy static dropdown captures
|
||||
dropdown_captures = [
|
||||
getattr(self, "menu_button_dropdown", None),
|
||||
getattr(self, "system_dropdown", None),
|
||||
getattr(self, "global_menu_title", None),
|
||||
getattr(self, "global_menu_view", None),
|
||||
getattr(self, "global_menu_window", None),
|
||||
@@ -342,22 +510,13 @@ class GlobalMenu(Box):
|
||||
name="globalmenu", orientation="horizontal", spacing=0, **kwargs
|
||||
)
|
||||
|
||||
self.dropdown_system = GlobalMenuDropdowns(parent=parent_window)
|
||||
self.dropdown_system = GlobalMenuDropdowns(parent=parent_window, menu_box=self)
|
||||
|
||||
self.children = [
|
||||
self.dropdown_system.global_menu_button_title,
|
||||
self.dropdown_system.global_menu_button_file,
|
||||
self.dropdown_system.global_menu_button_edit,
|
||||
self.dropdown_system.global_menu_button_view,
|
||||
self.dropdown_system.global_menu_button_go,
|
||||
self.dropdown_system.global_menu_button_window,
|
||||
self.dropdown_system.global_menu_button_help,
|
||||
]
|
||||
self.children = self.dropdown_system.all_menu_buttons
|
||||
|
||||
def show_system_dropdown(self, imac_button):
|
||||
self.dropdown_system.menu_button_dropdown.set_pointing_to(imac_button)
|
||||
mouse_capture = self.dropdown_system.menu_button_dropdown
|
||||
mouse_capture.toggle()
|
||||
self.dropdown_system.system_dropdown.set_pointing_to(imac_button)
|
||||
self.dropdown_system.system_dropdown.toggle()
|
||||
|
||||
def destroy(self):
|
||||
"""Clean up the global menu and its dropdowns"""
|
||||
|
||||
Reference in new issue
Block a user