Source code for qtframework.widgets.advanced.tabs
"""Advanced tab widgets for the framework."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import (
QCheckBox,
QComboBox,
QDoubleSpinBox,
QFormLayout,
QGroupBox,
QLabel,
QLineEdit,
QSlider,
QSpinBox,
QTabBar,
QTabWidget,
QVBoxLayout,
)
from qtframework.widgets.base import Widget
if TYPE_CHECKING:
from PySide6.QtWidgets import (
QWidget,
)
[docs]
class TabWidget(Widget):
"""Enhanced tab widget with framework integration."""
tab_changed = Signal(int)
tab_close_requested = Signal(int)
def __init__(
self,
parent: QWidget | None = None,
*,
closeable_tabs: bool = False,
moveable_tabs: bool = True,
) -> None:
"""Initialize tab widget.
Args:
parent: Parent widget
closeable_tabs: Whether tabs can be closed
moveable_tabs: Whether tabs can be moved/reordered
"""
super().__init__(parent)
# Main layout
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
# Create tab widget
self._tab_widget = QTabWidget()
if closeable_tabs:
self._tab_widget.setTabsClosable(True)
self._tab_widget.tabCloseRequested.connect(self.tab_close_requested.emit)
if moveable_tabs:
self._tab_widget.setMovable(True)
self._tab_widget.currentChanged.connect(self.tab_changed.emit)
layout.addWidget(self._tab_widget)
# Store tab metadata
self._tab_data: dict[int, dict[str, Any]] = {}
[docs]
def add_tab(
self,
widget: QWidget,
title: str,
*,
icon: Any = None,
closeable: bool | None = None,
data: dict[str, Any] | None = None,
) -> int:
"""Add a tab to the widget.
Args:
widget: The widget to add as a tab
title: Tab title
icon: Optional tab icon
closeable: Whether this specific tab is closeable (overrides global setting)
data: Optional metadata for the tab
Returns:
Index of the added tab
"""
if icon:
index = self._tab_widget.addTab(widget, icon, title)
else:
index = self._tab_widget.addTab(widget, title)
# Store tab data
self._tab_data[index] = data or {}
# Set tab-specific closeable state if provided
if closeable is not None:
tab_bar = self._tab_widget.tabBar()
position = (
QTabBar.ButtonPosition.RightSide if closeable else QTabBar.ButtonPosition.LeftSide
)
tab_bar.setTabButton(index, position, None)
return int(index)
[docs]
def insert_tab(
self,
index: int,
widget: QWidget,
title: str,
*,
icon: Any = None,
data: dict[str, Any] | None = None,
) -> int:
"""Insert a tab at the specified index.
Args:
index: Position to insert the tab
widget: The widget to add as a tab
title: Tab title
icon: Optional tab icon
data: Optional metadata for the tab
Returns:
Index of the inserted tab
"""
if icon:
actual_index = self._tab_widget.insertTab(index, widget, icon, title)
else:
actual_index = self._tab_widget.insertTab(index, widget, title)
# Update tab data indices and store new data
new_tab_data = {}
for i, tab_data in self._tab_data.items():
if i >= index:
new_tab_data[i + 1] = tab_data
else:
new_tab_data[i] = tab_data
new_tab_data[actual_index] = data or {}
self._tab_data = new_tab_data
return int(actual_index)
[docs]
def remove_tab(self, index: int) -> None:
"""Remove tab at the specified index.
Args:
index: Index of tab to remove
"""
self._tab_widget.removeTab(index)
# Update tab data indices
if index in self._tab_data:
del self._tab_data[index]
new_tab_data = {}
for i, tab_data in self._tab_data.items():
if i > index:
new_tab_data[i - 1] = tab_data
else:
new_tab_data[i] = tab_data
self._tab_data = new_tab_data
[docs]
def set_tab_title(self, index: int, title: str) -> None:
"""Set the title of a tab.
Args:
index: Tab index
title: New title
"""
self._tab_widget.setTabText(index, title)
[docs]
def get_tab_title(self, index: int) -> str:
"""Get the title of a tab.
Args:
index: Tab index
Returns:
Tab title
"""
return str(self._tab_widget.tabText(index))
[docs]
def set_tab_enabled(self, index: int, enabled: bool) -> None:
"""Enable or disable a tab.
Args:
index: Tab index
enabled: Whether the tab should be enabled
"""
self._tab_widget.setTabEnabled(index, enabled)
[docs]
def is_tab_enabled(self, index: int) -> bool:
"""Check if a tab is enabled.
Args:
index: Tab index
Returns:
True if tab is enabled
"""
return bool(self._tab_widget.isTabEnabled(index))
[docs]
def set_current_index(self, index: int) -> None:
"""Set the current tab by index.
Args:
index: Tab index to make current
"""
self._tab_widget.setCurrentIndex(index)
[docs]
def current_index(self) -> int:
"""Get the current tab index.
Returns:
Current tab index
"""
return int(self._tab_widget.currentIndex())
[docs]
def current_widget(self) -> QWidget | None:
"""Get the current tab widget.
Returns:
Current tab widget or None
"""
return self._tab_widget.currentWidget()
[docs]
def widget(self, index: int) -> QWidget | None:
"""Get the widget at the specified tab index.
Args:
index: Tab index
Returns:
Tab widget or None
"""
return self._tab_widget.widget(index)
[docs]
def count(self) -> int:
"""Get the number of tabs.
Returns:
Number of tabs
"""
return int(self._tab_widget.count())
[docs]
def get_tab_data(self, index: int) -> dict[str, Any]:
"""Get metadata for a tab.
Args:
index: Tab index
Returns:
Tab metadata dictionary
"""
return self._tab_data.get(index, {})
[docs]
def set_tab_data(self, index: int, data: dict[str, Any]) -> None:
"""Set metadata for a tab.
Args:
index: Tab index
data: Metadata dictionary
"""
self._tab_data[index] = data
[docs]
def set_tab_position(self, position: str) -> None:
"""Set the position of tab bar.
Args:
position: One of 'north', 'south', 'west', 'east'
"""
position_map = {
"north": QTabWidget.TabPosition.North,
"south": QTabWidget.TabPosition.South,
"west": QTabWidget.TabPosition.West,
"east": QTabWidget.TabPosition.East,
}
if position in position_map:
self._tab_widget.setTabPosition(position_map[position])
[docs]
def clear(self) -> None:
"""Remove all tabs."""
self._tab_widget.clear()
self._tab_data.clear()
[docs]
class BaseTabPage(Widget):
"""Base class for tab page widgets."""
value_changed = Signal(str, object) # key, value
def __init__(
self,
data: dict[str, Any] | None = None,
parent: QWidget | None = None,
) -> None:
"""Initialize tab page.
Args:
data: Data for this tab page
parent: Parent widget
"""
super().__init__(parent)
self._data = data or {}
self._controls: dict[str, QWidget] = {}
# Main layout
self._layout = QVBoxLayout(self)
self._layout.setContentsMargins(20, 20, 20, 20)
self._layout.setSpacing(15)
self._setup_ui()
self._connect_signals()
self._load_values()
def _setup_ui(self) -> None:
"""Setup the UI. Override in subclasses."""
def _connect_signals(self) -> None:
"""Connect control signals. Override in subclasses."""
def _load_values(self) -> None:
"""Load values from data into controls."""
for key, control in self._controls.items():
if key in self._data:
self._set_control_value(control, self._data[key])
def _set_control_value(self, control: QWidget, value: Any) -> None:
"""Set value of a control widget."""
if isinstance(control, QLineEdit):
control.setText(str(value))
elif isinstance(control, QSpinBox | QDoubleSpinBox):
control.setValue(value)
elif isinstance(control, QCheckBox):
control.setChecked(bool(value))
elif isinstance(control, QComboBox):
index = control.findText(str(value))
if index >= 0:
control.setCurrentIndex(index)
elif isinstance(control, QSlider):
control.setValue(int(value))
def _get_control_value(self, control: QWidget) -> Any:
"""Get value from a control widget."""
if isinstance(control, QLineEdit):
return control.text()
if isinstance(control, QSpinBox | QDoubleSpinBox):
return control.value()
if isinstance(control, QCheckBox):
return control.isChecked()
if isinstance(control, QComboBox):
return control.currentText()
if isinstance(control, QSlider):
return control.value()
return None
[docs]
def get_values(self) -> dict[str, Any]:
"""Get all values from the tab controls."""
values = {}
for key, control in self._controls.items():
values[key] = self._get_control_value(control)
return values
[docs]
def update_data(self, data: dict[str, Any]) -> None:
"""Update data and refresh controls."""
self._data = data
self._load_values()
def _create_group(self, title: str) -> QGroupBox:
"""Create a group box with title."""
group = QGroupBox(title)
group.setLayout(QFormLayout())
return group
def _add_control_to_group(
self,
group: QGroupBox,
label: str,
control: QWidget,
key: str,
) -> None:
"""Add a control to a group with proper labeling."""
self._controls[key] = control
layout = group.layout()
if isinstance(layout, QFormLayout):
layout.addRow(label, control)
def _create_slider_with_label(
self,
min_val: int,
max_val: int,
current_val: int,
suffix: str = "",
) -> tuple[QSlider, QLabel]:
"""Create a slider with value label."""
slider = QSlider(Qt.Orientation.Horizontal)
slider.setMinimum(min_val)
slider.setMaximum(max_val)
slider.setValue(current_val)
label = QLabel(f"{current_val}{suffix}")
slider.valueChanged.connect(lambda v: label.setText(f"{v}{suffix}"))
return slider, label