From dd8ed70958b6c24d32f99141f67a53e1ee2e1e7f Mon Sep 17 00:00:00 2001 From: "emdee@spm.plastiras.org" Date: Sat, 10 Feb 2024 20:32:31 +0000 Subject: [PATCH] qt6 fixed --- requirements.txt | 7 +- toxygen/__init__.py | 1 + toxygen/third_party/qweechat/about.py | 2 +- toxygen/third_party/qweechat/buffer.py | 7 +- toxygen/third_party/qweechat/chat.py | 4 +- toxygen/third_party/qweechat/connection.py | 10 +- toxygen/third_party/qweechat/debug.py | 4 +- toxygen/third_party/qweechat/input.py | 1 - toxygen/third_party/qweechat/network.py | 27 +- toxygen/third_party/qweechat/preferences.py | 536 ++++++++++++++++- .../third_party/qweechat/preferences.py.bak | 57 ++ toxygen/third_party/qweechat/qweechat.py | 69 +-- toxygen/third_party/qweechat/qweechat.py.bak | 569 ++++++++++++++++++ 13 files changed, 1212 insertions(+), 82 deletions(-) create mode 100644 toxygen/third_party/qweechat/preferences.py.bak create mode 100644 toxygen/third_party/qweechat/qweechat.py.bak diff --git a/requirements.txt b/requirements.txt index 09926ea..35df599 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,9 @@ # the versions are the current ones tested - may work with earlier versions # choose one of PyQt5 PyQt6 PySide2 PySide6 -# for now only PyQt5 is working: -# AttributeError: module 'qtpy.QtCore' has no attribute 'pyqtSignal' -PyQt5 >= 5.15.10 +# for now PyQt5 and PyQt6 is working, and most of the testing is PyQt5 +# usually this is installed by your OS package manager and pip may not +# detect the right version, so we leave these commented +# PyQt5 >= 5.15.10 QtPy >= 2.4.1 PyAudio >= 0.2.13 numpy >= 1.26.1 diff --git a/toxygen/__init__.py b/toxygen/__init__.py index 70180be..56fcde8 100644 --- a/toxygen/__init__.py +++ b/toxygen/__init__.py @@ -5,4 +5,5 @@ path = os.path.dirname(os.path.realpath(__file__)) # curr dir sys.path.insert(0, os.path.join(path, 'styles')) sys.path.insert(0, os.path.join(path, 'plugins')) +sys.path.insert(0, os.path.join(path, 'third_party')) sys.path.insert(0, path) diff --git a/toxygen/third_party/qweechat/about.py b/toxygen/third_party/qweechat/about.py index 26dcd90..8ec87ab 100644 --- a/toxygen/third_party/qweechat/about.py +++ b/toxygen/third_party/qweechat/about.py @@ -24,7 +24,7 @@ from qtpy import QtCore, QtWidgets as QtGui -from third_party.qweechat.version import qweechat_version +from qweechat.version import qweechat_version class AboutDialog(QtGui.QDialog): diff --git a/toxygen/third_party/qweechat/buffer.py b/toxygen/third_party/qweechat/buffer.py index d012cd8..9cca40e 100644 --- a/toxygen/third_party/qweechat/buffer.py +++ b/toxygen/third_party/qweechat/buffer.py @@ -25,12 +25,11 @@ from pkg_resources import resource_filename from qtpy import QtCore, QtGui, QtWidgets -# from PyQt5.QtCore import pyqtSignal as Signal from qtpy.QtCore import Signal -from third_party.qweechat.chat import ChatTextEdit -from third_party.qweechat.input import InputLineEdit -from third_party.qweechat.weechat import color +from qweechat.chat import ChatTextEdit +from qweechat.input import InputLineEdit +from qweechat.weechat import color class GenericListWidget(QtWidgets.QListWidget): diff --git a/toxygen/third_party/qweechat/chat.py b/toxygen/third_party/qweechat/chat.py index 9bd1efe..43c4568 100644 --- a/toxygen/third_party/qweechat/chat.py +++ b/toxygen/third_party/qweechat/chat.py @@ -26,8 +26,8 @@ import datetime from qtpy import QtCore, QtWidgets, QtGui -from third_party.qweechat import config -from third_party.qweechat.weechat import color +from qweechat import config +from qweechat.weechat import color class ChatTextEdit(QtWidgets.QTextEdit): diff --git a/toxygen/third_party/qweechat/connection.py b/toxygen/third_party/qweechat/connection.py index 6777122..7f1884d 100644 --- a/toxygen/third_party/qweechat/connection.py +++ b/toxygen/third_party/qweechat/connection.py @@ -45,7 +45,10 @@ class ConnectionDialog(QtWidgets.QDialog): line_edit = QtWidgets.QLineEdit() line_edit.setFixedWidth(200) value = self.values.get('hostname', '') - if value in ['None', None]: value = '' + if value in ['None', None]: + value = '0' + elif type(value) == int: + value = str(value) line_edit.insert(value) grid.addWidget(line_edit, 0, 1) self.fields['hostname'] = line_edit @@ -78,7 +81,10 @@ class ConnectionDialog(QtWidgets.QDialog): line_edit.setFixedWidth(200) line_edit.setEchoMode(QtWidgets.QLineEdit.Password) value = self.values.get('password', '') - if value in ['None', None]: value = '' + if value in ['None', None]: + value = '0' + elif type(value) == int: + value = str(value) line_edit.insert(value) grid.addWidget(line_edit, 2, 1) self.fields['password'] = line_edit diff --git a/toxygen/third_party/qweechat/debug.py b/toxygen/third_party/qweechat/debug.py index e1f09b3..48be08e 100644 --- a/toxygen/third_party/qweechat/debug.py +++ b/toxygen/third_party/qweechat/debug.py @@ -24,8 +24,8 @@ from qtpy import QtWidgets -from third_party.qweechat.chat import ChatTextEdit -from third_party.qweechat.input import InputLineEdit +from qweechat.chat import ChatTextEdit +from qweechat.input import InputLineEdit class DebugDialog(QtWidgets.QDialog): diff --git a/toxygen/third_party/qweechat/input.py b/toxygen/third_party/qweechat/input.py index cc6a0be..98b05bd 100644 --- a/toxygen/third_party/qweechat/input.py +++ b/toxygen/third_party/qweechat/input.py @@ -23,7 +23,6 @@ """Input line for chat and debug window.""" from qtpy import QtCore, QtWidgets -# from PyQt5.QtCore import pyqtSignal as Signal from qtpy.QtCore import Signal class InputLineEdit(QtWidgets.QLineEdit): diff --git a/toxygen/third_party/qweechat/network.py b/toxygen/third_party/qweechat/network.py index e570d9b..0127a98 100644 --- a/toxygen/third_party/qweechat/network.py +++ b/toxygen/third_party/qweechat/network.py @@ -30,8 +30,8 @@ from qtpy import QtCore, QtNetwork # from PyQt5.QtCore import pyqtSignal as Signal from qtpy.QtCore import Signal -from third_party.qweechat import config -from third_party.qweechat.debug import DebugDialog +from qweechat import config +from qweechat.debug import DebugDialog # list of supported hash algorithms on our side @@ -232,7 +232,19 @@ class Network(QtCore.QObject): def is_connected(self): """Return True if the socket is connected, False otherwise.""" - return self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState + return is_state(self, at='ConnectedState') + + def is_state(self, at='ConnectedState'): + """Return True if the socket is connected, False otherwise.""" + if hasattr(QtNetwork.QAbstractSocket, 'ConnectedState'): + if self._socket.state() == getattr(QtNetwork.QAbstractSocket, at): + return True + return False + if hasattr(QtNetwork.QAbstractSocket, 'SocketState'): + if self._socket.state() == getattr(QtNetwork.QAbstractSocket.SocketState, at): + return True + return False + return False def is_ssl(self): """Return True if SSL is used, False otherwise.""" @@ -252,9 +264,10 @@ class Network(QtCore.QObject): self._lines = int(lines) except ValueError: self._lines = config.CONFIG_DEFAULT_RELAY_LINES - if self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState: + # AttributeError: type object 'QAbstractSocket' has no attribute 'ConnectedState' + if self.is_connected(): return - if self._socket.state() != QtNetwork.QAbstractSocket.UnconnectedState: + if not self.is_state('UnconnectedState'): self._socket.abort() if self._ssl: self._socket.ignoreSslErrors() @@ -265,10 +278,10 @@ class Network(QtCore.QObject): def disconnect_weechat(self): """Disconnect from WeeChat.""" - if self._socket.state() == QtNetwork.QAbstractSocket.UnconnectedState: + if self.is_state('UnconnectedState'): self.set_status(STATUS_DISCONNECTED) return - if self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState: + if self.is_state('ConnectedState'): self.send_to_weechat('quit\n') self._socket.waitForBytesWritten(1000) else: diff --git a/toxygen/third_party/qweechat/preferences.py b/toxygen/third_party/qweechat/preferences.py index 64315cf..64a3d79 100644 --- a/toxygen/third_party/qweechat/preferences.py +++ b/toxygen/third_party/qweechat/preferences.py @@ -2,7 +2,7 @@ # # preferences.py - preferences dialog box # -# Copyright (C) 2011-2022 Sébastien Helleu +# Copyright (C) 2016 Ricky Brent # # This file is part of QWeeChat, a Qt remote GUI for WeeChat. # @@ -20,38 +20,538 @@ # along with QWeeChat. If not, see . # -"""Preferences dialog box.""" - -from qtpy import QtCore, QtWidgets as QtGui +import config +import utils +from inputlinespell import InputLineSpell +from qtpy import QtCore, QtGui class PreferencesDialog(QtGui.QDialog): """Preferences dialog.""" - def __init__(self, *args): + custom_sections = { + "look": "Look", + "input": "Input Box", + "nicks": "Nick List", + "buffers": "Buffer List", + "buffer_flags": False, + "notifications": "Notifications", + "color": "Colors", + "relay": "Relay/Connection" + } + + def __init__(self, name, parent, *args): QtGui.QDialog.__init__(*(self,) + args) self.setModal(True) - self.setWindowTitle('Preferences') + self.setWindowTitle(name) + self.parent = parent + self.config = parent.config + self.stacked_panes = QtGui.QStackedWidget() + self.list_panes = PreferencesTreeWidget("Settings") - close_button = QtGui.QPushButton('Close') - close_button.pressed.connect(self.close) + splitter = QtGui.QSplitter() + splitter.addWidget(self.list_panes) + splitter.addWidget(self.stacked_panes) + + # Follow same order as defaults: + section_panes = {} + for section in config.CONFIG_DEFAULT_SECTIONS: + item = QtGui.QTreeWidgetItem(section) + name = section + item.setText(0, section.title()) + if section in self.custom_sections: + if not self.custom_sections[section]: + continue + item.setText(0, self.custom_sections[section]) + section_panes[section] = PreferencesPaneWidget(section, name) + self.list_panes.addTopLevelItem(item) + self.stacked_panes.addWidget(section_panes[section]) + + for setting, default in config.CONFIG_DEFAULT_OPTIONS: + section_key = setting.split(".") + section = section_key[0] + key = ".".join(section_key[1:]) + section_panes[section].addItem( + key, self.config.get(section, key), default) + for key, value in self.config.items("color"): + section_panes["color"].addItem(key, value, False) + notification_field_count = len(section_panes["notifications"].fields) + notification = PreferencesNotificationBlock( + section_panes["notifications"]) + section_panes["notifications"].grid.addLayout( + notification, notification_field_count, 0, 1, -1) + + self.list_panes.currentItemChanged.connect(self._pane_switch) + self.list_panes.setCurrentItem(self.list_panes.topLevelItem(0)) hbox = QtGui.QHBoxLayout() + self.dialog_buttons = QtGui.QDialogButtonBox() + self.dialog_buttons.setStandardButtons( + QtGui.QDialogButtonBox.Save | QtGui.QDialogButtonBox.Cancel) + self.dialog_buttons.rejected.connect(self.close) + self.dialog_buttons.accepted.connect(self._save_and_close) + hbox.addStretch(1) - hbox.addWidget(close_button) + hbox.addWidget(self.dialog_buttons) hbox.addStretch(1) vbox = QtGui.QVBoxLayout() - - label = QtGui.QLabel('Not yet implemented!') - label.setAlignment(QtCore.Qt.AlignHCenter) - vbox.addWidget(label) - - label = QtGui.QLabel('') - label.setAlignment(QtCore.Qt.AlignHCenter) - vbox.addWidget(label) - + vbox.addWidget(splitter) vbox.addLayout(hbox) self.setLayout(vbox) self.show() + + def _pane_switch(self, item): + """Switch the visible preference pane.""" + index = self.list_panes.indexOfTopLevelItem(item) + if index >= 0: + self.stacked_panes.setCurrentIndex(index) + + def _save_and_close(self): + for widget in (self.stacked_panes.widget(i) + for i in range(self.stacked_panes.count())): + for key, field in widget.fields.items(): + if isinstance(field, QtGui.QComboBox): + text = field.itemText(field.currentIndex()) + data = field.itemData(field.currentIndex()) + text = data if data else text + elif isinstance(field, QtGui.QCheckBox): + text = "on" if field.isChecked() else "off" + else: + text = field.text() + self.config.set(widget.section_name, key, str(text)) + config.write(self.config) + self.parent.apply_preferences() + self.close() + + +class PreferencesNotificationBlock(QtGui.QVBoxLayout): + """Display notification settings with drill down to configure.""" + def __init__(self, pane, *args): + QtGui.QVBoxLayout.__init__(*(self,) + args) + self.section = "notifications" + self.config = QtGui.QApplication.instance().config + self.pane = pane + self.stack = QtGui.QStackedWidget() + + self.table = QtGui.QTableWidget() + fg_color = self.table.palette().text().color().name() + self.action_labels = { + "sound": "Play a sound", + "message": "Show a message in a popup", + "file": "Log to a file", + "taskbar": "Mark taskbar entry", + "tray": "Mark systray/indicator", + "command": "Run a command"} + self.action_icons = { + "sound": utils.qicon_from_theme("media-playback-start"), + "message": utils.qicon_from_theme("dialog-information"), + "file": utils.qicon_from_theme("document-export"), + "taskbar": utils.qicon_from_theme("weechat"), + "tray": utils.qicon_tint("ic_hot", fg_color), + "command": utils.qicon_from_theme("system-run")} + self.icon_widget_qss = "padding:0;min-height:10px;min-width:16px;" + self.table.resizeColumnsToContents() + self.table.setColumnCount(2) + self.table.resizeRowsToContents() + self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.table.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) + self.table.setHorizontalHeaderLabels(["State", "Type"]) + self.table.horizontalHeader().setStretchLastSection(True) + self.table.horizontalHeader().setHighlightSections(False) + self.table.verticalHeader().setVisible(False) + self.table.setShowGrid(False) + self.table.itemSelectionChanged.connect(self._table_row_changed) + + self.buftypes = {} + for key, value in config.CONFIG_DEFAULT_NOTIFICATION_OPTIONS: + buftype, optkey = key.split(".") + if buftype not in self.buftypes: + self.buftypes[buftype] = {} + self.buftypes[buftype][optkey] = self.config.get(self.section, key) + for buftype, optkey in self.buftypes.items(): + self._insert_type(buftype) + self.update_icons() + self.resize_table() + self.addWidget(self.table) + self.addWidget(self.stack) + self.table.selectRow(0) + + def _insert_type(self, buftype): + row = self.table.rowCount() + self.table.insertRow(row) + buftype_item = QtGui.QTableWidgetItem(buftype) + buftype_item.setTextAlignment(QtCore.Qt.AlignCenter) + self.table.setItem(row, 0, QtGui.QTableWidgetItem()) + self.table.setItem(row, 1, buftype_item) + subgrid = QtGui.QGridLayout() + subgrid.setColumnStretch(2, 1) + subgrid.setSpacing(10) + + for key, qicon in self.action_icons.items(): + value = self.buftypes[buftype][key] + line = subgrid.rowCount() + label = IconTextLabel(self.action_labels[key], qicon, 16) + + checkbox = QtGui.QCheckBox() + span = 1 + edit = None + if key in ("message", "taskbar", "tray"): + checkbox.setChecked(value == "on") + span = 2 + elif key == "sound": + edit = PreferencesFileEdit( + checkbox=checkbox, caption='Select a sound file', + filter='Audio Files (*.wav *.mp3 *.ogg)') + elif key == "file": + edit = PreferencesFileEdit(checkbox=checkbox, mode="save") + else: + edit = PreferencesFileEdit(checkbox=checkbox) + if edit: + edit.insert(value) + subgrid.addWidget(edit, line, 2) + else: + edit = checkbox + subgrid.addWidget(label, line, 1, 1, span) + subgrid.addWidget(checkbox, line, 0) + self.pane.fields[buftype + "." + key] = edit + subpane = QtGui.QWidget() + subpane.setLayout(subgrid) + subpane.setMaximumHeight(subgrid.totalMinimumSize().height()) + self.stack.addWidget(subpane) + + def resize_table(self): + """Fit the table height to contents.""" + height = self.table.horizontalHeader().height() + height = height * (self.table.rowCount() + 1) + height += self.table.contentsMargins().top() + height += self.table.contentsMargins().bottom() + self.table.setMaximumHeight(height) + self.table.setMinimumHeight(height) + self.table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + def update_icons(self): + """Draw the correct icons in the left col.""" + for i in range(self.table.rowCount()): + hbox = QtGui.QHBoxLayout() + iconset = QtGui.QWidget() + buftype = self.table.item(i, 1).text() + for key, qicon in self.action_icons.items(): + field = self.pane.fields[buftype + "." + key] + if isinstance(field, QtGui.QCheckBox): + val = "on" if field.isChecked() else "off" + else: + val = field.text() + iconbtn = QtGui.QPushButton() + iconbtn.setContentsMargins(0, 0, 0, 0) + iconbtn.setFlat(True) + iconbtn.setFocusPolicy(QtCore.Qt.NoFocus) + if val and val != "off": + iconbtn.setIcon(qicon) + iconbtn.setStyleSheet(self.icon_widget_qss) + iconbtn.setToolTip(key) + iconbtn.clicked.connect(lambda i=i: self.table.selectRow(i)) + hbox.addWidget(iconbtn) + iconset.setLayout(hbox) + self.table.setCellWidget(i, 0, iconset) + + def _table_row_changed(self): + row = self.table.selectionModel().selectedRows()[0].row() + self.stack.setCurrentIndex(row) + + +class PreferencesTreeWidget(QtGui.QTreeWidget): + """Widget with tree list of preferences.""" + def __init__(self, header_label, *args): + QtGui.QTreeWidget.__init__(*(self,) + args) + self.setHeaderLabel(header_label) + self.setRootIsDecorated(False) + self.setMaximumWidth(180) + self.setTextElideMode(QtCore.Qt.ElideNone) + self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.setFocusPolicy(QtCore.Qt.NoFocus) + + +class PreferencesSliderEdit(QtGui.QSlider): + """Percentage slider.""" + def __init__(self, *args): + QtGui.QSlider.__init__(*(self,) + args) + self.setMinimum(0) + self.setMaximum(100) + self.setTickPosition(QtGui.QSlider.TicksBelow) + self.setTickInterval(5) + + def insert(self, percent): + self.setValue(int(percent[:-1])) + + def text(self): + return str(self.value()) + "%" + + +class PreferencesColorEdit(QtGui.QPushButton): + """Simple color square that changes based on the color selected.""" + def __init__(self, *args): + QtGui.QPushButton.__init__(*(self,) + args) + self.color = "#000000" + self.clicked.connect(self._color_picker) + # Some of the configured colors use a astrisk prefix. + # Toggle this on right click. + self.star = False + self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.customContextMenuRequested.connect(self._color_star) + + def insert(self, color): + """Insert the desired color for the widget.""" + if color[:1] == "*": + self.star = True + color = color[1:] + self.setText("*" if self.star else "") + self.color = color + self.setStyleSheet("background-color: " + color) + + def text(self): + """Returns the hex value of the color.""" + return ("*" if self.star else "") + self.color + + def _color_picker(self): + color = QtGui.QColorDialog.getColor(self.color) + self.insert(color.name()) + + def _color_star(self): + self.star = not self.star + self.insert(self.text()) + + +class PreferencesFontEdit(QtGui.QWidget): + """Font entry and selection.""" + def __init__(self, *args): + QtGui.QWidget.__init__(*(self,) + args) + layout = QtGui.QHBoxLayout() + self.checkbox = QtGui.QCheckBox() + self.edit = QtGui.QLineEdit() + self.font = "" + self.qfont = None + self.button = QtGui.QPushButton("C&hoose") + self.button.clicked.connect(self._font_picker) + self.checkbox.toggled.connect( + lambda: self._checkbox_toggled(self.checkbox)) + layout.addWidget(self.checkbox) + layout.addWidget(self.edit) + layout.addWidget(self.button) + layout.setContentsMargins(0, 0, 0, 0) + self.setLayout(layout) + + def insert(self, font_str): + """Insert the font described by the string.""" + self.font = font_str + self.edit.insert(font_str) + if font_str: + self.qfont = utils.Font.str_to_qfont(font_str) + self.edit.setFont(self.qfont) + self.checkbox.setChecked(True) + self._checkbox_toggled(self.checkbox) + else: + self.checkbox.setChecked(False) + self.qfont = None + self._checkbox_toggled(self.checkbox) + + def text(self): + """Returns the human readable font string.""" + return self.font + + def _font_picker(self): + font, ok = QtGui.QFontDialog.getFont(self.qfont) + if ok: + self.insert(utils.Font.qfont_to_str(font)) + + def _checkbox_toggled(self, button): + if button.isChecked() is False and not self.font == "": + self.insert("") + self.edit.setEnabled(button.isChecked()) + self.button.setEnabled(button.isChecked()) + + +class PreferencesFileEdit(QtGui.QWidget): + """File entry and selection.""" + def __init__(self, checkbox=None, caption="Select a file", filter=None, + mode="open", *args): + QtGui.QWidget.__init__(*(self,) + args) + layout = QtGui.QHBoxLayout() + self.caption = caption + self.filter = filter + self.edit = QtGui.QLineEdit() + self.file_str = "" + self.mode = mode + self.button = QtGui.QPushButton("B&rowse") + self.button.clicked.connect(self._file_picker) + if checkbox: + self.checkbox = checkbox + else: + self.checkbox = QtGui.QCheckBox() + layout.addWidget(self.checkbox) + self.checkbox.toggled.connect( + lambda: self._checkbox_toggled(self.checkbox)) + layout.addWidget(self.edit) + layout.addWidget(self.button) + layout.setContentsMargins(0, 0, 0, 0) + self.setLayout(layout) + + def insert(self, file_str): + """Insert the file.""" + self.file_str = file_str + self.edit.insert(file_str) + if file_str: + self.checkbox.setChecked(True) + self._checkbox_toggled(self.checkbox) + else: + self.checkbox.setChecked(False) + self._checkbox_toggled(self.checkbox) + + def text(self): + """Returns the human readable font string.""" + return self.file_str + + def _file_picker(self): + path = "" + if self.mode == "save": + fn = QtGui.QFileDialog.getSaveFileName + else: + fn = QtGui.QFileDialog.getOpenFileName + filename, fil = fn(self, self.caption, path, self.filter, self.filter) + if filename: + self.insert(filename) + + def _checkbox_toggled(self, button): + if button.isChecked() is False and not self.file_str == "": + self.insert("") + self.edit.setEnabled(button.isChecked()) + self.button.setEnabled(button.isChecked()) + + +class PreferencesPaneWidget(QtGui.QWidget): + """ + Widget with (from top to bottom): + title, chat + nicklist (optional) + prompt/input. + """ + + disabled_fields = ["show_hostnames", "hide_nick_changes", + "hide_join_and_part"] + + def __init__(self, section, section_name): + QtGui.QWidget.__init__(self) + self.grid = QtGui.QGridLayout() + self.grid.setAlignment(QtCore.Qt.AlignTop) + self.section = section + self.section_name = section_name + self.fields = {} + self.setLayout(self.grid) + self.grid.setColumnStretch(2, 1) + self.grid.setSpacing(10) + self.int_validator = QtGui.QIntValidator(0, 2147483647, self) + toolbar_icons = [ + ('ToolButtonFollowStyle', 'Default'), + ('ToolButtonIconOnly', 'Icon Only'), + ('ToolButtonTextOnly', 'Text Only'), + ('ToolButtonTextBesideIcon', 'Text Alongside Icons'), + ('ToolButtonTextUnderIcon', 'Text Under Icons')] + tray_options = [ + ('always', 'Always'), + ('unread', 'On Unread Messages'), + ('never', 'Never'), + ] + list_positions = [ + ('left', 'Left'), + ('right', 'Right'), + ] + sort_options = ['A-Z Ranked', 'A-Z', 'Z-A Ranked', 'Z-A'] + spellcheck_langs = [(x, x) for x in + InputLineSpell.list_languages()] + spellcheck_langs.insert(0, ('', '')) + focus_opts = ["requested", "always", "never"] + self.comboboxes = {"style": QtGui.QStyleFactory.keys(), + "position": list_positions, + "toolbar_icons": toolbar_icons, + "focus_new_tabs": focus_opts, + "tray_icon": tray_options, + "sort": sort_options, + "spellcheck_dictionary": spellcheck_langs} + + def addItem(self, key, value, default): + """Add a key-value pair.""" + line = len(self.fields) + name = key.split(".")[-1:][0].capitalize().replace("_", " ") + label = QtGui.QLabel(name) + start = 0 + + if self.section == "color": + start = 2 * (line % 2) + line = line // 2 + edit = PreferencesColorEdit() + edit.setFixedWidth(edit.sizeHint().height()) + edit.insert(value) + label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + elif key == "custom_stylesheet": + edit = PreferencesFileEdit(caption='Select QStyleSheet File', + filter='*.qss') + edit.insert(value) + elif name.lower()[-5:] == "sound": + edit = PreferencesFileEdit( + caption='Select a sound file', + filter='Audio Files (*.wav *.mp3 *.ogg)') + edit.insert(value) + elif name.lower()[-4:] == "font": + edit = PreferencesFontEdit() + edit.setFixedWidth(200) + edit.insert(value) + elif key in self.comboboxes.keys(): + edit = QtGui.QComboBox() + if len(self.comboboxes[key][0]) == 2: + for keyvalue in self.comboboxes[key]: + edit.addItem(keyvalue[1], keyvalue[0]) + # if self.section == "nicks" and key == "position": + # edit.addItem("below", "Below Buffer List") + # edit.addItem("above", "Above Buffer List") + edit.setCurrentIndex(edit.findData(value)) + else: + edit.addItems(self.comboboxes[key]) + edit.setCurrentIndex(edit.findText(value)) + edit.setFixedWidth(200) + elif default in ["on", "off"]: + edit = QtGui.QCheckBox() + edit.setChecked(value == "on") + elif default[-1:] == "%": + edit = PreferencesSliderEdit(QtCore.Qt.Horizontal) + edit.setFixedWidth(200) + edit.insert(value) + else: + edit = QtGui.QLineEdit() + edit.setFixedWidth(200) + edit.insert(value) + if default.isdigit() or key == "port": + edit.setValidator(self.int_validator) + if key == 'password': + edit.setEchoMode(QtGui.QLineEdit.Password) + if key in self.disabled_fields: + edit.setDisabled(True) + self.grid.addWidget(label, line, start + 0) + self.grid.addWidget(edit, line, start + 1) + + self.fields[key] = edit + + +class IconTextLabel(QtGui.QWidget): + """An icon next to text.""" + def __init__(self, text=None, icon=None, extent=None): + QtGui.QWidget.__init__(self) + text_label = QtGui.QLabel(text) + if not extent: + extent = text_label.height() + icon_label = QtGui.QLabel() + pixmap = icon.pixmap(extent, QtGui.QIcon.Normal, QtGui.QIcon.On) + icon_label.setPixmap(pixmap) + label_layout = QtGui.QHBoxLayout() + label_layout.addWidget(icon_label) + label_layout.addWidget(text_label) + label_layout.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) + self.setLayout(label_layout) diff --git a/toxygen/third_party/qweechat/preferences.py.bak b/toxygen/third_party/qweechat/preferences.py.bak new file mode 100644 index 0000000..c7dcd7c --- /dev/null +++ b/toxygen/third_party/qweechat/preferences.py.bak @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# +# preferences.py - preferences dialog box +# +# Copyright (C) 2011-2022 Sébastien Helleu +# +# This file is part of QWeeChat, a Qt remote GUI for WeeChat. +# +# QWeeChat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# QWeeChat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with QWeeChat. If not, see . +# + +"""Preferences dialog box.""" + +from qtpy import QtCore, QtGui + + +class PreferencesDialog(QtGui.QDialog): + """Preferences dialog.""" + + def __init__(self, *args): + QtGui.QDialog.__init__(*(self,) + args) + self.setModal(True) + self.setWindowTitle('Preferences') + + close_button = QtGui.QPushButton('Close') + close_button.pressed.connect(self.close) + + hbox = QtGui.QHBoxLayout() + hbox.addStretch(1) + hbox.addWidget(close_button) + hbox.addStretch(1) + + vbox = QtGui.QVBoxLayout() + + label = QtGui.QLabel('Not yet implemented!') + label.setAlignment(QtCore.Qt.AlignHCenter) + vbox.addWidget(label) + + label = QtGui.QLabel('') + label.setAlignment(QtCore.Qt.AlignHCenter) + vbox.addWidget(label) + + vbox.addLayout(hbox) + + self.setLayout(vbox) + self.show() diff --git a/toxygen/third_party/qweechat/qweechat.py b/toxygen/third_party/qweechat/qweechat.py index 03e1b5e..ed6187a 100644 --- a/toxygen/third_party/qweechat/qweechat.py +++ b/toxygen/third_party/qweechat/qweechat.py @@ -39,13 +39,13 @@ from pkg_resources import resource_filename from qtpy import QtCore, QtGui, QtWidgets -from third_party.qweechat import config -from third_party.qweechat.about import AboutDialog -from third_party.qweechat.buffer import BufferListWidget, Buffer -from third_party.qweechat.connection import ConnectionDialog -from third_party.qweechat.network import Network, STATUS_DISCONNECTED -from third_party.qweechat.preferences import PreferencesDialog -from third_party.qweechat.weechat import protocol +from qweechat import config +from qweechat.about import AboutDialog +from qweechat.buffer import BufferListWidget, Buffer +from qweechat.connection import ConnectionDialog +from qweechat.network import Network, STATUS_DISCONNECTED +from qweechat.preferences import PreferencesDialog +from qweechat.weechat import protocol APP_NAME = 'QWeeChat' @@ -163,13 +163,6 @@ class MainWindow(QtWidgets.QMainWindow): self.actions['quit']]) menu_window = self.menu.addMenu('&Window') menu_window.addAction(self.actions['debug']) - name = 'toggle' - menu_window.addAction( - QtWidgets.QAction(QtGui.QIcon( - resource_filename(__name__, 'data/icons/%s' % 'weechat.png')), - name.capitalize(), self)) - #? .triggered.connect(self.onMyToolBarButtonClick) - menu_help = self.menu.addMenu('&Help') menu_help.addAction(self.actions['about']) self.network_status = QtWidgets.QLabel() @@ -177,21 +170,23 @@ class MainWindow(QtWidgets.QMainWindow): self.network_status.setFixedWidth(200) self.network_status.setContentsMargins(0, 0, 10, 0) self.network_status.setAlignment(QtCore.Qt.AlignRight) - if hasattr(self.menu, 'setCornerWidget'): - self.menu.setCornerWidget(self.network_status, - QtCore.Qt.TopRightCorner) + if hasattr(self, 'menuBar'): + if hasattr(self.menu, 'setCornerWidget'): + self.menu.setCornerWidget(self.network_status, + QtCore.Qt.TopRightCorner) self.network_status_set(STATUS_DISCONNECTED) # toolbar - toolbar = self.addToolBar('toolBar') - toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon) - toolbar.addActions([self.actions['connect'], - self.actions['disconnect'], - self.actions['debug'], - self.actions['preferences'], - self.actions['about'], - self.actions['quit']]) - self.toolbar = toolbar + if hasattr(self, 'addToolBar'): + toolbar = self.addToolBar('toolBar') + toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon) + toolbar.addActions([self.actions['connect'], + self.actions['disconnect'], + self.actions['debug'], + self.actions['preferences'], + self.actions['about'], + self.actions['quit']]) + self.buffers[0].widget.input.setFocus() # open debug dialog @@ -203,9 +198,9 @@ class MainWindow(QtWidgets.QMainWindow): self.network.connect_weechat( hostname=self.config.get('relay', 'hostname', fallback='127.0.0.1'), port=self.config.get('relay', 'port', fallback='9000'), - ssl=self.config.getboolean('relay', 'ssl', fallback=False), + ssl=self.config.getboolean('relay', 'ssl', fallback=''), password=self.config.get('relay', 'password', fallback=''), - totp=self.config.get('relay', 'password', fallback=''), + totp=None, lines=self.config.get('relay', 'lines', fallback=''), ) @@ -244,10 +239,7 @@ class MainWindow(QtWidgets.QMainWindow): """Open a dialog with connection settings.""" values = {} for option in ('hostname', 'port', 'ssl', 'password', 'lines'): - val = self.config.get('relay', option, fallback='') - if val in [None, 'None']: val = '' - if option == 'port' and val in [None, 'None']: val = 0 - values[option] = val + values[option] = self.config.get('relay', option, fallback='') self.connection_dialog = ConnectionDialog(values, self) self.connection_dialog.dialog_buttons.accepted.connect( self.connect_weechat) @@ -262,13 +254,6 @@ class MainWindow(QtWidgets.QMainWindow): totp=self.connection_dialog.fields['totp'].text(), lines=int(self.connection_dialog.fields['lines'].text()), ) - hostname=self.connection_dialog.fields['hostname'].text() - port = self.connection_dialog.fields['port'].text() - ssl=self.connection_dialog.fields['ssl'].isChecked() - password = '' # self.connection_dialog.fields['password'].text() - self.config.set('relay', 'port', port) - self.config.set('relay', 'hostname', hostname) - self.config.set('relay', 'password', password) self.connection_dialog.close() def _network_status_changed(self, status, extra): @@ -536,10 +521,10 @@ class MainWindow(QtWidgets.QMainWindow): if next_buffer == '0x0': index = len(self.buffers) else: - elts = [i for i, b in enumerate(self.buffers) + index = [i for i, b in enumerate(self.buffers) if b.pointer() == next_buffer] - if len(elts): - index = elts[0] + if index: + index = index[0] if index < 0: print('Warning: unable to find position for buffer, using end of ' 'list by default') diff --git a/toxygen/third_party/qweechat/qweechat.py.bak b/toxygen/third_party/qweechat/qweechat.py.bak new file mode 100644 index 0000000..465363d --- /dev/null +++ b/toxygen/third_party/qweechat/qweechat.py.bak @@ -0,0 +1,569 @@ +# -*- coding: utf-8 -*- +# +# qweechat.py - WeeChat remote GUI using Qt toolkit +# +# Copyright (C) 2011-2022 Sébastien Helleu +# +# This file is part of QWeeChat, a Qt remote GUI for WeeChat. +# +# QWeeChat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# QWeeChat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with QWeeChat. If not, see . +# + +""" +QWeeChat is a WeeChat remote GUI using Qt toolkit. + +It requires requires WeeChat 0.3.7 or newer, running on local or remote host. +""" + +# +# History: +# +# 2011-05-27, Sébastien Helleu : +# start dev +# + +import sys +import traceback +from pkg_resources import resource_filename + +from qtpy import QtCore, QtGui, QtWidgets + +from qweechat import config +from qweechat.about import AboutDialog +from qweechat.buffer import BufferListWidget, Buffer +from qweechat.connection import ConnectionDialog +from qweechat.network import Network, STATUS_DISCONNECTED +from qweechat.preferences import PreferencesDialog +from qweechat.weechat import protocol + + +APP_NAME = 'QWeeChat' +AUTHOR = 'Sébastien Helleu' +WEECHAT_SITE = 'https://weechat.org/' + +# not QFrame +class MainWindow(QtWidgets.QMainWindow): + """Main window.""" + + def __init__(self, *args): + super().__init__(*args) + + self.config = config.read() + + self.resize(1000, 600) + self.setWindowTitle(APP_NAME) + + self.about_dialog = None + self.connection_dialog = None + self.preferences_dialog = None + + # network + self.network = Network() + self.network.statusChanged.connect(self._network_status_changed) + self.network.messageFromWeechat.connect(self._network_weechat_msg) + + # list of buffers + self.list_buffers = BufferListWidget() + self.list_buffers.currentRowChanged.connect(self._buffer_switch) + + # default buffer + self.buffers = [Buffer()] + self.stacked_buffers = QtWidgets.QStackedWidget() + self.stacked_buffers.addWidget(self.buffers[0].widget) + + # splitter with buffers + chat/input + splitter = QtWidgets.QSplitter() + splitter.addWidget(self.list_buffers) + splitter.addWidget(self.stacked_buffers) + + self.list_buffers.setSizePolicy(QtWidgets.QSizePolicy.Preferred, + QtWidgets.QSizePolicy.Preferred) + self.stacked_buffers.setSizePolicy(QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Expanding) + # MainWindow + self.setCentralWidget(splitter) + + if self.config.getboolean('look', 'statusbar'): + self.statusBar().visible = True + self.statusBar().visible = True + + # actions for menu and toolbar + actions_def = { + 'connect': [ + 'network-connect.png', + 'Connect to WeeChat', + 'Ctrl+O', + self.open_connection_dialog, + ], + 'disconnect': [ + 'network-disconnect.png', + 'Disconnect from WeeChat', + 'Ctrl+D', + self.network.disconnect_weechat, + ], + 'debug': [ + 'edit-find.png', + 'Open debug console window', + 'Ctrl+B', + self.network.open_debug_dialog, + ], + 'preferences': [ + 'preferences-other.png', + 'Change preferences', + 'Ctrl+P', + self.open_preferences_dialog, + ], + 'about': [ + 'help-about.png', + 'About QWeeChat', + 'Ctrl+H', + self.open_about_dialog, + ], + 'save connection': [ + 'document-save.png', + 'Save connection configuration', + 'Ctrl+S', + self.save_connection, + ], + 'quit': [ + 'application-exit.png', + 'Quit application', + 'Ctrl+Q', + self.close, + ], + } + self.actions = {} + for name, action in list(actions_def.items()): + self.actions[name] = QtWidgets.QAction( + QtGui.QIcon( + resource_filename(__name__, 'data/icons/%s' % action[0])), + name.capitalize(), self) + self.actions[name].setToolTip(f'{action[1]} ({action[2]})') + self.actions[name].setShortcut(action[2]) + self.actions[name].triggered.connect(action[3]) + + # menu + self.menu = self.menuBar() + menu_file = self.menu.addMenu('&File') + menu_file.addActions([self.actions['connect'], + self.actions['disconnect'], + self.actions['preferences'], + self.actions['save connection'], + self.actions['quit']]) + menu_window = self.menu.addMenu('&Window') + menu_window.addAction(self.actions['debug']) + name = 'toggle' + menu_window.addAction( + QtWidgets.QAction(QtGui.QIcon( + resource_filename(__name__, 'data/icons/%s' % 'weechat.png')), + name.capitalize(), self)) + #? .triggered.connect(self.onMyToolBarButtonClick) + + menu_help = self.menu.addMenu('&Help') + menu_help.addAction(self.actions['about']) + self.network_status = QtWidgets.QLabel() + self.network_status.setFixedHeight(20) + self.network_status.setFixedWidth(200) + self.network_status.setContentsMargins(0, 0, 10, 0) + self.network_status.setAlignment(QtCore.Qt.AlignRight) + if hasattr(self.menu, 'setCornerWidget'): + self.menu.setCornerWidget(self.network_status, + QtCore.Qt.TopRightCorner) + self.network_status_set(STATUS_DISCONNECTED) + + # toolbar + toolbar = self.addToolBar('toolBar') + toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon) + toolbar.addActions([self.actions['connect'], + self.actions['disconnect'], + self.actions['debug'], + self.actions['preferences'], + self.actions['about'], + self.actions['quit']]) + self.toolbar = toolbar + self.buffers[0].widget.input.setFocus() + + # open debug dialog + if self.config.getboolean('look', 'debug'): + self.network.open_debug_dialog() + + # auto-connect to relay + if self.config.getboolean('relay', 'autoconnect'): + self.network.connect_weechat( + hostname=self.config.get('relay', 'hostname', fallback='127.0.0.1'), + port=self.config.get('relay', 'port', fallback='9000'), + ssl=self.config.getboolean('relay', 'ssl', fallback=False), + password=self.config.get('relay', 'password', fallback=''), + totp=self.config.get('relay', 'password', fallback=''), + lines=self.config.get('relay', 'lines', fallback=''), + ) + + self.show() + + def _buffer_switch(self, index): + """Switch to a buffer.""" + if index >= 0: + self.stacked_buffers.setCurrentIndex(index) + self.stacked_buffers.widget(index).input.setFocus() + + def buffer_input(self, full_name, text): + """Send buffer input to WeeChat.""" + if self.network.is_connected(): + message = 'input %s %s\n' % (full_name, text) + self.network.send_to_weechat(message) + self.network.debug_print(0, '<==', message, forcecolor='#AA0000') + + def open_preferences_dialog(self): + """Open a dialog with preferences.""" + # TODO: implement the preferences dialog box + self.preferences_dialog = PreferencesDialog(self) + + def save_connection(self): + """Save connection configuration.""" + if self.network: + options = self.network.get_options() + for option in options: + self.config.set('relay', option, options[option]) + + def open_about_dialog(self): + """Open a dialog with info about QWeeChat.""" + self.about_dialog = AboutDialog(APP_NAME, AUTHOR, WEECHAT_SITE, self) + + def open_connection_dialog(self): + """Open a dialog with connection settings.""" + values = {} + for option in ('hostname', 'port', 'ssl', 'password', 'lines'): + val = self.config.get('relay', option, fallback='') + if val in [None, 'None']: val = '' + if option == 'port' and val in [None, 'None']: val = 0 + values[option] = val + self.connection_dialog = ConnectionDialog(values, self) + self.connection_dialog.dialog_buttons.accepted.connect( + self.connect_weechat) + + def connect_weechat(self): + """Connect to WeeChat.""" + self.network.connect_weechat( + hostname=self.connection_dialog.fields['hostname'].text(), + port=self.connection_dialog.fields['port'].text(), + ssl=self.connection_dialog.fields['ssl'].isChecked(), + password=self.connection_dialog.fields['password'].text(), + totp=self.connection_dialog.fields['totp'].text(), + lines=int(self.connection_dialog.fields['lines'].text()), + ) + hostname=self.connection_dialog.fields['hostname'].text() + port = self.connection_dialog.fields['port'].text() + ssl=self.connection_dialog.fields['ssl'].isChecked() + password = '' # self.connection_dialog.fields['password'].text() + self.config.set('relay', 'port', port) + self.config.set('relay', 'hostname', hostname) + self.config.set('relay', 'password', password) + self.connection_dialog.close() + + def _network_status_changed(self, status, extra): + """Called when the network status has changed.""" + if self.config.getboolean('look', 'statusbar'): + self.statusBar().showMessage(status) + self.network.debug_print(0, '', status, forcecolor='#0000AA') + self.network_status_set(status) + + def network_status_set(self, status): + """Set the network status.""" + pal = self.network_status.palette() + try: + pal.setColor(self.network_status.foregroundRole(), + self.network.status_color(status)) + except: + # dunno + pass + ssl = ' (SSL)' if status != STATUS_DISCONNECTED \ + and self.network.is_ssl() else '' + self.network_status.setPalette(pal) + icon = self.network.status_icon(status) + if icon: + self.network_status.setText( + ' %s' % + (resource_filename(__name__, 'data/icons/%s' % icon), + self.network.status_label(status) + ssl)) + else: + self.network_status.setText(status.capitalize()) + if status == STATUS_DISCONNECTED: + self.actions['connect'].setEnabled(True) + self.actions['disconnect'].setEnabled(False) + else: + self.actions['connect'].setEnabled(False) + self.actions['disconnect'].setEnabled(True) + + def _network_weechat_msg(self, message): + """Called when a message is received from WeeChat.""" + self.network.debug_print( + 0, '==>', + 'message (%d bytes):\n%s' + % (len(message), + protocol.hex_and_ascii(message.data(), 20)), + forcecolor='#008800', + ) + try: + proto = protocol.Protocol() + message = proto.decode(message.data()) + if message.uncompressed: + self.network.debug_print( + 0, '==>', + 'message uncompressed (%d bytes):\n%s' + % (message.size_uncompressed, + protocol.hex_and_ascii(message.uncompressed, 20)), + forcecolor='#008800') + self.network.debug_print(0, '', 'Message: %s' % message) + self.parse_message(message) + except Exception: # noqa: E722 + print('Error while decoding message from WeeChat:\n%s' + % traceback.format_exc()) + self.network.disconnect_weechat() + + def _parse_handshake(self, message): + """Parse a WeeChat message with handshake response.""" + for obj in message.objects: + if obj.objtype != 'htb': + continue + self.network.init_with_handshake(obj.value) + break + + def _parse_listbuffers(self, message): + """Parse a WeeChat message with list of buffers.""" + for obj in message.objects: + if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer': + continue + self.list_buffers.clear() + while self.stacked_buffers.count() > 0: + buf = self.stacked_buffers.widget(0) + self.stacked_buffers.removeWidget(buf) + self.buffers = [] + for item in obj.value['items']: + buf = self.create_buffer(item) + self.insert_buffer(len(self.buffers), buf) + self.list_buffers.setCurrentRow(0) + self.buffers[0].widget.input.setFocus() + + def _parse_line(self, message): + """Parse a WeeChat message with a buffer line.""" + for obj in message.objects: + lines = [] + if obj.objtype != 'hda' or obj.value['path'][-1] != 'line_data': + continue + for item in obj.value['items']: + if message.msgid == 'listlines': + ptrbuf = item['__path'][0] + else: + ptrbuf = item['buffer'] + index = [i for i, b in enumerate(self.buffers) + if b.pointer() == ptrbuf] + if index: + lines.append( + (index[0], + (item['date'], item['prefix'], + item['message'])) + ) + if message.msgid == 'listlines': + lines.reverse() + for line in lines: + self.buffers[line[0]].widget.chat.display(*line[1]) + + def _parse_nicklist(self, message): + """Parse a WeeChat message with a buffer nicklist.""" + buffer_refresh = {} + for obj in message.objects: + if obj.objtype != 'hda' or \ + obj.value['path'][-1] != 'nicklist_item': + continue + group = '__root' + for item in obj.value['items']: + index = [i for i, b in enumerate(self.buffers) + if b.pointer() == item['__path'][0]] + if index: + if not index[0] in buffer_refresh: + self.buffers[index[0]].nicklist = {} + buffer_refresh[index[0]] = True + if item['group']: + group = item['name'] + self.buffers[index[0]].nicklist_add_item( + group, item['group'], item['prefix'], item['name'], + item['visible']) + for index in buffer_refresh: + self.buffers[index].nicklist_refresh() + + def _parse_nicklist_diff(self, message): + """Parse a WeeChat message with a buffer nicklist diff.""" + buffer_refresh = {} + for obj in message.objects: + if obj.objtype != 'hda' or \ + obj.value['path'][-1] != 'nicklist_item': + continue + group = '__root' + for item in obj.value['items']: + index = [i for i, b in enumerate(self.buffers) + if b.pointer() == item['__path'][0]] + if not index: + continue + buffer_refresh[index[0]] = True + if item['_diff'] == ord('^'): + group = item['name'] + elif item['_diff'] == ord('+'): + self.buffers[index[0]].nicklist_add_item( + group, item['group'], item['prefix'], item['name'], + item['visible']) + elif item['_diff'] == ord('-'): + self.buffers[index[0]].nicklist_remove_item( + group, item['group'], item['name']) + elif item['_diff'] == ord('*'): + self.buffers[index[0]].nicklist_update_item( + group, item['group'], item['prefix'], item['name'], + item['visible']) + for index in buffer_refresh: + self.buffers[index].nicklist_refresh() + + def _parse_buffer_opened(self, message): + """Parse a WeeChat message with a new buffer (opened).""" + for obj in message.objects: + if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer': + continue + for item in obj.value['items']: + buf = self.create_buffer(item) + index = self.find_buffer_index_for_insert(item['next_buffer']) + self.insert_buffer(index, buf) + + def _parse_buffer(self, message): + """Parse a WeeChat message with a buffer event + (anything except a new buffer). + """ + for obj in message.objects: + if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer': + continue + for item in obj.value['items']: + index = [i for i, b in enumerate(self.buffers) + if b.pointer() == item['__path'][0]] + if not index: + continue + index = index[0] + if message.msgid == '_buffer_type_changed': + self.buffers[index].data['type'] = item['type'] + elif message.msgid in ('_buffer_moved', '_buffer_merged', + '_buffer_unmerged'): + buf = self.buffers[index] + buf.data['number'] = item['number'] + self.remove_buffer(index) + index2 = self.find_buffer_index_for_insert( + item['next_buffer']) + self.insert_buffer(index2, buf) + elif message.msgid == '_buffer_renamed': + self.buffers[index].data['full_name'] = item['full_name'] + self.buffers[index].data['short_name'] = item['short_name'] + elif message.msgid == '_buffer_title_changed': + self.buffers[index].data['title'] = item['title'] + self.buffers[index].update_title() + elif message.msgid == '_buffer_cleared': + self.buffers[index].widget.chat.clear() + elif message.msgid.startswith('_buffer_localvar_'): + self.buffers[index].data['local_variables'] = \ + item['local_variables'] + self.buffers[index].update_prompt() + elif message.msgid == '_buffer_closing': + self.remove_buffer(index) + + def parse_message(self, message): + """Parse a WeeChat message.""" + if message.msgid.startswith('debug'): + self.network.debug_print(0, '', '(debug message, ignored)') + elif message.msgid == 'handshake': + self._parse_handshake(message) + elif message.msgid == 'listbuffers': + self._parse_listbuffers(message) + elif message.msgid in ('listlines', '_buffer_line_added'): + self._parse_line(message) + elif message.msgid in ('_nicklist', 'nicklist'): + self._parse_nicklist(message) + elif message.msgid == '_nicklist_diff': + self._parse_nicklist_diff(message) + elif message.msgid == '_buffer_opened': + self._parse_buffer_opened(message) + elif message.msgid.startswith('_buffer_'): + self._parse_buffer(message) + elif message.msgid == '_upgrade': + self.network.desync_weechat() + elif message.msgid == '_upgrade_ended': + self.network.sync_weechat() + else: + print(f"Unknown message with id {message.msgid}") + + def create_buffer(self, item): + """Create a new buffer.""" + buf = Buffer(item) + buf.bufferInput.connect(self.buffer_input) + buf.widget.input.bufferSwitchPrev.connect( + self.list_buffers.switch_prev_buffer) + buf.widget.input.bufferSwitchNext.connect( + self.list_buffers.switch_next_buffer) + return buf + + def insert_buffer(self, index, buf): + """Insert a buffer in list.""" + self.buffers.insert(index, buf) + self.list_buffers.insertItem(index, '%s' + % (buf.data['local_variables']['name'])) + self.stacked_buffers.insertWidget(index, buf.widget) + + def remove_buffer(self, index): + """Remove a buffer.""" + if self.list_buffers.currentRow == index and index > 0: + self.list_buffers.setCurrentRow(index - 1) + self.list_buffers.takeItem(index) + self.stacked_buffers.removeWidget(self.stacked_buffers.widget(index)) + self.buffers.pop(index) + + def find_buffer_index_for_insert(self, next_buffer): + """Find position to insert a buffer in list.""" + index = -1 + if next_buffer == '0x0': + index = len(self.buffers) + else: + elts = [i for i, b in enumerate(self.buffers) + if b.pointer() == next_buffer] + if len(elts): + index = elts[0] + if index < 0: + print('Warning: unable to find position for buffer, using end of ' + 'list by default') + index = len(self.buffers) + return index + + def closeEvent(self, event): + """Called when QWeeChat window is closed.""" + self.network.disconnect_weechat() + if self.network.debug_dialog: + self.network.debug_dialog.close() + config.write(self.config) + QtWidgets.QFrame.closeEvent(self, event) + + +def main(): + app = QtWidgets.QApplication(sys.argv) + app.setStyle(QtWidgets.QStyleFactory.create('Cleanlooks')) + app.setWindowIcon(QtGui.QIcon( + resource_filename(__name__, 'data/icons/weechat.png'))) + main_win = MainWindow() + main_win.show() + sys.exit(app.exec_()) + + +if __name__ == '__main__': + main()