diff --git a/src/ui/channel_manager.py b/src/ui/channel_manager.py new file mode 100644 index 0000000..b1c7f9d --- /dev/null +++ b/src/ui/channel_manager.py @@ -0,0 +1,216 @@ +""" +Channel management dialog - manage autojoin channels for a server +""" + +import logging +from PySide6.QtWidgets import ( + QDialog, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, + QPushButton, QLabel, QHeaderView, QMessageBox +) +from PySide6.QtCore import Qt +from PySide6.QtGui import QKeyEvent + +logger = logging.getLogger(__name__) + + +class ChannelManagerDialog(QDialog): + """Dialog to manage channels for a server.""" + + def __init__(self, parent, config_manager, server_config, irc_client=None): + """ + Initialize channel manager dialog. + + Args: + parent: Parent window + config_manager: ConfigManager instance + server_config: ServerConfig for the server + irc_client: Optional IRCClient instance for joining channels + """ + super().__init__(parent) + self.config_manager = config_manager + self.server_config = server_config + self.irc_client = irc_client + + self.setWindowTitle(f"Manage Channels - {server_config.name}") + self.resize(600, 400) + + self.setup_ui() + self.load_channels() + + def setup_ui(self): + """Set up the user interface.""" + layout = QVBoxLayout() + self.setLayout(layout) + + # Info label + info_label = QLabel(f"Channels for {self.server_config.name}") + info_label.setAccessibleName(f"Channels for {self.server_config.name}") + layout.addWidget(info_label) + + # Table widget + self.table = QTableWidget() + self.table.setAccessibleName("Channel list table") + self.table.setColumnCount(2) + self.table.setHorizontalHeaderLabels(["Autojoin", "Channel"]) + + # Make first column narrow for checkbox + header = self.table.horizontalHeader() + header.setSectionResizeMode(0, QHeaderView.ResizeToContents) + header.setSectionResizeMode(1, QHeaderView.Stretch) + + # Enable keyboard navigation + self.table.setFocusPolicy(Qt.StrongFocus) + self.table.setSelectionBehavior(QTableWidget.SelectRows) + self.table.setSelectionMode(QTableWidget.SingleSelection) + + # Connect signals + self.table.itemActivated.connect(self.on_item_activated) + self.table.itemChanged.connect(self.on_item_changed) + + layout.addWidget(self.table) + + # Buttons + button_layout = QHBoxLayout() + + join_button = QPushButton("Join Channel") + join_button.setAccessibleName("Join selected channel") + join_button.clicked.connect(self.on_join_clicked) + button_layout.addWidget(join_button) + + remove_button = QPushButton("Remove Channel") + remove_button.setAccessibleName("Remove selected channel from list") + remove_button.clicked.connect(self.on_remove_clicked) + button_layout.addWidget(remove_button) + + button_layout.addStretch() + + close_button = QPushButton("Close") + close_button.setAccessibleName("Close dialog") + close_button.clicked.connect(self.accept) + button_layout.addWidget(close_button) + + layout.addLayout(button_layout) + + def load_channels(self): + """Load channels from config into table.""" + self.table.setRowCount(0) + self.table.blockSignals(True) # Prevent itemChanged from firing during load + + for channel_data in self.server_config.known_channels: + channel_name = channel_data.get('name', '') + autojoin = channel_data.get('autojoin', False) + + if channel_name: + self.add_channel_row(channel_name, autojoin) + + self.table.blockSignals(False) + logger.info(f"Loaded {self.table.rowCount()} channels") + + def add_channel_row(self, channel_name, autojoin): + """Add a channel row to the table.""" + row = self.table.rowCount() + self.table.insertRow(row) + + # Autojoin checkbox + checkbox_item = QTableWidgetItem() + checkbox_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsSelectable) + checkbox_item.setCheckState(Qt.Checked if autojoin else Qt.Unchecked) + checkbox_item.setData(Qt.UserRole, channel_name) # Store channel name + # Add accessible text for screen readers + checkbox_item.setText("Checked" if autojoin else "Unchecked") + self.table.setItem(row, 0, checkbox_item) + + # Channel name + name_item = QTableWidgetItem(channel_name) + name_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) + name_item.setData(Qt.UserRole, channel_name) + self.table.setItem(row, 1, name_item) + + def on_item_changed(self, item): + """Handle checkbox state change.""" + if item.column() != 0: # Only handle autojoin column + return + + channel_name = item.data(Qt.UserRole) + new_state = item.checkState() == Qt.Checked + + logger.info(f"Autojoin changed for {channel_name}: {new_state}") + + # Update config + self.config_manager.set_channel_autojoin( + self.server_config.name, + channel_name, + new_state + ) + + # Update text for screen readers + item.setText("Checked" if new_state else "Unchecked") + + def on_item_activated(self, item): + """Handle Enter key or double-click on item.""" + # Get channel name from the row + channel_name = self.table.item(item.row(), 1).data(Qt.UserRole) + logger.info(f"Item activated: {channel_name}") + + # Join the channel if IRC client is available + if self.irc_client: + self.join_channel(channel_name) + + def on_join_clicked(self): + """Handle Join button click.""" + current_row = self.table.currentRow() + if current_row < 0: + QMessageBox.warning(self, "No Selection", "Please select a channel to join.") + return + + channel_name = self.table.item(current_row, 1).data(Qt.UserRole) + self.join_channel(channel_name) + + def join_channel(self, channel_name): + """Join a channel.""" + if not self.irc_client: + QMessageBox.warning( + self, + "Not Connected", + "Cannot join channel - not connected to server." + ) + return + + logger.info(f"Joining channel: {channel_name}") + self.irc_client.send_raw(f"JOIN {channel_name}") + + # Close dialog after joining + self.accept() + + def on_remove_clicked(self): + """Handle Remove button click.""" + current_row = self.table.currentRow() + if current_row < 0: + QMessageBox.warning(self, "No Selection", "Please select a channel to remove.") + return + + channel_name = self.table.item(current_row, 1).data(Qt.UserRole) + + reply = QMessageBox.question( + self, + "Remove Channel", + f"Remove {channel_name} from the channel list?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + + if reply == QMessageBox.Yes: + self.config_manager.remove_channel_from_server( + self.server_config.name, + channel_name + ) + self.table.removeRow(current_row) + logger.info(f"Removed channel: {channel_name}") + + def keyPressEvent(self, event: QKeyEvent): + """Handle key press events.""" + if event.key() == Qt.Key_Escape: + logger.info("Escape pressed, closing dialog") + self.accept() + else: + super().keyPressEvent(event)