From 1ec5d87477f5c2945a1bde829094186c00abbf2d Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Mon, 4 Aug 2025 09:09:22 +0545 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(panel):=20simplif?= =?UTF-8?q?y=20dropdown=20menu=20creation=20for=20system=20tray?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../panel/components/enhanced_system_tray.py | 345 ++---------------- 1 file changed, 24 insertions(+), 321 deletions(-) diff --git a/modules/panel/components/enhanced_system_tray.py b/modules/panel/components/enhanced_system_tray.py index 43d0aba..56ba1f8 100644 --- a/modules/panel/components/enhanced_system_tray.py +++ b/modules/panel/components/enhanced_system_tray.py @@ -3,13 +3,7 @@ Enhanced System Tray Icon Handling This module provides enhanced icon loading capabilities for system tray items, including fallback mechanisms for file paths and common icon locations. -It also adds dropdown menu functionality for system tray items with: - -- Dynamic menu state tracking (checkboxes, radio buttons) -- Real-time menu refresh on each right-click -- Proper toggle state visualization (✓ for checked, ● for radio selected) -- Enhanced error handling and debugging -- Support for enabled/disabled menu items +It also adds dropdown menu functionality for system tray items. """ import os @@ -158,191 +152,11 @@ def _set_tooltip(self): ) -def dropdown_option(label: str = "", on_click=None, submenu_items=None, has_native_submenu=False, native_menu_item=None): - """Create a dropdown option for system tray items with optional submenu""" +def dropdown_option(label: str = "", on_click=None): + """Create a dropdown option for system tray items""" from fabric.widgets.button import Button from fabric.widgets.centerbox import CenterBox - from fabric.widgets.box import Box - # If this has native submenu (like NetworkManager WiFi list), create special expandable option - if has_native_submenu and native_menu_item: - from fabric.widgets.revealer import Revealer - - # State for submenu expansion - submenu_expanded = False - - def toggle_native_submenu(_): - nonlocal submenu_expanded - submenu_expanded = not submenu_expanded - submenu_revealer.set_reveal_child(submenu_expanded) - - # Update the arrow indicator - arrow_label = "▼" if submenu_expanded else "▶" - main_button.get_child().get_start_children()[0].set_label(f"{label} {arrow_label}") - - print(f"[DEBUG] Native submenu {'expanded' if submenu_expanded else 'collapsed'}") - - def show_native_menu(_): - """Show the native context menu for this item""" - try: - print(f"[DEBUG] Showing native menu for '{label}'") - - # Hide our dropdown first - from widgets.dropdown import dropdowns - for dropdown in dropdowns: - if dropdown.is_visible() and hasattr(dropdown, "hide_via_mousecapture"): - dropdown.hide_via_mousecapture() - break - - # Try to activate the native menu item - if hasattr(native_menu_item, 'activate'): - native_menu_item.activate() - elif hasattr(native_menu_item, 'handle_event'): - native_menu_item.handle_event("clicked", None, 0) - - except Exception as e: - print(f"[DEBUG] Failed to show native menu for '{label}': {e}") - - # Create main button with arrow indicator - main_button = Button( - child=CenterBox( - start_children=[ - Label(label=f"{label} ▶", h_align="start", name="dropdown-option-label"), - ], - orientation="horizontal", - h_align="fill", - h_expand=True, - v_expand=True, - ), - name="dropdown-option", - h_align="fill", - on_clicked=toggle_native_submenu, - h_expand=True, - v_expand=True, - ) - - # Create submenu with native menu option - native_submenu_items = [ - Button( - child=CenterBox( - start_children=[ - Label(label=" Show Native Menu", h_align="start", name="dropdown-option-label"), - ], - orientation="horizontal", - h_align="fill", - h_expand=True, - v_expand=True, - ), - name="dropdown-option", - h_align="fill", - on_clicked=show_native_menu, - h_expand=True, - v_expand=True, - ), - Button( - child=CenterBox( - start_children=[ - Label(label=" (Hover over tray icon for full menu)", h_align="start", name="dropdown-option-label"), - ], - orientation="horizontal", - h_align="fill", - h_expand=True, - v_expand=True, - ), - name="dropdown-option", - h_align="fill", - sensitive=False, # Disabled info text - h_expand=True, - v_expand=True, - ) - ] - - # Create revealer for submenu - submenu_revealer = Revealer( - child=Box( - children=native_submenu_items, - orientation="vertical", - name="submenu-items", - ), - reveal_child=False, - transition_type="slide-down", - transition_duration=200, - ) - - # Return container with main button and submenu - return Box( - children=[main_button, submenu_revealer], - orientation="vertical", - name="dropdown-option-with-submenu", - ) - - # If this has submenu items, create an expandable option - elif submenu_items: - from fabric.widgets.revealer import Revealer - - # State for submenu expansion - submenu_expanded = False - - def toggle_submenu(_): - nonlocal submenu_expanded - submenu_expanded = not submenu_expanded - submenu_revealer.set_reveal_child(submenu_expanded) - - # Update the arrow indicator - arrow_label = "▼" if submenu_expanded else "▶" - main_button.get_child().get_start_children()[0].set_label(f"{label} {arrow_label}") - - print(f"[DEBUG] Submenu {'expanded' if submenu_expanded else 'collapsed'}") - - # Create main button with arrow indicator - main_button = Button( - child=CenterBox( - start_children=[ - Label(label=f"{label} ▶", h_align="start", name="dropdown-option-label"), - ], - orientation="horizontal", - h_align="fill", - h_expand=True, - v_expand=True, - ), - name="dropdown-option", - h_align="fill", - on_clicked=toggle_submenu, - h_expand=True, - v_expand=True, - ) - - # Create submenu items with indentation - indented_submenu_items = [] - for item in submenu_items: - # Add indentation to submenu items - if hasattr(item, 'get_child') and hasattr(item.get_child(), 'get_start_children'): - original_label = item.get_child().get_start_children()[0] - if hasattr(original_label, 'get_label'): - original_text = original_label.get_label() - original_label.set_label(f" {original_text}") # Add indentation - indented_submenu_items.append(item) - - # Create revealer for submenu - submenu_revealer = Revealer( - child=Box( - children=indented_submenu_items, - orientation="vertical", - name="submenu-items", - ), - reveal_child=False, - transition_type="slide-down", - transition_duration=200, - ) - - # Return container with main button and submenu - return Box( - children=[main_button, submenu_revealer], - orientation="vertical", - name="dropdown-option-with-submenu", - ) - - # Regular option without submenu def on_click_handler(_): if on_click: on_click() @@ -371,12 +185,8 @@ def dropdown_option(label: str = "", on_click=None, submenu_items=None, has_nati ) -# Note: Submenu functionality is now handled inline within dropdown_option -# using Revealer widgets for expandable menu sections - - def extract_menu_items(menu, tray_item): - """Recursively extract menu items from a DbusmenuGtk3.Menu with state tracking""" + """Recursively extract menu items from a DbusmenuGtk3.Menu""" options = [] try: @@ -397,48 +207,6 @@ def extract_menu_items(menu, tray_item): options.append(dropdown_divider("")) continue - # Check for toggle state and other properties - toggle_type = None - toggle_state = None - enabled = True - visible = True - - if hasattr(child, 'property_get'): - toggle_type = child.property_get("toggle-type") - toggle_state = child.property_get("toggle-state") - enabled = child.property_get("enabled") - visible = child.property_get("visible") - - # Handle different property formats - if enabled is None: - enabled = True - if visible is None: - visible = True - - # Debug output for complex menu items - if toggle_type or toggle_state is not None: - print(f"[DEBUG] Menu item '{label}': toggle_type={toggle_type}, toggle_state={toggle_state}, enabled={enabled}") - - # Skip invisible items - if not visible: - continue - - # Format label based on toggle state - display_label = label.replace("_", "") # Remove mnemonics - - if toggle_type == "checkmark" and toggle_state is not None: - # Add checkmark for checked items - if toggle_state == 1 or toggle_state is True: # Checked - display_label = f"✓ {display_label}" - else: # Unchecked - display_label = f" {display_label}" - elif toggle_type == "radio" and toggle_state is not None: - # Add radio button indicator - if toggle_state == 1 or toggle_state is True: # Selected - display_label = f"● {display_label}" - else: # Not selected - display_label = f"○ {display_label}" - # Create action for this menu item def create_menu_action(menu_child): def action(): @@ -447,65 +215,24 @@ def extract_menu_items(menu, tray_item): menu_child.handle_event("clicked", None, 0) elif hasattr(menu_child, 'activate'): menu_child.activate() - else: - print(f"[DEBUG] No activation method found for menu item: {label}") - except Exception as e: - print(f"[DEBUG] Failed to activate menu item '{label}': {e}") + except Exception: + pass return action - # Handle submenus - submenu_items = None - has_native_submenu = False + options.append(dropdown_option( + label.replace("_", ""), # Remove mnemonics + on_click=create_menu_action(child) + )) + # Handle submenus if hasattr(child, 'get_submenu'): submenu = child.get_submenu() if submenu: - submenu_items = extract_menu_items(submenu, tray_item) - if submenu_items: - print(f"[DEBUG] Found submenu for '{label}' with {len(submenu_items)} items") + sub_options = extract_menu_items(submenu, tray_item) + if sub_options: + options.extend(sub_options) - # Check for potential native submenus (common patterns) - if not submenu_items and hasattr(child, 'property_get'): - # Check for indicators that this might have a native submenu - item_type = child.property_get("type") or "" - children_display = child.property_get("children-display") or "" - - # Common patterns for items that show native submenus on hover - submenu_indicators = [ - "WiFi Networks" in label, - "Available Networks" in label, - "Connect to" in label, - "Bluetooth" in label and ("Device" in label or "Connect" in label), - "Audio" in label and ("Device" in label or "Output" in label), - "VPN" in label and "Connect" in label, - children_display == "submenu", - item_type == "submenu", - # Additional patterns for common system tray items - label.endswith("...") and ("Network" in label or "Audio" in label or "Bluetooth" in label), - "Select" in label and ("Device" in label or "Network" in label), - ] - - if any(submenu_indicators): - has_native_submenu = True - print(f"[DEBUG] Detected potential native submenu for '{label}'") - - # Create dropdown option with proper state indication and submenu - option = dropdown_option( - display_label, - on_click=create_menu_action(child) if not submenu_items and not has_native_submenu else None, - submenu_items=submenu_items, - has_native_submenu=has_native_submenu, - native_menu_item=child if has_native_submenu else None - ) - - # Disable option if not enabled - if not enabled: - option.set_sensitive(False) - - options.append(option) - - except Exception as e: - print(f"[DEBUG] Error processing menu item: {e}") + except Exception: continue except Exception: @@ -523,14 +250,14 @@ def get_tray_menu_options(tray_item): def activate_app(): try: tray_item._item.activate_for_event(None) - except Exception as e: - print(f"[DEBUG] Failed to activate {app_name}: {e}") + except Exception: + pass def show_native_menu(): try: tray_item._item.invoke_menu_for_event(None) - except Exception as e: - print(f"[DEBUG] Failed to show native menu for {app_name}: {e}") + except Exception: + pass try: # Try to get the actual menu items from the tray item @@ -543,11 +270,9 @@ def get_tray_menu_options(tray_item): menu_extraction_successful = False if menu is not None: - print(f"[DEBUG] Extracting menu for {app_name}, menu type: {type(menu)}") # Extract actual menu items extracted_options = extract_menu_items(menu, tray_item) if extracted_options: - print(f"[DEBUG] Successfully extracted {len(extracted_options)} menu items for {app_name}") # Add dividers after each option (except the last one) for i, option in enumerate(extracted_options): options.append(option) @@ -556,21 +281,17 @@ def get_tray_menu_options(tray_item): options.append(dropdown_divider("")) menu_extraction_successful = True - else: - print(f"[DEBUG] No menu items extracted for {app_name}") # Only add fallback options if menu extraction was not successful if not menu_extraction_successful: if menu is not None: # Menu exists but extraction failed, show native menu option - print(f"[DEBUG] Menu exists but extraction failed for {app_name}, adding fallback") options.append(dropdown_option( f"Show {app_name} Menu", on_click=show_native_menu )) else: # No menu available, just add basic activation - print(f"[DEBUG] No menu available for {app_name}, adding basic options") options.append(dropdown_option( f"Open {app_name}", on_click=activate_app @@ -581,8 +302,7 @@ def get_tray_menu_options(tray_item): on_click=show_native_menu )) - except Exception as e: - print(f"[DEBUG] Exception while extracting menu for {app_name}: {e}") + except Exception: # Fallback to basic options if anything fails options = [ dropdown_option( @@ -599,21 +319,6 @@ def get_tray_menu_options(tray_item): return options -def refresh_dropdown_menu(dropdown, tray_item): - """Refresh the dropdown menu with current menu state""" - try: - # Get fresh menu options - fresh_options = get_tray_menu_options(tray_item) - - # Update the dropdown children - if hasattr(dropdown, 'child_window') and hasattr(dropdown.child_window, 'dropdown'): - dropdown.child_window.dropdown.children = fresh_options - - return True - except Exception: - return False - - def create_system_tray_dropdown(tray_item, parent=None): """Create a dropdown menu for a system tray item""" dropdown_children = get_tray_menu_options(tray_item) @@ -632,9 +337,6 @@ def create_system_tray_dropdown(tray_item, parent=None): child_window=dropdown ) - # Store reference to tray item for refreshing - dropdown_menu._tray_item = tray_item - # Custom positioning for system tray items def custom_get_coords_for_widget(widget): """Custom coordinate calculation for system tray items""" @@ -677,9 +379,10 @@ def patched_on_clicked(self, widget, event): """Enhanced click handler that adds dropdown functionality""" # Right click shows our custom dropdown instead of native menu if event.button == 3: - # Always refresh the dropdown to get current menu state - parent = self.get_toplevel() - self._dropdown_capture = create_system_tray_dropdown(self, parent) + if not hasattr(self, '_dropdown_capture'): + # Try to find the parent window by traversing up the widget hierarchy + parent = self.get_toplevel() + self._dropdown_capture = create_system_tray_dropdown(self, parent) # Toggle the dropdown (positioning is handled by custom positioning function) self._dropdown_capture.toggle_mousecapture()