Repository URL to install this package:
|
Version:
0.10.6 ▾
|
"""
TabUIManager - Manages the setup of UI tabs
"""
from PyQt6.QtWidgets import (
QVBoxLayout,
QHBoxLayout,
QLabel,
QSpacerItem,
QSplitter,
QSizePolicy,
QWidget,
QTextEdit,
QComboBox,
QPushButton,
QToolButton,
QMenu,
) # Added QToolButton, QMenu, QSplitter
from PyQt6.QtCore import Qt, QSize
from PyQt6.QtGui import QFont, QIcon, QAction
import threading # Added for graph tab
from cable_core import config_keys as keys
import logging
from typing import TYPE_CHECKING, Optional, Any, Union, List
if TYPE_CHECKING:
from PyQt6.QtWidgets import QTabWidget
from cables.connection_manager import JackConnectionManager
from cables.ui.port_tree_widget import PortTreeWidget
from cables.ui.connection_view import ConnectionView
logger = logging.getLogger(__name__)
from cables.ui.port_tree_widget import DragPortTreeWidget, DropPortTreeWidget
from cables.ui.shared_widgets import create_action_button
# Imports for Graph Tab
import graph
import graph.jack_handler
import graph.main_window
# import graph.gui_view # If needed directly
GraphJackHandler = graph.jack_handler.GraphJackHandler # Updated to GraphJackHandler
GraphMainWindow = graph.main_window.MainWindow
# GraphGuiView = graph.gui_view.JackGraphView # If JackGraphView is obtained from MainWindow instance, this isn't needed here
from cable_core import app_config
class TabUIManager:
"""
Manages the setup of UI tabs in the Cables application.
This class provides methods to set up the UI for the different tabs
in the application, including the port tabs, pw-top tab, and latency test tab.
"""
def setup_port_tab(
self,
manager: "JackConnectionManager",
tab_widget: QWidget,
tab_name: str,
port_type: str,
) -> None:
"""
Set up a port tab (Audio or MIDI).
Args:
manager: The JackConnectionManager instance
tab_widget: The widget to set up as a port tab
tab_name: The name of the tab ('Audio' or 'MIDI')
port_type: The type of ports to display ('audio' or 'midi')
"""
# Create main layout for the tab
layout = QVBoxLayout(tab_widget)
layout.setContentsMargins(0, 0, 0, 0)
# Create button widget and layout at the top
button_widget = QWidget()
button_layout = QHBoxLayout(button_widget)
button_layout.setContentsMargins(0, 0, 0, 0)
# Create buttons using shared_widgets factory
if port_type == "audio":
actual_connect_action = manager.action_manager.audio_connect_action
actual_disconnect_action = manager.action_manager.audio_disconnect_action
elif port_type == "midi":
actual_connect_action = manager.action_manager.midi_connect_action
actual_disconnect_action = manager.action_manager.midi_disconnect_action
else: # Fallback, should not be reached for valid port_types
actual_connect_action = None
actual_disconnect_action = None
connect_button = create_action_button(
parent_widget=button_widget,
action=actual_connect_action,
tooltip="Connect selected items <span style='color:grey'>C</span>",
)
disconnect_button = create_action_button(
parent_widget=button_widget,
action=actual_disconnect_action,
tooltip="Disconnect selected items <span style='color:grey'>D/Del</span>",
)
# Create per-tab undo/redo buttons
undo_button = create_action_button(
parent_widget=button_widget,
action=manager.action_manager.global_undo_action,
tooltip="Undo last connection <span style='color:grey'>Ctrl+Z</span>",
)
redo_button = create_action_button(
parent_widget=button_widget,
action=manager.action_manager.global_redo_action,
tooltip="Redo last connection <span style='color:grey'>Shift+Ctrl+Z/Ctrl+Y</span>",
)
# Add buttons to layout with stretches for centering
button_layout.addStretch()
button_layout.addWidget(connect_button)
button_layout.addWidget(disconnect_button)
button_layout.addWidget(undo_button)
button_layout.addWidget(redo_button)
button_layout.addStretch()
# Add button widget to main layout with fixed height
button_widget.setSizePolicy(
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
)
layout.addWidget(button_widget)
# Create port list widgets with labels
input_widget = QWidget()
input_layout = QVBoxLayout(input_widget)
input_layout.setContentsMargins(0, 0, 0, 0)
output_widget = QWidget()
output_layout = QVBoxLayout(output_widget)
output_layout.setContentsMargins(0, 0, 0, 0)
input_label = QLabel(f" {tab_name} Input Ports")
output_label = QLabel(f" {tab_name} Output Ports")
font = QFont()
font.setBold(True)
input_label.setFont(font)
output_label.setFont(font)
input_label.setStyleSheet(f"color: {manager.text_color.name()};")
output_label.setStyleSheet(f"color: {manager.text_color.name()};")
# Create tree widgets with appropriate roles, passing the highlight manager
input_tree = DropPortTreeWidget(
highlight_manager=manager.highlight_manager, parent=tab_widget
)
output_tree = DragPortTreeWidget(
highlight_manager=manager.highlight_manager, parent=tab_widget
)
# Create connection visualization
from PyQt6.QtWidgets import QGraphicsScene
from cables.ui.connection_view import ConnectionView
connection_scene = QGraphicsScene()
connection_view = ConnectionView(connection_scene)
connection_view.connect_to_jack_signals(manager.client)
# Apply styles
input_tree.setStyleSheet(manager.ui_manager.list_stylesheet())
output_tree.setStyleSheet(manager.ui_manager.list_stylesheet())
connection_view.setStyleSheet(
f"background: {manager.background_color.name()}; border: none;"
)
# Add spacers and labels to layouts
input_layout.addSpacerItem(
QSpacerItem(20, 10, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
)
output_layout.addSpacerItem(
QSpacerItem(20, 10, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
)
input_layout.addWidget(input_label)
input_layout.addWidget(input_tree)
output_layout.addWidget(output_label)
output_layout.addWidget(output_tree)
# Create middle widget with connection view
middle_widget = QWidget()
middle_layout = QVBoxLayout(middle_widget)
middle_layout.setContentsMargins(0, 0, 0, 0)
middle_layout.addSpacerItem(
QSpacerItem(20, 30, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
)
middle_layout.addWidget(connection_view)
# Create splitter for resizable panels
splitter = QSplitter(Qt.Orientation.Horizontal)
splitter.setHandleWidth(1)
splitter.setStyleSheet("QSplitter::handle { background: transparent; }")
splitter.addWidget(output_widget)
splitter.addWidget(middle_widget)
splitter.addWidget(input_widget)
# Set minimum widths (kept small to allow window resizing on small screens)
output_widget.setMinimumWidth(50)
input_widget.setMinimumWidth(50)
middle_widget.setMinimumWidth(20)
# Load saved splitter sizes or use defaults
connection_view_initial_width = manager.config_manager.get_int_setting(
keys.CONNECTION_VIEW_INITIAL_WIDTH, app_config.CONNECTION_VIEW_INITIAL_WIDTH
)
config_key = f"{port_type}_splitter_sizes"
saved_sizes = manager.config_manager.get_str(config_key, "")
if saved_sizes:
try:
sizes = [int(s) for s in saved_sizes.split(",")]
if len(sizes) == 3:
splitter.setSizes(sizes)
except ValueError:
logger.debug("ValueError suppressed")
if not saved_sizes or len(saved_sizes.split(",")) != 3:
# Default: equal space for trees, configured width for middle
total_width = 1200 # Approximate initial width
tree_width = (total_width - connection_view_initial_width) // 2
splitter.setSizes([tree_width, connection_view_initial_width, tree_width])
# Save splitter sizes when changed
def save_splitter_sizes() -> None:
sizes = splitter.sizes()
manager.config_manager.set_str(config_key, ",".join(str(s) for s in sizes))
connection_view.request_refresh()
splitter.splitterMoved.connect(lambda: save_splitter_sizes())
layout.addWidget(splitter)
# Store references in manager
if port_type == "audio":
manager.input_tree = input_tree
manager.output_tree = output_tree
manager.connection_scene = connection_scene
manager.connection_view = connection_view
manager.connect_button = connect_button
manager.disconnect_button = disconnect_button
elif port_type == "midi":
manager.midi_input_tree = input_tree
manager.midi_output_tree = output_tree
manager.midi_connection_scene = connection_scene
manager.midi_connection_view = connection_view
manager.midi_connect_button = connect_button
manager.midi_disconnect_button = disconnect_button
# Connect tree signals to trigger connection view refresh on scroll/expand/collapse
self._connect_tree_refresh_signals(input_tree, connection_view)
self._connect_tree_refresh_signals(output_tree, connection_view)
def _connect_tree_refresh_signals(
self, tree: "PortTreeWidget", connection_view: "ConnectionView"
) -> None:
"""
Connect tree widget signals to connection view refresh.
This enables event-driven refresh when the user scrolls, expands,
or collapses tree items, ensuring connection lines stay aligned.
Args:
tree: The PortTreeWidget to connect signals from
connection_view: The ConnectionView to refresh
"""
if not tree or not connection_view:
return
# Scroll events
if tree.verticalScrollBar():
tree.verticalScrollBar().valueChanged.connect(
lambda _: connection_view.request_refresh()
)
# Expand/collapse events
tree.itemExpanded.connect(lambda _: connection_view.request_refresh())
tree.itemCollapsed.connect(lambda _: connection_view.request_refresh())
def _setup_matrix_tab(
self, manager: "JackConnectionManager", tab_widget: QWidget, port_type: str
) -> None:
"""
Set up a Matrix tab (MIDI or Audio).
Args:
manager: The JackConnectionManager instance
tab_widget: The widget to set up as the Matrix tab
port_type: Either 'midi' or 'audio'
"""
display_name = port_type.upper()
# Create main layout for the tab
layout = QVBoxLayout(tab_widget)
layout.setContentsMargins(0, 0, 0, 0)
# Create button widget and layout at the top
button_widget = QWidget()
button_layout = QHBoxLayout(button_widget)
button_layout.setContentsMargins(0, 0, 0, 0)
# Create buttons using shared_widgets factory
presets_action = manager.action_manager.presets_action
presets_button = create_action_button(
parent_widget=button_widget, action=presets_action, tooltip="Manage Presets"
)
presets_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
# Add Node Visibility button
node_visibility_action = QAction("Clients Visibility", button_widget)
node_visibility_action.triggered.connect(
lambda _checked=False, pt=port_type: manager.show_node_visibility_dialog(
f"{pt}_matrix"
)
)
node_visibility_button = create_action_button(
parent_widget=button_widget,
action=node_visibility_action,
tooltip=f"Configure which {display_name.title()} clients should be visible in the matrix",
)
# Add Undo/Redo buttons
undo_button = create_action_button(
parent_widget=button_widget,
action=manager.action_manager.global_undo_action,
tooltip="Undo last connection <span style='color:grey'>Ctrl+Z</span>",
)
redo_button = create_action_button(
parent_widget=button_widget,
action=manager.action_manager.global_redo_action,
tooltip="Redo last connection <span style='color:grey'>Shift+Ctrl+Z/Ctrl+Y</span>",
)
# Add zoom buttons
zoom_in_button = QPushButton("+")
zoom_in_button.setToolTip(
"Zoom In <span style='color:grey'>Ctrl++/Ctrl+Scroll</span>"
)
zoom_out_button = QPushButton("-")
zoom_out_button.setToolTip(
"Zoom Out <span style='color:grey'>Ctrl+-/Ctrl+Scroll</span>"
)
zoom_button_size = QSize(25, 25)
zoom_in_button.setFixedSize(zoom_button_size)
zoom_out_button.setFixedSize(zoom_button_size)
# Add buttons to layout with stretches for centering
button_layout.addStretch()
button_layout.addWidget(undo_button)
button_layout.addWidget(redo_button)
button_layout.addWidget(presets_button)
button_layout.addWidget(node_visibility_button)
button_layout.addStretch()
button_layout.addWidget(zoom_out_button)
button_layout.addWidget(zoom_in_button)
# Add button widget to main layout
layout.addWidget(button_widget)
setattr(manager, f"{port_type}_matrix_controls_widget", button_widget)
# Bottom widget - matrix view with adjustable output labels
from cables.ui.matrix_widget import MatrixWidget
matrix_widget = MatrixWidget(manager, tab_widget, port_type=port_type)
setattr(manager, f"{port_type}_matrix_widget", matrix_widget)
setattr(
manager,
f"{port_type}_matrix_node_visibility_button",
node_visibility_button,
)
# Connect zoom buttons
zoom_in_button.clicked.connect(matrix_widget.zoom_in)
zoom_out_button.clicked.connect(matrix_widget.zoom_out)
# Add matrix widget directly to layout
layout.addWidget(matrix_widget)
def setup_midi_matrix_tab(
self, manager: "JackConnectionManager", tab_widget: QWidget
) -> None:
"""Set up the MIDI Matrix tab."""
self._setup_matrix_tab(manager, tab_widget, "midi")
def setup_audio_matrix_tab(
self, manager: "JackConnectionManager", tab_widget: QWidget
) -> None:
"""Set up the Audio Matrix tab."""
self._setup_matrix_tab(manager, tab_widget, "audio")
def setup_pwtop_tab(
self, manager: "JackConnectionManager", tab_widget: QWidget
) -> None:
"""
Set up the pw-top statistics tab.
Args:
manager: The JackConnectionManager instance
tab_widget: The widget to set up as the pw-top tab
"""
layout = QVBoxLayout(tab_widget)
# Create text display widget
pwtop_text_widget = QTextEdit()
pwtop_text_widget.setReadOnly(True)
pwtop_text_widget.setStyleSheet(f"""
QTextEdit {{
background-color: {manager.background_color.name()};
color: {manager.text_color.name()};
font-family: monospace;
font-size: {manager.config_manager.get_int_setting(keys.PWTOP_FONT_SIZE_PT, app_config.PWTOP_FONT_SIZE_PT)}pt;
}}
""")
layout.addWidget(pwtop_text_widget)
# Assign the widget to the manager
manager.pwtop_text = pwtop_text_widget
# Instantiate PwTopMonitor and store it on the manager
from cables.features.pwtop_monitor import PwTopMonitor
manager.pwtop_monitor = PwTopMonitor(manager, pwtop_text_widget)
def setup_latency_tab(
self, manager: "JackConnectionManager", tab_widget: QWidget
) -> None:
"""
Set up the Latency Test tab.
Args:
manager: The JackConnectionManager instance
tab_widget: The widget to set up as the latency test tab
"""
layout = QVBoxLayout(tab_widget)
# Instantiate LatencyTester
from cables.features.latency_tester import LatencyTester
manager.latency_tester = LatencyTester(manager)
# Instructions Label
instructions_text = (
"<b>Instructions:</b><br><br>"
"1. Ensure 'jack_delay', 'jack-delay' or 'jack_iodelay' (via 'jack-example-tools') is installed.<br>"
"2. Physically connect an output and input of your audio interface using a cable (loopback).<br>"
"3. Select the corresponding Input (Capture) and Output (Playback) ports using the dropdowns below.<br>"
"4. Click 'Start Measurement'. The selected ports will be automatically connected to jack_delay.<br>"
"(you can click 'Start Measurement' first and then try different ports)<br>"
"5. <b><font color='orange'>Warning:</font></b> Start with low volume/gain levels on your interface "
"to avoid potential damage from the test signal.<br><br>"
"After the signal is detected, the average measured round-trip latency will be shown after 10 seconds.<br><br><br><br><br>"
)
instructions_label = QLabel(instructions_text)
instructions_label.setWordWrap(True)
instructions_label.setAlignment(Qt.AlignmentFlag.AlignTop)
instructions_label.setStyleSheet(
f"color: {manager.text_color.name()}; font-size: 11pt;"
)
layout.addWidget(instructions_label)
# Combo Boxes for Port Selection
manager.latency_input_combo = QComboBox()
manager.latency_input_combo.setPlaceholderText("Select Input (Capture)...")
manager.latency_input_combo.setStyleSheet(manager.ui_manager.list_stylesheet())
manager.latency_output_combo = QComboBox()
manager.latency_output_combo.setPlaceholderText("Select Output (Playback)...")
manager.latency_output_combo.setStyleSheet(manager.ui_manager.list_stylesheet())
# Refresh Button
manager.latency_refresh_button = QPushButton("Refresh Ports")
manager.latency_refresh_button.setStyleSheet(
manager.ui_manager.button_stylesheet()
)
manager.latency_refresh_button.clicked.connect(
manager.latency_tester._populate_latency_combos
)
# Combo Boxes Layout
combo_box_container = QWidget()
combo_box_layout = QVBoxLayout(combo_box_container)
combo_box_layout.setContentsMargins(0, 0, 0, 0)
input_combo_layout = QHBoxLayout()
input_combo_layout.addWidget(manager.latency_input_combo)
input_combo_layout.addStretch(1)
combo_box_layout.addLayout(input_combo_layout)
output_combo_layout = QHBoxLayout()
output_combo_layout.addWidget(manager.latency_output_combo)
output_combo_layout.addStretch(1)
combo_box_layout.addLayout(output_combo_layout)
layout.addWidget(combo_box_container)
# Refresh Button Layout
refresh_button_layout = QHBoxLayout()
refresh_button_layout.addWidget(manager.latency_refresh_button)
refresh_button_layout.addStretch(1)
layout.addLayout(refresh_button_layout)
layout.addSpacerItem(
QSpacerItem(20, 20, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
)
# Start/Stop Buttons Layout
start_stop_button_layout = QHBoxLayout()
manager.latency_run_button = QPushButton("Start measurement")
manager.latency_run_button.setStyleSheet(manager.ui_manager.button_stylesheet())
manager.latency_run_button.clicked.connect(
manager.latency_tester.run_latency_test
)
manager.latency_stop_button = QPushButton("Stop")
manager.latency_stop_button.setStyleSheet(
manager.ui_manager.button_stylesheet()
)
manager.latency_stop_button.clicked.connect(
manager.latency_tester.stop_latency_test
)
manager.latency_stop_button.setEnabled(False)
start_stop_button_layout.addWidget(manager.latency_run_button)
start_stop_button_layout.addWidget(manager.latency_stop_button)
start_stop_button_layout.addStretch(2)
layout.addLayout(start_stop_button_layout)
# Raw Output Toggle Checkbox
from PyQt6.QtWidgets import QCheckBox
manager.latency_raw_output_checkbox = QCheckBox("Show Raw Output (Continuous)")
manager.latency_raw_output_checkbox.setToolTip(
"If 'ON', measurement has to be stopped manually with 'Stop' button"
)
manager.latency_raw_output_checkbox.setStyleSheet(
f"color: {manager.text_color.name()};"
)
# Results Text Edit
manager.latency_results_text = QTextEdit()
manager.latency_results_text.setReadOnly(True)
manager.latency_results_text.setStyleSheet(f"""
QTextEdit {{
background-color: {manager.background_color.name()};
color: {manager.text_color.name()};
font-family: monospace;
font-size: 14pt;
}}
""")
manager.latency_results_text.setText("Ready to test.")
layout.addWidget(manager.latency_results_text, 1)
layout.addWidget(
manager.latency_raw_output_checkbox
) # Add checkbox below results
# Populate combo boxes and connect signals
manager.latency_tester._populate_latency_combos()
manager.latency_input_combo.currentIndexChanged.connect(
manager.latency_tester._on_latency_input_selected
)
manager.latency_output_combo.currentIndexChanged.connect(
manager.latency_tester._on_latency_output_selected
)
def setup_graph_tab(
self, manager: "JackConnectionManager", tab_widget: QWidget
) -> None:
"""
Set up the Graph tab.
Args:
manager: The JackConnectionManager instance
tab_widget: The widget to set up as the graph tab
"""
layout = QVBoxLayout(tab_widget)
tab_widget.setLayout(layout) # Ensure layout is set for the tab_widget
# The Graph tab will now use the main jack.Client from JackConnectionManager (manager.client)
# No separate GraphJackHandler instance is created here anymore.
# Instantiate MainWindow for the graph, passing the main jack.Client,
# the JackConnectionManager (for signals), and connection_history.
# The preset_handler_ref is still needed for preset functionality within the graph.
manager.graph_main_window = GraphMainWindow(
jack_client=manager.client, # Pass the main jack.Client instance
connection_manager=manager, # Pass the JackConnectionManager for signals
preset_handler_ref=manager.preset_handler,
connection_history_ref=manager.connection_history,
# The graph's MainWindow will internally create its GraphJackHandler
# and JackGraphScene, passing the jack_client and connection_manager down.
)
# Get the central widget (JackGraphView) from the graph's MainWindow
graph_view_widget = manager.graph_main_window.centralWidget()
if graph_view_widget:
# Make sure the view can accept keyboard input - critical for shortcuts
from PyQt6.QtCore import Qt
graph_view_widget.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
# Set tab order to ensure the view gets focus when tab is clicked
tab_widget.setTabOrder(tab_widget, graph_view_widget)
# Add the widget to the layout
layout.addWidget(graph_view_widget)
# Add zoom actions directly to the view widget - this is crucial for shortcuts
if (
hasattr(manager.graph_main_window, "zoom_in_action")
and manager.graph_main_window.zoom_in_action
):
graph_view_widget.addAction(manager.graph_main_window.zoom_in_action)
if (
hasattr(manager.graph_main_window, "zoom_out_action")
and manager.graph_main_window.zoom_out_action
):
graph_view_widget.addAction(manager.graph_main_window.zoom_out_action)
# Add untangle shortcut action to the view widget
if (
hasattr(manager.graph_main_window, "untangle_shortcut_action")
and manager.graph_main_window.untangle_shortcut_action
):
graph_view_widget.addAction(
manager.graph_main_window.untangle_shortcut_action
)
# Create Node Visibility button and add it to the main window's top toolbar layout
if (
hasattr(manager.graph_main_window, "preset_button")
and manager.graph_main_window.preset_button
):
# Create the Node Visibility button
graph_node_visibility_action = QAction(
"Clients Visibility", manager.graph_main_window
)
graph_node_visibility_action.triggered.connect(
lambda: manager.show_node_visibility_dialog("graph")
)
graph_node_visibility_button = create_action_button(
parent_widget=manager.graph_main_window,
action=graph_node_visibility_action,
tooltip="Configure which nodes should be visible",
)
# Try to get the bottom toolbar layout (Presets are in the bottom toolbar)
bottom_toolbar_layout = None
if hasattr(manager.graph_main_window, "get_bottom_toolbar_layout"):
bottom_toolbar_layout = (
manager.graph_main_window.get_bottom_toolbar_layout()
)
if bottom_toolbar_layout:
# Insert after the preset button
preset_index = -1
for i in range(bottom_toolbar_layout.count()):
item = bottom_toolbar_layout.itemAt(i)
if item.widget() == manager.graph_main_window.preset_button:
preset_index = i
break
if preset_index != -1:
bottom_toolbar_layout.insertWidget(
preset_index + 1, graph_node_visibility_button
)
else:
# Fallback - insert before the trailing stretch
for i in range(bottom_toolbar_layout.count()):
item = bottom_toolbar_layout.itemAt(i)
if item.spacerItem() and i > 0:
bottom_toolbar_layout.insertWidget(
i, graph_node_visibility_button
)
break
else:
bottom_toolbar_layout.addWidget(
graph_node_visibility_button
)
else:
# Create a separate button container if we can't access the bottom toolbar
button_container = QWidget()
button_layout = QHBoxLayout(button_container)
button_layout.setContentsMargins(0, 0, 0, 0)
button_layout.addStretch(1)
button_layout.addWidget(graph_node_visibility_button)
button_layout.addStretch(1)
layout.insertWidget(0, button_container)
# Store a reference to the graph tab's node visibility button
manager.graph_node_visibility_button = graph_node_visibility_button
# Add to the internal controls to handle fullscreen mode
if hasattr(manager.graph_main_window, "_internal_controls"):
manager.graph_main_window._internal_controls.append(
graph_node_visibility_button
)
else:
# Fallback if central widget is None
error_label = QLabel("Could not load Graph View.")
error_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(error_label)
# Add zoom actions to the tab_widget as well for shortcuts to work when tab is active
if (
hasattr(manager.graph_main_window, "zoom_in_action")
and manager.graph_main_window.zoom_in_action
):
tab_widget.addAction(manager.graph_main_window.zoom_in_action)
if (
hasattr(manager.graph_main_window, "zoom_out_action")
and manager.graph_main_window.zoom_out_action
):
tab_widget.addAction(manager.graph_main_window.zoom_out_action)
# Add untangle shortcut action to the tab_widget
if (
hasattr(manager.graph_main_window, "untangle_shortcut_action")
and manager.graph_main_window.untangle_shortcut_action
):
tab_widget.addAction(manager.graph_main_window.untangle_shortcut_action)
# Store a reference to the graph tab's preset button on the connection manager
# so PresetHandler can find it.
if hasattr(manager.graph_main_window, "preset_button"):
manager.graph_tab_presets_button = manager.graph_main_window.preset_button
else:
manager.graph_tab_presets_button = None
# The graph's Jack client (the main client) is managed by JackConnectionManager,
# so no separate thread or start call is needed here for a graph-specific handler.
# Add an explicit refresh call after a short delay to populate the graph initially.
# This is still useful as the JACK client might take a moment to be fully ready
# or for initial events to propagate.
if (
hasattr(manager, "graph_main_window")
and manager.graph_main_window
and hasattr(manager.graph_main_window, "scene")
and manager.graph_main_window.scene
):
# Define a slot for the refresh
def delayed_refresh() -> None:
logger.debug(
"TabUIManager: Explicit delayed full_graph_refresh for graph tab."
)
if (
manager.graph_main_window and manager.graph_main_window.scene
): # Re-check existence
manager.graph_main_window.scene.full_graph_refresh()
from PyQt6.QtCore import (
QTimer,
) # Ensure QTimer is imported if not already at top
QTimer.singleShot(250, delayed_refresh) # Increased delay to 250ms
# Pass node visibility manager to the graph scene
if (
hasattr(manager, "node_visibility_manager")
and manager.node_visibility_manager
and hasattr(manager.graph_main_window, "scene")
and manager.graph_main_window.scene
):
manager.graph_main_window.scene.set_node_visibility_manager(
manager.node_visibility_manager
)
# Styling: For now, assume main app styling is sufficient.
# If graph-specific styles are needed, they could be applied here:
# e.g., graph_view_widget.setStyleSheet(...)
# or manager.graph_main_window.setStyleSheet(...)
# The original graph.py applies stylesheet to the QApplication.
# We might need to apply it to graph_view_widget or its parent tab.
# For simplicity, this is omitted for now.