forked from Support/sp-hydra-veil-gui
Main Menu connect/disconnect combined into a single action button, with it's state tied to a global repo. New notification system in core, pushes button changes to the main menu. Also the individual profiles get from this same global state repo, to toggle on and off by notification.
This commit is contained in:
parent
8b651aff0f
commit
9a13ae1939
3 changed files with 228 additions and 86 deletions
|
|
@ -25,7 +25,7 @@ from gui.v2.infrastructure.setup_observers import (
|
|||
# generic
|
||||
from typing import Callable, Optional
|
||||
|
||||
|
||||
@type_checker
|
||||
def get_profile(profile_id: int):
|
||||
profile = ProfileController.get(int(profile_id))
|
||||
if not profile:
|
||||
|
|
@ -61,7 +61,7 @@ def start_and_navigate_to_ticketing():
|
|||
return start_result
|
||||
|
||||
|
||||
|
||||
@type_checker
|
||||
def random_ticket_dispatch(profile_id: int, passed_ui_caller: Callable) -> Result:
|
||||
"""
|
||||
User explicitly wants a random ticket.
|
||||
|
|
@ -82,7 +82,7 @@ def random_ticket_dispatch(profile_id: int, passed_ui_caller: Callable) -> Resul
|
|||
# calls GUI repo function:
|
||||
return use_a_ticket(profile_id=profile_id, which_ticket=which_ticket, ui_caller=passed_ui_caller)
|
||||
|
||||
|
||||
@type_checker
|
||||
def dispatch_ticketing(profile_id: int, passed_ui_caller: Callable) -> Result:
|
||||
"""Evaluate if a random ticket will be used."""
|
||||
|
||||
|
|
@ -156,7 +156,7 @@ def switch_to_tor_connection() -> Result:
|
|||
ConfigurationController.set_connection_enum(ConnectionChoice.TOR)
|
||||
return Result(valid=True)
|
||||
|
||||
|
||||
@type_checker
|
||||
def NOT_need_tor_warnings(profile_id: int) -> Result:
|
||||
"""
|
||||
Purpose:
|
||||
|
|
@ -226,7 +226,7 @@ def literally_connect(
|
|||
else:
|
||||
return Result(valid=True)
|
||||
except Exception as error:
|
||||
return translate_error(error=error, profile_id=profile_id)
|
||||
return translate_error(error=error, profile_id=profile.id)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ class ConnectionManager:
|
|||
return self.profile_button_objects.get(profile_id, None)
|
||||
|
||||
def set_profile_button_objects(self, profile_id, profile_button_objects):
|
||||
self.profile_button_objects[profile_id] = profile_button_objects
|
||||
pass
|
||||
# self.profile_button_objects[profile_id] = profile_button_objects
|
||||
|
||||
def get_available_resolutions(self, profile_id):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
from core.infrastructure.global_state_coordinator import connected_session_profile_ids, connected_system_profile_id, is_systemwide_on, is_connected
|
||||
from core.controllers.ProfileController import ProfileController
|
||||
from core.models.BaseProfile import ProfileType
|
||||
from core.models.Result import Result, ResultError
|
||||
from core.observers.Notifications import Notifications, EventType
|
||||
|
||||
import functools
|
||||
import os
|
||||
|
|
@ -34,6 +37,10 @@ from gui.v2.infrastructure.setup_observers import (
|
|||
profile_observer
|
||||
)
|
||||
|
||||
from gui.v2.infrastructure.ThreadSafetyTool import ThreadSafetyTool
|
||||
mainthread = ThreadSafetyTool()
|
||||
|
||||
from typing import Optional
|
||||
|
||||
class MenuPage(Page):
|
||||
def __init__(self, page_stack=None, main_window=None, parent=None):
|
||||
|
|
@ -77,10 +84,12 @@ class MenuPage(Page):
|
|||
self.font_style = f"font-family: '{main_window.open_sans_family}';" if main_window.open_sans_family else ""
|
||||
|
||||
self.create_interface_elements()
|
||||
self.observers_subscribe()
|
||||
|
||||
def showEvent(self, event):
|
||||
super().showEvent(event)
|
||||
self._set_verification_icons_enabled()
|
||||
self.state_refresh()
|
||||
|
||||
def _set_verification_icons_enabled(self):
|
||||
parents = filter(None, (button.parent() for button in getattr(self, 'buttons', [])))
|
||||
|
|
@ -105,41 +114,91 @@ class MenuPage(Page):
|
|||
button_definitions = {
|
||||
"create_profile": (self.create_prof, (410, 440, 110, 60), True, True),
|
||||
"edit_profile": (self.edit_prof, (540, 440, 110, 60), False, True),
|
||||
"launch": (self.connect, (0, 440, 185, 63), False, False),
|
||||
"disconnect": (self.disconnect, (200, 433, 185, 73), True, False),
|
||||
"disconnect_system_wide": (self.disconnect, (200, 433, 185, 73), True, False),
|
||||
"just": (self.connect, (200, 433, 185, 73), False, False),
|
||||
"just_session": (self.connect, (200, 433, 185, 73), False, True),
|
||||
"settings": (self.settings_gui, (670, 440, 110, 60), True, True),
|
||||
"action_button": (self.handle_action_click, (200, 433, 185, 73), True, True),
|
||||
}
|
||||
|
||||
buttons = {}
|
||||
for icon_name, (function, position, enabled, visible) in button_definitions.items():
|
||||
boton = QPushButton(self)
|
||||
boton.setGeometry(QtCore.QRect(*position))
|
||||
boton.setObjectName("create_edit_settings_buttons")
|
||||
# Note: action_button icon is handled dynamically in update_action_button()
|
||||
if icon_name != "action_button":
|
||||
icon = QtGui.QIcon(os.path.join(self.btn_path, f"{icon_name}.png"))
|
||||
boton.setIcon(icon)
|
||||
boton.setIconSize(boton.size())
|
||||
|
||||
boton.clicked.connect(functools.partial(function))
|
||||
boton.setEnabled(enabled)
|
||||
boton.setVisible(visible)
|
||||
buttons[icon_name] = boton
|
||||
|
||||
button_attributes = {
|
||||
"disconnect_button": "disconnect",
|
||||
"boton_launch": "launch",
|
||||
"boton_create": "create_profile",
|
||||
"boton_edit": "edit_profile",
|
||||
"boton_just": "just",
|
||||
"boton_just_session": "just_session",
|
||||
"connect_button": "just_session",
|
||||
"disconnect_system_wide_button": "disconnect_system_wide",
|
||||
"settings_button": "settings",
|
||||
}
|
||||
self.__dict__.update({attribute: buttons[name] for attribute, name in button_attributes.items()})
|
||||
# Map the specific buttons back to the class
|
||||
self.boton_create = buttons["create_profile"]
|
||||
self.boton_edit = buttons["edit_profile"]
|
||||
self.settings_button = buttons["settings"]
|
||||
self.action_button = buttons["action_button"]
|
||||
self.action_button.setIconSize(self.action_button.size())
|
||||
|
||||
self.refresh_menu_buttons()
|
||||
|
||||
|
||||
def update_action_button_visuals(self, triggered_profile_id: Optional[int] = None):
|
||||
"""Syncs the button art and enabled state with the current external truth."""
|
||||
|
||||
# Which profile is selected:
|
||||
profile_id = getattr(self.update_status, 'current_profile_id', None)
|
||||
if profile_id is None:
|
||||
self.action_button.setVisible(False)
|
||||
return
|
||||
|
||||
# triggered_profile_id is coming via a Notification.
|
||||
# Don't update the button via from a notification, if the user clicked to a different profile,
|
||||
if triggered_profile_id and profile_id != triggered_profile_id:
|
||||
return
|
||||
|
||||
self.action_button.setVisible(True)
|
||||
self.action_button.setEnabled(True) # Re-enable after the Event has fired
|
||||
|
||||
connected = is_connected(profile_id)
|
||||
p_type = ProfileController.what_type_is_profile(profile_id)
|
||||
|
||||
# if it's connected, then it can't be edited.
|
||||
self.boton_edit.setEnabled(not connected)
|
||||
|
||||
# Logic Matrix for Art
|
||||
if p_type == ProfileType.SESSION:
|
||||
icon_name = "disconnect" if connected else "just_session"
|
||||
else: # SYSTEM
|
||||
icon_name = "disconnect_system_wide" if connected else "just"
|
||||
|
||||
print(f"Setting icon_name to {icon_name}")
|
||||
icon = QtGui.QIcon(os.path.join(self.btn_path, f"{icon_name}.png"))
|
||||
self.action_button.setIcon(icon)
|
||||
|
||||
|
||||
def handle_action_click(self):
|
||||
"""Route the action button to connect or disconnect, depending on state"""
|
||||
|
||||
# 1. Relevant profile ID:
|
||||
profile_id = getattr(self.update_status, 'current_profile_id', None)
|
||||
if profile_id is None:
|
||||
return
|
||||
|
||||
# 2. Immediately disable to prevent double-clicks during async transition
|
||||
self.action_button.setEnabled(False)
|
||||
|
||||
# 3. Connection Status
|
||||
connected = is_connected(profile_id)
|
||||
|
||||
# 4. Route:
|
||||
if connected:
|
||||
self.disconnect()
|
||||
else:
|
||||
self.connect()
|
||||
|
||||
|
||||
def _make_confirmation_popup(self, message, button_text='Proceed'):
|
||||
popup = ConfirmationPopup(
|
||||
self, message=message, action_button_text=button_text, cancel_button_text="Cancel")
|
||||
|
|
@ -165,7 +224,8 @@ class MenuPage(Page):
|
|||
icon_paths = {"session_tor": self.connected_tor_profile,
|
||||
"session": self.connected_profile, "system": self.system_wide_connected_profile}
|
||||
self.button_states = {profile_id: icon_paths[kind] for profile_id, kind in menu_data["enabled_kinds"].items()}
|
||||
self.update_status.update_image(appearance_value=menu_data["background"])
|
||||
|
||||
|
||||
for button in getattr(self, 'buttons', []):
|
||||
button.parent().deleteLater()
|
||||
if hasattr(self, 'button_group'):
|
||||
|
|
@ -200,12 +260,8 @@ class MenuPage(Page):
|
|||
self.update_status.current_profile_id = None
|
||||
self.current_profile_location = None
|
||||
self.reverse_id = None
|
||||
|
||||
self.boton_edit.setEnabled(False)
|
||||
self.boton_launch.setEnabled(False)
|
||||
self.boton_just.setVisible(False)
|
||||
self.boton_just_session.setVisible(False)
|
||||
self.disconnect_button.setVisible(False)
|
||||
self.disconnect_system_wide_button.setVisible(False)
|
||||
if not hasattr(self, 'button_group'):
|
||||
return
|
||||
self.button_group.setExclusive(False)
|
||||
|
|
@ -234,14 +290,13 @@ class MenuPage(Page):
|
|||
parent_label.setVisible(True)
|
||||
return parent_label
|
||||
|
||||
|
||||
def create_profile_button(self, parent_label, profile_id, key):
|
||||
button = QPushButton(parent_label)
|
||||
button.setGeometry(0, 0, 175, 60)
|
||||
button.setStyleSheet(CREATE_BUTTON_STYLE)
|
||||
|
||||
icon = QIcon(self.button_states.get(
|
||||
profile_id, self.not_connected_profile))
|
||||
button.setIcon(icon)
|
||||
button.setIcon(QIcon(self.not_connected_profile))
|
||||
button.setIconSize(QtCore.QSize(175, 60))
|
||||
|
||||
button.show()
|
||||
|
|
@ -393,8 +448,7 @@ class MenuPage(Page):
|
|||
|
||||
def _render_profile_details(self, details):
|
||||
|
||||
print(f"details entering _render_profile_details is {details}")
|
||||
|
||||
# print(f"details entering _render_profile_details is {details}")
|
||||
|
||||
self.reverse_id = details["id"]
|
||||
|
||||
|
|
@ -402,16 +456,13 @@ class MenuPage(Page):
|
|||
self.update_status.current_profile_id = self.reverse_id
|
||||
|
||||
self.current_profile_location = details["location"]
|
||||
target_button = self.profile_button_map.get(self.reverse_id)
|
||||
if target_button is not None:
|
||||
target_button.setChecked(True)
|
||||
self.connection_manager.set_profile_button_objects(self.reverse_id, target_button)
|
||||
self.boton_edit.setEnabled(not details["is_connected"] and not self._task_busy)
|
||||
self.boton_launch.setEnabled(not self._task_busy)
|
||||
self.boton_just.setEnabled(not self._task_busy)
|
||||
self.boton_just_session.setEnabled(not self._task_busy)
|
||||
|
||||
# Update buttons:
|
||||
self.update_action_button_visuals(self.reverse_id)
|
||||
|
||||
# verification icons:
|
||||
self._set_verification_icons_enabled()
|
||||
self.update_button_visibility(details["is_connected"], details["is_session"])
|
||||
|
||||
self.update_status_message(details)
|
||||
for label in self.additional_labels:
|
||||
label.deleteLater()
|
||||
|
|
@ -511,15 +562,16 @@ class MenuPage(Page):
|
|||
self.page_stack.indexOf(verification_page))
|
||||
|
||||
def _set_task_busy(self, busy):
|
||||
self._task_busy = busy
|
||||
for button in (self.boton_just, self.boton_just_session, self.disconnect_button,
|
||||
self.disconnect_system_wide_button, self.boton_create, self.boton_edit):
|
||||
button.setEnabled(not busy)
|
||||
self.scroll_area.setEnabled(not busy)
|
||||
self.boton_just.setEnabled(not busy and self.reverse_id is not None)
|
||||
self.boton_just_session.setEnabled(not busy and self.reverse_id is not None)
|
||||
self.boton_edit.setEnabled(not busy and self.reverse_id is not None
|
||||
and not self.connection_manager.is_profile_connected(self.reverse_id))
|
||||
pass
|
||||
# self._task_busy = busy
|
||||
# for button in (self.boton_just, self.boton_just_session, self.disconnect_button,
|
||||
# self.disconnect_system_wide_button, self.boton_create, self.boton_edit):
|
||||
# button.setEnabled(not busy)
|
||||
# self.scroll_area.setEnabled(not busy)
|
||||
# self.boton_just.setEnabled(not busy and self.reverse_id is not None)
|
||||
# self.boton_just_session.setEnabled(not busy and self.reverse_id is not None)
|
||||
# self.boton_edit.setEnabled(not busy and self.reverse_id is not None
|
||||
# and not self.connection_manager.is_profile_connected(self.reverse_id))
|
||||
|
||||
|
||||
def post_connect(self, result: Result):
|
||||
|
|
@ -537,18 +589,6 @@ class MenuPage(Page):
|
|||
# CONNECTION WORKED
|
||||
# --------------------
|
||||
if result.valid:
|
||||
# BACKGROUND CONNECTION IMAGE:
|
||||
self.update_status.update_image(appearance_value="background_connected")
|
||||
|
||||
# BUTTON:
|
||||
profile_type = result.data
|
||||
if profile_type == ProfileType.SYSTEM:
|
||||
self.disconnect_system_wide_button.setVisible(True)
|
||||
elif profile_type == ProfileType.SESSION:
|
||||
self.disconnect_button.setVisible(True)
|
||||
else:
|
||||
print("This is an error where the GUI recieved an invalid profile type, but we are enabling the systemwide by default.")
|
||||
self.disconnect_system_wide_button.setVisible(True)
|
||||
return
|
||||
|
||||
# --------------------
|
||||
|
|
@ -571,10 +611,8 @@ class MenuPage(Page):
|
|||
"""Enable the profile id tied to reverse_id"""
|
||||
print("Starting to connect..")
|
||||
|
||||
# Step 1. Disable all buttons to prevent repeat pushing.
|
||||
self.boton_just.setVisible(False)
|
||||
self.boton_launch.setEnabled(False)
|
||||
self.boton_just_session.setEnabled(False)
|
||||
# # Step 1. Disable edit button.
|
||||
self.boton_edit.setEnabled(False)
|
||||
|
||||
# Step 2. Connect with post_connect setup to recieve the result.
|
||||
immediate_error = connect_profile(profile_id=self.reverse_id, ui_caller=self.post_connect)
|
||||
|
|
@ -584,13 +622,6 @@ class MenuPage(Page):
|
|||
self.update_status.update_status(immediate_error.message)
|
||||
return
|
||||
|
||||
# Temporary until moved to menu coordinator:
|
||||
# print("connecting...")
|
||||
# self._set_task_busy(True)
|
||||
# self.update_status.update_status("Attempting to connect...")
|
||||
# self.worker = enable_profile({"id": self.reverse_id}, self.handle_task_result)
|
||||
|
||||
|
||||
def disconnect(self):
|
||||
self._set_task_busy(True)
|
||||
self.update_status.update_status("Disconnecting...")
|
||||
|
|
@ -602,14 +633,6 @@ class MenuPage(Page):
|
|||
self.worker = create_profile(getattr(self.update_status, "gui_config_file", None),
|
||||
self.connection_manager.is_synced(), self.handle_task_result)
|
||||
|
||||
def update_button_visibility(self, is_connected, is_session_profile):
|
||||
self.boton_just_session.setVisible(
|
||||
not is_connected and is_session_profile)
|
||||
self.boton_just.setVisible(not is_connected and not is_session_profile)
|
||||
self.disconnect_button.setVisible(is_connected and is_session_profile)
|
||||
self.disconnect_system_wide_button.setVisible(
|
||||
is_connected and not is_session_profile)
|
||||
|
||||
def update_status_message(self, state):
|
||||
if not state["profile_loaded"]:
|
||||
self.update_status.update_status("Profile data could not be loaded. Sync or restart and try again.")
|
||||
|
|
@ -707,3 +730,121 @@ class MenuPage(Page):
|
|||
|
||||
def _show_ticket_loss_result(self, result):
|
||||
self.show_ticket_data_loss_popup(result["ticket_number"], result["billing_code"])
|
||||
|
||||
|
||||
def _update_profile_icon(self, profile_id: int, image_path: str, is_active: bool = False):
|
||||
"""Helper to update profile icons and the 'checked' state for the border/highlight."""
|
||||
button = self.profile_button_map.get(profile_id)
|
||||
if button:
|
||||
button.setIcon(QIcon(image_path))
|
||||
# This ensures the UI 'checked' state matches the connection state
|
||||
button.setChecked(is_active)
|
||||
|
||||
|
||||
def profile_is_currently_selected(self, profile_id: int) -> bool:
|
||||
"""Checks if the profile that's currently selected by the user, matches profile_id"""
|
||||
return self.update_status.current_profile_id == profile_id
|
||||
|
||||
@mainthread
|
||||
def turn_on_session_visuals(self, profile_id: int):
|
||||
"""Flip off the Visual buttons for a given profile."""
|
||||
print(f"Turning ON Session {profile_id}!")
|
||||
|
||||
# Update the specific profile button
|
||||
self._update_profile_icon(profile_id, self.connected_profile, is_active=True)
|
||||
|
||||
# Update disconnect/action button:
|
||||
self.update_action_button_visuals(profile_id)
|
||||
|
||||
@mainthread
|
||||
def turn_off_session_visuals(self, profile_id: int):
|
||||
"""
|
||||
Flip off the Visual buttons for a given profile.
|
||||
And re-enable the edit button if its currently selected.
|
||||
"""
|
||||
print(f"Stopping Session {profile_id}!")
|
||||
# If you want the border to stay when off, set is_active=True
|
||||
# If you want the border to disappear when off, set is_active=False
|
||||
self._update_profile_icon(profile_id, self.not_connected_profile, is_active=True)
|
||||
|
||||
# Update disconnect/action button:
|
||||
self.update_action_button_visuals(profile_id)
|
||||
|
||||
# Re-enable edit button:
|
||||
if self.profile_is_currently_selected(profile_id):
|
||||
self.boton_edit.setEnabled(True)
|
||||
|
||||
|
||||
@mainthread
|
||||
def turn_on_system_visuals(self, profile_id: int):
|
||||
print(f"Turning ON Systemwide on {profile_id}!")
|
||||
|
||||
# Update the specific profile button
|
||||
self._update_profile_icon(profile_id, self.system_wide_connected_profile, is_active=True)
|
||||
|
||||
# Update the general background status
|
||||
self.update_status.update_image(appearance_value="background_connected")
|
||||
|
||||
# Update disconnect/action button:
|
||||
self.update_action_button_visuals(profile_id)
|
||||
|
||||
@mainthread
|
||||
def turn_off_system_visuals(self, profile_id: int):
|
||||
"""
|
||||
Flip off the Visual buttons for a given System-wide.
|
||||
Change the entire app's background for connected.
|
||||
And re-enable the edit button if its currently selected.
|
||||
"""
|
||||
print(f"Turning OFF Systemwide on {profile_id}!")
|
||||
|
||||
# Update the specific profile button
|
||||
self._update_profile_icon(profile_id, self.not_connected_profile, is_active=True)
|
||||
|
||||
# Update the general background status
|
||||
self.update_status.update_image(appearance_value="original")
|
||||
|
||||
# Update connect/action button:
|
||||
self.update_action_button_visuals(profile_id)
|
||||
|
||||
# Re-enable edit button:
|
||||
if self.profile_is_currently_selected(profile_id):
|
||||
self.boton_edit.setEnabled(True)
|
||||
|
||||
def observers_subscribe(self):
|
||||
Notifications.subscribe(EventType.SESSION_PROFILE_ENABLE, self.turn_on_session_visuals)
|
||||
Notifications.subscribe(EventType.SESSION_PROFILE_DISABLE, self.turn_off_session_visuals)
|
||||
Notifications.subscribe(EventType.SYSTEM_ENABLE, self.turn_on_system_visuals)
|
||||
Notifications.subscribe(EventType.SYSTEM_DISABLE, self.turn_off_system_visuals)
|
||||
|
||||
|
||||
def state_refresh(self):
|
||||
"""Update the UI with the current Global State Repo"""
|
||||
|
||||
# 1. Collect current state
|
||||
sys_on = is_systemwide_on()
|
||||
sys_id = connected_system_profile_id()
|
||||
sess_ids = connected_session_profile_ids() or []
|
||||
|
||||
# 2. Set the global background
|
||||
if sys_on:
|
||||
self.update_status.update_image(appearance_value="background_connected")
|
||||
else:
|
||||
self.update_status.update_image(appearance_value="original")
|
||||
|
||||
# 3. Sync every button
|
||||
for pid in self.profile_button_map.keys():
|
||||
# We check for the "On" states first.
|
||||
# If none of these match, it falls through to the default 'off' state.
|
||||
|
||||
if sys_on and pid == sys_id:
|
||||
# Priority 1: System-wide active
|
||||
self.turn_on_system_visuals(pid)
|
||||
|
||||
elif pid in sess_ids:
|
||||
# Priority 2: Session active
|
||||
self.turn_on_session_visuals(pid)
|
||||
|
||||
else:
|
||||
# DEFAULT STARTING PLACE:
|
||||
# If it's not system-wide and not a session, it must be off.
|
||||
self.turn_off_session_visuals(pid)
|
||||
|
|
|
|||
Loading…
Reference in a new issue