From 77df9d06f7d44caed27e29222413548358842772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Thu, 8 May 2014 17:40:31 +0200 Subject: [PATCH] Code refactoring, fix setup.py All changes: - full PEP8 compliance - move sources from src/qweechat/ to qweechat/ - move data from data/icons/ to qweechat/data/icons/ - sources validated with PEP8 - use setuptools in setup.py, fix path of data files --- .gitignore | 5 +- README.asciidoc | 29 +- {src/qweechat => qweechat}/__init__.py | 0 {src/qweechat => qweechat}/about.py | 0 {src/qweechat => qweechat}/buffer.py | 42 +- {src/qweechat => qweechat}/chat.py | 41 +- qweechat/config.py | 133 +++++ {src/qweechat => qweechat}/connection.py | 3 +- {data => qweechat/data}/icons/README | 0 .../data}/icons/application-exit.png | Bin .../data}/icons/bullet_green_8x8.png | Bin .../data}/icons/bullet_yellow_8x8.png | Bin .../data}/icons/dialog-close.png | Bin .../data}/icons/dialog-ok-apply.png | Bin .../data}/icons/document-save.png | Bin {data => qweechat/data}/icons/edit-find.png | Bin {data => qweechat/data}/icons/help-about.png | Bin .../data}/icons/network-connect.png | Bin .../data}/icons/network-disconnect.png | Bin .../data}/icons/preferences-other.png | Bin .../data}/icons/weechat_icon_32.png | Bin {src/qweechat => qweechat}/debug.py | 0 {src/qweechat => qweechat}/input.py | 38 +- {src/qweechat => qweechat}/network.py | 40 +- {src/qweechat => qweechat}/qt_compat.py | 3 +- qweechat/qweechat.py | 545 ++++++++++++++++++ .../qweechat => qweechat}/weechat/__init__.py | 0 {src/qweechat => qweechat}/weechat/color.py | 79 ++- .../qweechat => qweechat}/weechat/protocol.py | 54 +- .../weechat/testproto.py | 54 +- setup.py | 66 ++- src/qweechat/config.py | 130 ----- src/qweechat/qweechat.py | 419 -------------- 33 files changed, 953 insertions(+), 728 deletions(-) rename {src/qweechat => qweechat}/__init__.py (100%) rename {src/qweechat => qweechat}/about.py (100%) rename {src/qweechat => qweechat}/buffer.py (84%) rename {src/qweechat => qweechat}/chat.py (79%) create mode 100644 qweechat/config.py rename {src/qweechat => qweechat}/connection.py (95%) rename {data => qweechat/data}/icons/README (100%) rename {data => qweechat/data}/icons/application-exit.png (100%) rename {data => qweechat/data}/icons/bullet_green_8x8.png (100%) rename {data => qweechat/data}/icons/bullet_yellow_8x8.png (100%) rename {data => qweechat/data}/icons/dialog-close.png (100%) rename {data => qweechat/data}/icons/dialog-ok-apply.png (100%) rename {data => qweechat/data}/icons/document-save.png (100%) rename {data => qweechat/data}/icons/edit-find.png (100%) rename {data => qweechat/data}/icons/help-about.png (100%) rename {data => qweechat/data}/icons/network-connect.png (100%) rename {data => qweechat/data}/icons/network-disconnect.png (100%) rename {data => qweechat/data}/icons/preferences-other.png (100%) rename {data => qweechat/data}/icons/weechat_icon_32.png (100%) rename {src/qweechat => qweechat}/debug.py (100%) rename {src/qweechat => qweechat}/input.py (71%) rename {src/qweechat => qweechat}/network.py (85%) rename {src/qweechat => qweechat}/qt_compat.py (92%) create mode 100644 qweechat/qweechat.py rename {src/qweechat => qweechat}/weechat/__init__.py (100%) rename {src/qweechat => qweechat}/weechat/color.py (64%) rename {src/qweechat => qweechat}/weechat/protocol.py (85%) rename {src/qweechat => qweechat}/weechat/testproto.py (87%) mode change 100755 => 100644 mode change 100755 => 100644 setup.py delete mode 100644 src/qweechat/config.py delete mode 100755 src/qweechat/qweechat.py diff --git a/.gitignore b/.gitignore index 8183689..54e022d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Ignored files for Git +*.pyc +*.pyo MANIFEST build/* dist/* -*.pyc +qweechat.egg-info/* +src/qweechat.egg-info/* diff --git a/README.asciidoc b/README.asciidoc index 0ef63da..7999233 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -27,35 +27,10 @@ Following packages are *required*: * Python 2.x >= 2.6 * PySide (recommended, packages: python.pyside.*) or PyQt4 (python-qt4) -=== Run without install - -Extract files from archive and run qweechat.py: +=== Install via source distribution ---- -$ tar xvzf qweechat-x.y.tar.gz -$ cd qweechat-x.y -$ python src/qweechat/qweechat.py ----- - -=== Run with install - -Extract files from archive and install using script 'setup.py': - ----- -$ tar xvzf qweechat-x.y.tar.gz -$ cd qweechat-x.y ----- - -To install in your home: - ----- -$ python setup.py install --home=~/qweechat ----- - -To install in system directories (as root): - ----- -# python setup.py install +$ python setup.py install ---- == WeeChat setup diff --git a/src/qweechat/__init__.py b/qweechat/__init__.py similarity index 100% rename from src/qweechat/__init__.py rename to qweechat/__init__.py diff --git a/src/qweechat/about.py b/qweechat/about.py similarity index 100% rename from src/qweechat/about.py rename to qweechat/about.py diff --git a/src/qweechat/buffer.py b/qweechat/buffer.py similarity index 84% rename from src/qweechat/buffer.py rename to qweechat/buffer.py index 382e3f6..cfe51fc 100644 --- a/src/qweechat/buffer.py +++ b/qweechat/buffer.py @@ -21,6 +21,7 @@ # along with QWeeChat. If not, see . # +from pkg_resources import resource_filename import qt_compat QtCore = qt_compat.import_module('QtCore') QtGui = qt_compat.import_module('QtGui') @@ -85,7 +86,10 @@ class BufferListWidget(GenericListWidget): class BufferWidget(QtGui.QWidget): - """Widget with (from top to bottom): title, chat + nicklist (optional) + prompt/input.""" + """ + Widget with (from top to bottom): + title, chat + nicklist (optional) + prompt/input. + """ def __init__(self, display_nicklist=False): QtGui.QWidget.__init__(self) @@ -96,7 +100,8 @@ class BufferWidget(QtGui.QWidget): # splitter with chat + nicklist self.chat_nicklist = QtGui.QSplitter() - self.chat_nicklist.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + self.chat_nicklist.setSizePolicy(QtGui.QSizePolicy.Expanding, + QtGui.QSizePolicy.Expanding) self.chat = ChatTextEdit(debug=False) self.chat_nicklist.addWidget(self.chat) self.nicklist = GenericListWidget() @@ -148,7 +153,8 @@ class Buffer(QtCore.QObject): QtCore.QObject.__init__(self) self.data = data self.nicklist = {} - self.widget = BufferWidget(display_nicklist=self.data.get('nicklist', 0)) + self.widget = BufferWidget(display_nicklist=self.data.get('nicklist', + 0)) self.update_title() self.update_prompt() self.widget.input.textSent.connect(self.input_text_sent) @@ -160,7 +166,8 @@ class Buffer(QtCore.QObject): def update_title(self): """Update title.""" try: - self.widget.set_title(color.remove(self.data['title'].decode('utf-8'))) + self.widget.set_title( + color.remove(self.data['title'].decode('utf-8'))) except: self.widget.set_title(None) @@ -179,12 +186,14 @@ class Buffer(QtCore.QObject): def nicklist_add_item(self, parent, group, prefix, name, visible): """Add a group/nick in nicklist.""" if group: - self.nicklist[name] = { 'visible': visible, - 'nicks': [] } + self.nicklist[name] = { + 'visible': visible, + 'nicks': [] + } else: - self.nicklist[parent]['nicks'].append({ 'prefix': prefix, - 'name': name, - 'visible': visible }) + self.nicklist[parent]['nicks'].append({'prefix': prefix, + 'name': name, + 'visible': visible}) def nicklist_remove_item(self, parent, group, name): """Remove a group/nick from nicklist.""" @@ -193,7 +202,10 @@ class Buffer(QtCore.QObject): del self.nicklist[name] else: if parent in self.nicklist: - self.nicklist[parent]['nicks'] = [nick for nick in self.nicklist[parent]['nicks'] if nick['name'] != name] + self.nicklist[parent]['nicks'] = [ + nick for nick in self.nicklist[parent]['nicks'] + if nick['name'] != name + ] def nicklist_update_item(self, parent, group, prefix, name, visible): """Update a group/nick in nicklist.""" @@ -212,11 +224,15 @@ class Buffer(QtCore.QObject): """Refresh nicklist.""" self.widget.nicklist.clear() for group in sorted(self.nicklist): - for nick in sorted(self.nicklist[group]['nicks'], key=lambda n:n['name']): - prefix_color = { '': '', ' ': '', '+': 'yellow' } + for nick in sorted(self.nicklist[group]['nicks'], + key=lambda n: n['name']): + prefix_color = {'': '', ' ': '', '+': 'yellow'} color = prefix_color.get(nick['prefix'], 'green') if color: - icon = QtGui.QIcon('data/icons/bullet_%s_8x8.png' % color) + icon = QtGui.QIcon( + resource_filename(__name__, + 'data/icons/bullet_%s_8x8.png' % + color)) else: pixmap = QtGui.QPixmap(8, 8) pixmap.fill() diff --git a/src/qweechat/chat.py b/qweechat/chat.py similarity index 79% rename from src/qweechat/chat.py rename to qweechat/chat.py index 36f420e..5b4be59 100644 --- a/src/qweechat/chat.py +++ b/qweechat/chat.py @@ -40,13 +40,27 @@ class ChatTextEdit(QtGui.QTextEdit): self.setFontFamily('monospace') self._textcolor = self.textColor() self._bgcolor = QtGui.QColor('#FFFFFF') - self._setcolorcode = { 'F': (self.setTextColor, self._textcolor), - 'B': (self.setTextBackgroundColor, self._bgcolor) } - self._setfont = { '*': self.setFontWeight, - '_': self.setFontUnderline, - '/': self.setFontItalic } - self._fontvalues = { False: { '*': QtGui.QFont.Normal, '_': False, '/': False }, - True: { '*': QtGui.QFont.Bold, '_': True, '/': True } } + self._setcolorcode = { + 'F': (self.setTextColor, self._textcolor), + 'B': (self.setTextBackgroundColor, self._bgcolor) + } + self._setfont = { + '*': self.setFontWeight, + '_': self.setFontUnderline, + '/': self.setFontItalic + } + self._fontvalues = { + False: { + '*': QtGui.QFont.Normal, + '_': False, + '/': False + }, + True: { + '*': QtGui.QFont.Bold, + '_': True, + '/': True + } + } self._color = color.Color(config.color_options(), self.debug) def display(self, time, prefix, text, forcecolor=None): @@ -93,17 +107,22 @@ class ChatTextEdit(QtGui.QTextEdit): # reset attributes and color if code == 'r': self._reset_attributes() - self._setcolorcode[action][0](self._setcolorcode[action][1]) + self._setcolorcode[action][0]( + self._setcolorcode[action][1]) else: # set attributes + color - while code.startswith(('*', '!', '/', '_', '|', 'r')): + while code.startswith(('*', '!', '/', '_', '|', + 'r')): if code[0] == 'r': self._reset_attributes() elif code[0] in self._setfont: - self._set_attribute(code[0], not self._font[code[0]]) + self._set_attribute( + code[0], + not self._font[code[0]]) code = code[1:] if code: - self._setcolorcode[action][0](QtGui.QColor(code)) + self._setcolorcode[action][0]( + QtGui.QColor(code)) item = item[pos+1:] if len(item) > 0: self.insertPlainText(item) diff --git a/qweechat/config.py b/qweechat/config.py new file mode 100644 index 0000000..b56e283 --- /dev/null +++ b/qweechat/config.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# config.py - configuration for QWeeChat (~/.qweechat/qweechat.conf) +# +# Copyright (C) 2011-2014 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 . +# + +import ConfigParser +import os + +CONFIG_DIR = '%s/.qweechat' % os.getenv('HOME') +CONFIG_FILENAME = '%s/qweechat.conf' % CONFIG_DIR + +CONFIG_DEFAULT_RELAY_LINES = 50 + +CONFIG_DEFAULT_SECTIONS = ('relay', 'look', 'color') +CONFIG_DEFAULT_OPTIONS = (('relay.server', ''), + ('relay.port', ''), + ('relay.ssl', 'off'), + ('relay.password', ''), + ('relay.autoconnect', 'off'), + ('relay.lines', str(CONFIG_DEFAULT_RELAY_LINES)), + ('look.debug', 'off'), + ('look.statusbar', 'off')) + +# Default colors for WeeChat color options (option name, #rgb value) +CONFIG_DEFAULT_COLOR_OPTIONS = ( + ('separator', '#000066'), # 0 + ('chat', '#000000'), # 1 + ('chat_time', '#999999'), # 2 + ('chat_time_delimiters', '#000000'), # 3 + ('chat_prefix_error', '#FF6633'), # 4 + ('chat_prefix_network', '#990099'), # 5 + ('chat_prefix_action', '#000000'), # 6 + ('chat_prefix_join', '#00CC00'), # 7 + ('chat_prefix_quit', '#CC0000'), # 8 + ('chat_prefix_more', '#CC00FF'), # 9 + ('chat_prefix_suffix', '#330099'), # 10 + ('chat_buffer', '#000000'), # 11 + ('chat_server', '#000000'), # 12 + ('chat_channel', '#000000'), # 13 + ('chat_nick', '#000000'), # 14 + ('chat_nick_self', '*#000000'), # 15 + ('chat_nick_other', '#000000'), # 16 + ('', '#000000'), # 17 (nick1 -- obsolete) + ('', '#000000'), # 18 (nick2 -- obsolete) + ('', '#000000'), # 19 (nick3 -- obsolete) + ('', '#000000'), # 20 (nick4 -- obsolete) + ('', '#000000'), # 21 (nick5 -- obsolete) + ('', '#000000'), # 22 (nick6 -- obsolete) + ('', '#000000'), # 23 (nick7 -- obsolete) + ('', '#000000'), # 24 (nick8 -- obsolete) + ('', '#000000'), # 25 (nick9 -- obsolete) + ('', '#000000'), # 26 (nick10 -- obsolete) + ('chat_host', '#666666'), # 27 + ('chat_delimiters', '#9999FF'), # 28 + ('chat_highlight', '#3399CC'), # 29 + ('chat_read_marker', '#000000'), # 30 + ('chat_text_found', '#000000'), # 31 + ('chat_value', '#000000'), # 32 + ('chat_prefix_buffer', '#000000'), # 33 + ('chat_tags', '#000000'), # 34 + ('chat_inactive_window', '#000000'), # 35 + ('chat_inactive_buffer', '#000000'), # 36 + ('chat_prefix_buffer_inactive_buffer', '#000000'), # 37 + ('chat_nick_offline', '#000000'), # 38 + ('chat_nick_offline_highlight', '#000000'), # 39 + ('chat_nick_prefix', '#000000'), # 40 + ('chat_nick_suffix', '#000000'), # 41 + ('emphasis', '#000000'), # 42 + ('chat_day_change', '#000000'), # 43 +) +config_color_options = [] + + +def read(): + """Read config file.""" + global config_color_options + config = ConfigParser.RawConfigParser() + if os.path.isfile(CONFIG_FILENAME): + config.read(CONFIG_FILENAME) + + # add missing sections/options + for section in CONFIG_DEFAULT_SECTIONS: + if not config.has_section(section): + config.add_section(section) + for option in reversed(CONFIG_DEFAULT_OPTIONS): + section, name = option[0].split('.', 1) + if not config.has_option(section, name): + config.set(section, name, option[1]) + section = 'color' + for option in reversed(CONFIG_DEFAULT_COLOR_OPTIONS): + if option[0] and not config.has_option(section, option[0]): + config.set(section, option[0], option[1]) + + # build list of color options + config_color_options = [] + for option in CONFIG_DEFAULT_COLOR_OPTIONS: + if option[0]: + config_color_options.append(config.get('color', option[0])) + else: + config_color_options.append('#000000') + + return config + + +def write(config): + """Write config file.""" + if not os.path.exists(CONFIG_DIR): + os.mkdir(CONFIG_DIR, 0o0755) + with open(CONFIG_FILENAME, 'wb') as cfg: + config.write(cfg) + + +def color_options(): + global config_color_options + return config_color_options diff --git a/src/qweechat/connection.py b/qweechat/connection.py similarity index 95% rename from src/qweechat/connection.py rename to qweechat/connection.py index cdf30c3..739758a 100644 --- a/src/qweechat/connection.py +++ b/qweechat/connection.py @@ -57,7 +57,8 @@ class ConnectionDialog(QtGui.QDialog): self.fields['ssl'] = ssl self.dialog_buttons = QtGui.QDialogButtonBox() - self.dialog_buttons.setStandardButtons(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) + self.dialog_buttons.setStandardButtons( + QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) self.dialog_buttons.rejected.connect(self.close) grid.addWidget(self.dialog_buttons, 4, 0, 1, 2) diff --git a/data/icons/README b/qweechat/data/icons/README similarity index 100% rename from data/icons/README rename to qweechat/data/icons/README diff --git a/data/icons/application-exit.png b/qweechat/data/icons/application-exit.png similarity index 100% rename from data/icons/application-exit.png rename to qweechat/data/icons/application-exit.png diff --git a/data/icons/bullet_green_8x8.png b/qweechat/data/icons/bullet_green_8x8.png similarity index 100% rename from data/icons/bullet_green_8x8.png rename to qweechat/data/icons/bullet_green_8x8.png diff --git a/data/icons/bullet_yellow_8x8.png b/qweechat/data/icons/bullet_yellow_8x8.png similarity index 100% rename from data/icons/bullet_yellow_8x8.png rename to qweechat/data/icons/bullet_yellow_8x8.png diff --git a/data/icons/dialog-close.png b/qweechat/data/icons/dialog-close.png similarity index 100% rename from data/icons/dialog-close.png rename to qweechat/data/icons/dialog-close.png diff --git a/data/icons/dialog-ok-apply.png b/qweechat/data/icons/dialog-ok-apply.png similarity index 100% rename from data/icons/dialog-ok-apply.png rename to qweechat/data/icons/dialog-ok-apply.png diff --git a/data/icons/document-save.png b/qweechat/data/icons/document-save.png similarity index 100% rename from data/icons/document-save.png rename to qweechat/data/icons/document-save.png diff --git a/data/icons/edit-find.png b/qweechat/data/icons/edit-find.png similarity index 100% rename from data/icons/edit-find.png rename to qweechat/data/icons/edit-find.png diff --git a/data/icons/help-about.png b/qweechat/data/icons/help-about.png similarity index 100% rename from data/icons/help-about.png rename to qweechat/data/icons/help-about.png diff --git a/data/icons/network-connect.png b/qweechat/data/icons/network-connect.png similarity index 100% rename from data/icons/network-connect.png rename to qweechat/data/icons/network-connect.png diff --git a/data/icons/network-disconnect.png b/qweechat/data/icons/network-disconnect.png similarity index 100% rename from data/icons/network-disconnect.png rename to qweechat/data/icons/network-disconnect.png diff --git a/data/icons/preferences-other.png b/qweechat/data/icons/preferences-other.png similarity index 100% rename from data/icons/preferences-other.png rename to qweechat/data/icons/preferences-other.png diff --git a/data/icons/weechat_icon_32.png b/qweechat/data/icons/weechat_icon_32.png similarity index 100% rename from data/icons/weechat_icon_32.png rename to qweechat/data/icons/weechat_icon_32.png diff --git a/src/qweechat/debug.py b/qweechat/debug.py similarity index 100% rename from src/qweechat/debug.py rename to qweechat/debug.py diff --git a/src/qweechat/input.py b/qweechat/input.py similarity index 71% rename from src/qweechat/input.py rename to qweechat/input.py index 1fbacd8..373ee77 100644 --- a/src/qweechat/input.py +++ b/qweechat/input.py @@ -44,26 +44,32 @@ class InputLineEdit(QtGui.QLineEdit): key = event.key() modifiers = event.modifiers() bar = self.scroll_widget.verticalScrollBar() - if modifiers == QtCore.Qt.ControlModifier and key == QtCore.Qt.Key_PageUp: - self.bufferSwitchPrev.emit() - elif modifiers == QtCore.Qt.AltModifier and key in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Up): - self.bufferSwitchPrev.emit() - elif modifiers == QtCore.Qt.ControlModifier and key == QtCore.Qt.Key_PageDown: - self.bufferSwitchNext.emit() - elif modifiers == QtCore.Qt.AltModifier and key in (QtCore.Qt.Key_Right, QtCore.Qt.Key_Down): - self.bufferSwitchNext.emit() - elif modifiers == QtCore.Qt.AltModifier and key == QtCore.Qt.Key_PageUp: - bar.setValue(bar.value() - (bar.pageStep() / 10)) - elif modifiers == QtCore.Qt.AltModifier and key == QtCore.Qt.Key_PageDown: - bar.setValue(bar.value() + (bar.pageStep() / 10)) + if modifiers == QtCore.Qt.ControlModifier: + if key == QtCore.Qt.Key_PageUp: + self.bufferSwitchPrev.emit() + elif key == QtCore.Qt.Key_PageDown: + self.bufferSwitchNext.emit() + else: + QtGui.QLineEdit.keyPressEvent(self, event) + elif modifiers == QtCore.Qt.AltModifier: + if key in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Up): + self.bufferSwitchPrev.emit() + elif key in (QtCore.Qt.Key_Right, QtCore.Qt.Key_Down): + self.bufferSwitchNext.emit() + elif key == QtCore.Qt.Key_PageUp: + bar.setValue(bar.value() - (bar.pageStep() / 10)) + elif key == QtCore.Qt.Key_PageDown: + bar.setValue(bar.value() + (bar.pageStep() / 10)) + elif key == QtCore.Qt.Key_Home: + bar.setValue(bar.minimum()) + elif key == QtCore.Qt.Key_End: + bar.setValue(bar.maximum()) + else: + QtGui.QLineEdit.keyPressEvent(self, event) elif key == QtCore.Qt.Key_PageUp: bar.setValue(bar.value() - bar.pageStep()) elif key == QtCore.Qt.Key_PageDown: bar.setValue(bar.value() + bar.pageStep()) - elif modifiers == QtCore.Qt.AltModifier and key == QtCore.Qt.Key_Home: - bar.setValue(bar.minimum()) - elif modifiers == QtCore.Qt.AltModifier and key == QtCore.Qt.Key_End: - bar.setValue(bar.maximum()) elif key == QtCore.Qt.Key_Up: self._history_navigate(-1) elif key == QtCore.Qt.Key_Down: diff --git a/src/qweechat/network.py b/qweechat/network.py similarity index 85% rename from src/qweechat/network.py rename to qweechat/network.py index 8ae1622..eb74a13 100644 --- a/src/qweechat/network.py +++ b/qweechat/network.py @@ -27,12 +27,21 @@ QtCore = qt_compat.import_module('QtCore') QtNetwork = qt_compat.import_module('QtNetwork') import config -_PROTO_INIT_CMD = ['init password=%(password)s'] -_PROTO_SYNC_CMDS = ['(listbuffers) hdata buffer:gui_buffers(*) number,full_name,short_name,type,nicklist,title,local_variables', - '(listlines) hdata buffer:gui_buffers(*)/own_lines/last_line(-%(lines)d)/data date,displayed,prefix,message', - '(nicklist) nicklist', - 'sync', - ''] +_PROTO_INIT_CMD = ['init password=%(password)s'] + +_PROTO_SYNC_CMDS = [ + '(listbuffers) hdata buffer:gui_buffers(*) number,full_name,short_name,' + 'type,nicklist,title,local_variables', + + '(listlines) hdata buffer:gui_buffers(*)/own_lines/last_line(-%(lines)d)/' + 'data date,displayed,prefix,message', + + '(nicklist) nicklist', + + 'sync', + + '' +] class Network(QtCore.QObject): @@ -68,7 +77,9 @@ class Network(QtCore.QObject): def _socket_error(self, error): """Slot: socket error.""" - self.statusChanged.emit(self.status_disconnected, 'Failed, error: %s' % self._socket.errorString()) + self.statusChanged.emit( + self.status_disconnected, + 'Failed, error: %s' % self._socket.errorString()) def _socket_read(self): """Slot: data available on socket.""" @@ -129,13 +140,14 @@ class Network(QtCore.QObject): self.statusChanged.emit(self.status_connecting, None) def disconnect_weechat(self): - if self._socket.state() != QtNetwork.QAbstractSocket.UnconnectedState: - if self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState: - self.send_to_weechat('quit\n') - self._socket.waitForBytesWritten(1000) - else: - self.statusChanged.emit(self.status_disconnected, None) - self._socket.abort() + if self._socket.state() == QtNetwork.QAbstractSocket.UnconnectedState: + return + if self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState: + self.send_to_weechat('quit\n') + self._socket.waitForBytesWritten(1000) + else: + self.statusChanged.emit(self.status_disconnected, None) + self._socket.abort() def send_to_weechat(self, message): self._socket.write(message.encode('utf-8')) diff --git a/src/qweechat/qt_compat.py b/qweechat/qt_compat.py similarity index 92% rename from src/qweechat/qt_compat.py rename to qweechat/qt_compat.py index 9f7b93b..9b96a31 100644 --- a/src/qweechat/qt_compat.py +++ b/qweechat/qt_compat.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -# File downloaded from: https://github.com/epage/PythonUtils/blob/master/util/qt_compat.py +# File downloaded from: +# https://github.com/epage/PythonUtils/blob/master/util/qt_compat.py # Author: epage # License: LGPL 2.1 # diff --git a/qweechat/qweechat.py b/qweechat/qweechat.py new file mode 100644 index 0000000..d78c54d --- /dev/null +++ b/qweechat/qweechat.py @@ -0,0 +1,545 @@ +# -*- coding: utf-8 -*- +# +# qweechat.py - WeeChat remote GUI using Qt toolkit +# +# Copyright (C) 2011-2014 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 +import qt_compat +QTCORE = qt_compat.import_module('QtCore') +QTGUI = qt_compat.import_module('QtGui') +import config +import weechat.protocol as protocol +from network import Network +from connection import ConnectionDialog +from buffer import BufferListWidget, Buffer +from debug import DebugDialog +from about import AboutDialog + +NAME = 'QWeeChat' +VERSION = '0.0.1-dev' +AUTHOR = 'Sébastien Helleu' +AUTHOR_MAIL = 'flashcode@flashtux.org' +WEECHAT_SITE = 'http://weechat.org/' + +# number of lines in buffer for debug window +DEBUG_NUM_LINES = 50 + + +class MainWindow(QTGUI.QMainWindow): + """Main window.""" + + def __init__(self, *args): + QTGUI.QMainWindow.__init__(*(self,) + args) + + self.config = config.read() + + self.resize(1000, 600) + self.setWindowTitle(NAME) + + self.debug_dialog = None + self.debug_lines = [] + + self.about_dialog = None + self.connection_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 = QTGUI.QStackedWidget() + self.stacked_buffers.addWidget(self.buffers[0].widget) + + # splitter with buffers + chat/input + splitter = QTGUI.QSplitter() + splitter.addWidget(self.list_buffers) + splitter.addWidget(self.stacked_buffers) + + self.setCentralWidget(splitter) + + if self.config.getboolean('look', 'statusbar'): + 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', 'Debug console window', + 'Ctrl+B', self.open_debug_dialog], + 'preferences': [ + 'preferences-other.png', 'Preferences', + 'Ctrl+P', self.open_preferences_dialog], + 'about': [ + 'help-about.png', 'About', + '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] = QTGUI.QAction( + QTGUI.QIcon( + resource_filename(__name__, 'data/icons/%s' % action[0])), + name.capitalize(), self) + self.actions[name].setStatusTip(action[1]) + 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']) + menu_help = self.menu.addMenu('&Help') + menu_help.addAction(self.actions['about']) + self.network_status = QTGUI.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(self.network.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.buffers[0].widget.input.setFocus() + + # open debug dialog + if self.config.getboolean('look', 'debug'): + self.open_debug_dialog() + + # auto-connect to relay + if self.config.getboolean('relay', 'autoconnect'): + self.network.connect_weechat(self.config.get('relay', 'server'), + self.config.get('relay', 'port'), + self.config.getboolean('relay', + 'ssl'), + self.config.get('relay', 'password'), + self.config.get('relay', 'lines')) + + 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.debug_display(0, '<==', message, forcecolor='#AA0000') + + def open_preferences_dialog(self): + """Open a dialog with preferences.""" + pass # TODO + + def save_connection(self): + """Save connection configuration.""" + if self.network: + options = self.network.get_options() + for option in options.keys(): + self.config.set('relay', option, options[option]) + + def debug_display(self, *args, **kwargs): + """Display a debug message.""" + self.debug_lines.append((args, kwargs)) + self.debug_lines = self.debug_lines[-DEBUG_NUM_LINES:] + if self.debug_dialog: + self.debug_dialog.chat.display(*args, **kwargs) + + def open_debug_dialog(self): + """Open a dialog with debug messages.""" + if not self.debug_dialog: + self.debug_dialog = DebugDialog(self) + self.debug_dialog.input.textSent.connect( + self.debug_input_text_sent) + self.debug_dialog.finished.connect(self._debug_dialog_closed) + self.debug_dialog.display_lines(self.debug_lines) + self.debug_dialog.chat.scroll_bottom() + + def debug_input_text_sent(self, text): + """Send debug buffer input to WeeChat.""" + if self.network.is_connected(): + text = str(text) + pos = text.find(')') + if text.startswith('(') and pos >= 0: + text = '(debug_%s)%s' % (text[1:pos], text[pos+1:]) + else: + text = '(debug) %s' % text + self.debug_display(0, '<==', text, forcecolor='#AA0000') + self.network.send_to_weechat(text + '\n') + + def _debug_dialog_closed(self, result): + """Called when debug dialog is closed.""" + self.debug_dialog = None + + def open_about_dialog(self): + """Open a dialog with info about QWeeChat.""" + messages = ['%s %s' % (NAME, VERSION), + '© 2011-2014 %s <%s>' + % (AUTHOR, AUTHOR_MAIL, AUTHOR_MAIL), + '', + 'Running with %s' % ('PySide' if qt_compat.uses_pyside + else 'PyQt4'), + '', + 'WeeChat site: %s' + % (WEECHAT_SITE, WEECHAT_SITE), + ''] + self.about_dialog = AboutDialog(NAME, messages, self) + + def open_connection_dialog(self): + """Open a dialog with connection settings.""" + values = {} + for option in ('server', 'port', 'ssl', 'password', 'lines'): + values[option] = self.config.get('relay', option) + 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( + self.connection_dialog.fields['server'].text(), + self.connection_dialog.fields['port'].text(), + self.connection_dialog.fields['ssl'].isChecked(), + self.connection_dialog.fields['password'].text(), + int(self.connection_dialog.fields['lines'].text())) + 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.debug_display(0, '', status, forcecolor='#0000AA') + self.network_status_set(status) + + def network_status_set(self, status): + """Set the network status.""" + pal = self.network_status.palette() + if status == self.network.status_connected: + pal.setColor(self.network_status.foregroundRole(), + QTGUI.QColor('green')) + else: + pal.setColor(self.network_status.foregroundRole(), + QTGUI.QColor('#aa0000')) + ssl = ' (SSL)' if status != self.network.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), + status.capitalize() + ssl)) + else: + self.network_status.setText(status.capitalize()) + if status == self.network.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.debug_display(0, '==>', + 'message (%d bytes):\n%s' + % (len(message), + protocol.hex_and_ascii(message, 20)), + forcecolor='#008800') + try: + proto = protocol.Protocol() + message = proto.decode(str(message)) + if message.uncompressed: + self.debug_display( + 0, '==>', + 'message uncompressed (%d bytes):\n%s' + % (message.size_uncompressed, + protocol.hex_and_ascii(message.uncompressed, 20)), + forcecolor='#008800') + self.debug_display(0, '', 'Message: %s' % message) + self.parse_message(message) + except: + print('Error while decoding message from WeeChat:\n%s' + % traceback.format_exc()) + self.network.disconnect_weechat() + + def _parse_listbuffers(self, message): + """Parse a WeeChat 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((item['date'], item['prefix'], + item['message'])) + if message.msgid == 'listlines': + lines.reverse() + for line in lines: + self.buffers[index[0]].widget.chat.display(*line) + + 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.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.debug_display(0, '', '(debug message, ignored)') + 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() + + 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, '%d. %s' + % (buf.data['number'], + buf.data['full_name'].decode('utf-8'))) + 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: + index = [i for i, b in enumerate(self.buffers) + if b.pointer() == next_buffer] + if index: + index = index[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.debug_dialog: + self.debug_dialog.close() + config.write(self.config) + QTGUI.QMainWindow.closeEvent(self, event) + + +app = QTGUI.QApplication(sys.argv) +app.setStyle(QTGUI.QStyleFactory.create('Cleanlooks')) +app.setWindowIcon(QTGUI.QIcon( + resource_filename(__name__, 'data/icons/weechat_icon_32.png'))) +main = MainWindow() +sys.exit(app.exec_()) diff --git a/src/qweechat/weechat/__init__.py b/qweechat/weechat/__init__.py similarity index 100% rename from src/qweechat/weechat/__init__.py rename to qweechat/weechat/__init__.py diff --git a/src/qweechat/weechat/color.py b/qweechat/weechat/color.py similarity index 64% rename from src/qweechat/weechat/color.py rename to qweechat/weechat/color.py index e5581a4..924c0ce 100644 --- a/src/qweechat/weechat/color.py +++ b/qweechat/weechat/color.py @@ -28,33 +28,53 @@ RE_COLOR_STD = r'(?:%s\d{2})' % RE_COLOR_ATTRS RE_COLOR_EXT = r'(?:@%s\d{5})' % RE_COLOR_ATTRS RE_COLOR_ANY = r'(?:%s|%s)' % (RE_COLOR_STD, RE_COLOR_EXT) # \x19: color code, \x1A: set attribute, \x1B: remove attribute, \x1C: reset -RE_COLOR = re.compile(r'(\x19(?:\d{2}|F%s|B\d{2}|B@\d{5}|E|\\*%s(,%s)?|@\d{5}|b.|\x1C))|\x1A.|\x1B.|\x1C' - % (RE_COLOR_ANY, RE_COLOR_ANY, RE_COLOR_ANY)) +RE_COLOR = re.compile( + r'(\x19(?:\d{2}|F%s|B\d{2}|B@\d{5}|E|\\*%s(,%s)?|@\d{5}|b.|\x1C))|\x1A.|' + r'\x1B.|\x1C' + % (RE_COLOR_ANY, RE_COLOR_ANY, RE_COLOR_ANY)) TERMINAL_COLORS = \ - '000000cd000000cd00cdcd000000cdcd00cd00cdcde5e5e54d4d4dff000000ff00ffff000000ffff00ff00ffffffffff' \ - '00000000002a0000550000800000aa0000d4002a00002a2a002a55002a80002aaa002ad400550000552a005555005580' \ - '0055aa0055d400800000802a0080550080800080aa0080d400aa0000aa2a00aa5500aa8000aaaa00aad400d40000d42a' \ - '00d45500d48000d4aa00d4d42a00002a002a2a00552a00802a00aa2a00d42a2a002a2a2a2a2a552a2a802a2aaa2a2ad4' \ - '2a55002a552a2a55552a55802a55aa2a55d42a80002a802a2a80552a80802a80aa2a80d42aaa002aaa2a2aaa552aaa80' \ - '2aaaaa2aaad42ad4002ad42a2ad4552ad4802ad4aa2ad4d455000055002a5500555500805500aa5500d4552a00552a2a' \ - '552a55552a80552aaa552ad455550055552a5555555555805555aa5555d455800055802a5580555580805580aa5580d4' \ - '55aa0055aa2a55aa5555aa8055aaaa55aad455d40055d42a55d45555d48055d4aa55d4d480000080002a800055800080' \ - '8000aa8000d4802a00802a2a802a55802a80802aaa802ad480550080552a8055558055808055aa8055d480800080802a' \ - '8080558080808080aa8080d480aa0080aa2a80aa5580aa8080aaaa80aad480d40080d42a80d45580d48080d4aa80d4d4' \ - 'aa0000aa002aaa0055aa0080aa00aaaa00d4aa2a00aa2a2aaa2a55aa2a80aa2aaaaa2ad4aa5500aa552aaa5555aa5580' \ - 'aa55aaaa55d4aa8000aa802aaa8055aa8080aa80aaaa80d4aaaa00aaaa2aaaaa55aaaa80aaaaaaaaaad4aad400aad42a' \ - 'aad455aad480aad4aaaad4d4d40000d4002ad40055d40080d400aad400d4d42a00d42a2ad42a55d42a80d42aaad42ad4' \ - 'd45500d4552ad45555d45580d455aad455d4d48000d4802ad48055d48080d480aad480d4d4aa00d4aa2ad4aa55d4aa80' \ - 'd4aaaad4aad4d4d400d4d42ad4d455d4d480d4d4aad4d4d40808081212121c1c1c2626263030303a3a3a4444444e4e4e' \ - '5858586262626c6c6c7676768080808a8a8a9494949e9e9ea8a8a8b2b2b2bcbcbcc6c6c6d0d0d0dadadae4e4e4eeeeee' + '000000cd000000cd00cdcd000000cdcd00cd00cdcde5e5e5' \ + '4d4d4dff000000ff00ffff000000ffff00ff00ffffffffff' \ + '00000000002a0000550000800000aa0000d4002a00002a2a' \ + '002a55002a80002aaa002ad400550000552a005555005580' \ + '0055aa0055d400800000802a0080550080800080aa0080d4' \ + '00aa0000aa2a00aa5500aa8000aaaa00aad400d40000d42a' \ + '00d45500d48000d4aa00d4d42a00002a002a2a00552a0080' \ + '2a00aa2a00d42a2a002a2a2a2a2a552a2a802a2aaa2a2ad4' \ + '2a55002a552a2a55552a55802a55aa2a55d42a80002a802a' \ + '2a80552a80802a80aa2a80d42aaa002aaa2a2aaa552aaa80' \ + '2aaaaa2aaad42ad4002ad42a2ad4552ad4802ad4aa2ad4d4' \ + '55000055002a5500555500805500aa5500d4552a00552a2a' \ + '552a55552a80552aaa552ad455550055552a555555555580' \ + '5555aa5555d455800055802a5580555580805580aa5580d4' \ + '55aa0055aa2a55aa5555aa8055aaaa55aad455d40055d42a' \ + '55d45555d48055d4aa55d4d480000080002a800055800080' \ + '8000aa8000d4802a00802a2a802a55802a80802aaa802ad4' \ + '80550080552a8055558055808055aa8055d480800080802a' \ + '8080558080808080aa8080d480aa0080aa2a80aa5580aa80' \ + '80aaaa80aad480d40080d42a80d45580d48080d4aa80d4d4' \ + 'aa0000aa002aaa0055aa0080aa00aaaa00d4aa2a00aa2a2a' \ + 'aa2a55aa2a80aa2aaaaa2ad4aa5500aa552aaa5555aa5580' \ + 'aa55aaaa55d4aa8000aa802aaa8055aa8080aa80aaaa80d4' \ + 'aaaa00aaaa2aaaaa55aaaa80aaaaaaaaaad4aad400aad42a' \ + 'aad455aad480aad4aaaad4d4d40000d4002ad40055d40080' \ + 'd400aad400d4d42a00d42a2ad42a55d42a80d42aaad42ad4' \ + 'd45500d4552ad45555d45580d455aad455d4d48000d4802a' \ + 'd48055d48080d480aad480d4d4aa00d4aa2ad4aa55d4aa80' \ + 'd4aaaad4aad4d4d400d4d42ad4d455d4d480d4d4aad4d4d4' \ + '0808081212121c1c1c2626263030303a3a3a4444444e4e4e' \ + '5858586262626c6c6c7676768080808a8a8a9494949e9e9e' \ + 'a8a8a8b2b2b2bcbcbcc6c6c6d0d0d0dadadae4e4e4eeeeee' # WeeChat basic colors (color name, index in terminal colors) -WEECHAT_BASIC_COLORS = (('default', 0), ('black', 0), ('darkgray', 8), ('red', 1), - ('lightred', 9), ('green', 2), ('lightgreen', 10), ('brown', 3), - ('yellow', 11), ('blue', 4), ('lightblue', 12), ('magenta', 5), - ('lightmagenta', 13), ('cyan', 6), ('lightcyan', 14), ('gray', 7), - ('white', 0)) +WEECHAT_BASIC_COLORS = ( + ('default', 0), ('black', 0), ('darkgray', 8), ('red', 1), + ('lightred', 9), ('green', 2), ('lightgreen', 10), ('brown', 3), + ('yellow', 11), ('blue', 4), ('lightblue', 12), ('magenta', 5), + ('lightmagenta', 13), ('cyan', 6), ('lightcyan', 14), ('gray', 7), + ('white', 0)) + class Color(): def __init__(self, color_options, debug=False): @@ -90,23 +110,25 @@ class Color(): extended = True color = color[1:] attrs = '' - keep_attrs = False + # keep_attrs = False while color.startswith(('*', '!', '/', '_', '|')): - if color[0] == '|': - keep_attrs = True + # TODO: manage the "keep attributes" flag + # if color[0] == '|': + # keep_attrs = True attrs += color[0] color = color[1:] if extended: return self._convert_terminal_color(fg_bg, attrs, color) try: index = int(color) - return self._convert_terminal_color(fg_bg, attrs, WEECHAT_BASIC_COLORS[index][1]) + return self._convert_terminal_color(fg_bg, attrs, + WEECHAT_BASIC_COLORS[index][1]) except: print('Error decoding color "%s"' % color) return '' def _attrcode_to_char(self, code): - codes = { '\x01': '*', '\x02': '!', '\x03': '/', '\x04': '_' } + codes = {'\x01': '*', '\x02': '!', '\x03': '/', '\x04': '_'} return codes.get(code, '') def _convert_color(self, match): @@ -164,6 +186,7 @@ class Color(): else: return RE_COLOR.sub(self._convert_color, text) + def remove(text): """Remove colors in a WeeChat string.""" if not text: diff --git a/src/qweechat/weechat/protocol.py b/qweechat/weechat/protocol.py similarity index 85% rename from src/qweechat/weechat/protocol.py rename to qweechat/weechat/protocol.py index 8935e80..95ddbe4 100644 --- a/src/qweechat/weechat/protocol.py +++ b/qweechat/weechat/protocol.py @@ -31,20 +31,24 @@ # start dev # -import collections, struct, zlib +import collections +import struct +import zlib if hasattr(collections, 'OrderedDict'): # python >= 2.7 class WeechatDict(collections.OrderedDict): def __str__(self): - return '{%s}' % ', '.join(['%s: %s' % (repr(key), repr(self[key])) for key in self]) + return '{%s}' % ', '.join( + ['%s: %s' % (repr(key), repr(self[key])) for key in self]) else: # python <= 2.6 WeechatDict = dict + class WeechatObject: def __init__(self, objtype, value, separator='\n'): - self.objtype = objtype; + self.objtype = objtype self.value = value self.separator = separator self.indent = ' ' if separator == '\n' else '' @@ -56,17 +60,29 @@ class WeechatObject: return str(v) def _str_value_hdata(self): - lines = ['%skeys: %s%s%spath: %s' % (self.separator1, str(self.value['keys']), self.separator, self.indent, str(self.value['path']))] + lines = ['%skeys: %s%s%spath: %s' % (self.separator1, + str(self.value['keys']), + self.separator, + self.indent, + str(self.value['path']))] for i, item in enumerate(self.value['items']): - lines.append(' item %d:%s%s' % ((i + 1), self.separator, - self.separator.join(['%s%s: %s' % (self.indent * 2, key, self._str_value(value)) for key, value in item.items()]))) + lines.append(' item %d:%s%s' % ( + (i + 1), self.separator, + self.separator.join( + ['%s%s: %s' % (self.indent * 2, key, + self._str_value(value)) + for key, value in item.items()]))) return '\n'.join(lines) def _str_value_infolist(self): lines = ['%sname: %s' % (self.separator1, self.value['name'])] for i, item in enumerate(self.value['items']): - lines.append(' item %d:%s%s' % ((i + 1), self.separator, - self.separator.join(['%s%s: %s' % (self.indent * 2, key, self._str_value(value)) for key, value in item.items()]))) + lines.append(' item %d:%s%s' % ( + (i + 1), self.separator, + self.separator.join( + ['%s%s: %s' % (self.indent * 2, key, + self._str_value(value)) + for key, value in item.items()]))) return '\n'.join(lines) def _str_value_other(self): @@ -76,7 +92,9 @@ class WeechatObject: self._obj_cb = {'hda': self._str_value_hdata, 'inl': self._str_value_infolist, } - return '%s: %s' % (self.objtype, self._obj_cb.get(self.objtype, self._str_value_other)()) + return '%s: %s' % (self.objtype, + self._obj_cb.get(self.objtype, + self._str_value_other)()) class WeechatObjects(list): @@ -88,7 +106,8 @@ class WeechatObjects(list): class WeechatMessage: - def __init__(self, size, size_uncompressed, compression, uncompressed, msgid, objects): + def __init__(self, size, size_uncompressed, compression, uncompressed, + msgid, objects): self.size = size self.size_uncompressed = size_uncompressed self.compression = compression @@ -103,7 +122,9 @@ class WeechatMessage: 100 - ((self.size * 100) // self.size_uncompressed), self.msgid, self.objects) else: - return 'size: %d, id=\'%s\', objects:\n%s' % (self.size, self.msgid, self.objects) + return 'size: %d, id=\'%s\', objects:\n%s' % (self.size, + self.msgid, + self.objects) class Protocol: @@ -202,7 +223,10 @@ class Protocol: return int(str(value)) def _obj_hashtable(self): - """Read a hashtable in data (type for keys + type for values + count + items).""" + """ + Read a hashtable in data + (type for keys + type for values + count + items). + """ type_keys = self._obj_type() type_values = self._obj_type() count = self._obj_int() @@ -285,7 +309,8 @@ class Protocol: if compression: uncompressed = zlib.decompress(self.data[5:]) size_uncompressed = len(uncompressed) + 5 - uncompressed = '%s%s%s' % (struct.pack('>i', size_uncompressed), struct.pack('b', 0), uncompressed) + uncompressed = '%s%s%s' % (struct.pack('>i', size_uncompressed), + struct.pack('b', 0), uncompressed) self.data = uncompressed else: uncompressed = self.data[:] @@ -301,7 +326,8 @@ class Protocol: objtype = self._obj_type() value = self._obj_cb[objtype]() objects.append(WeechatObject(objtype, value, separator=separator)) - return WeechatMessage(size, size_uncompressed, compression, uncompressed, msgid, objects) + return WeechatMessage(size, size_uncompressed, compression, + uncompressed, msgid, objects) def hex_and_ascii(data, bytes_per_line=10): diff --git a/src/qweechat/weechat/testproto.py b/qweechat/weechat/testproto.py old mode 100755 new mode 100644 similarity index 87% rename from src/qweechat/weechat/testproto.py rename to qweechat/weechat/testproto.py index ded7f4a..72195e6 --- a/src/qweechat/weechat/testproto.py +++ b/qweechat/weechat/testproto.py @@ -1,7 +1,6 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # -# testproto.py - command-line program for testing protocol WeeChat/relay +# testproto.py - command-line program for testing WeeChat/relay protocol # # Copyright (C) 2013-2014 Sébastien Helleu # @@ -21,6 +20,10 @@ # along with QWeeChat. If not, see . # +""" +Command-line program for testing WeeChat/relay protocol. +""" + from __future__ import print_function import argparse @@ -35,8 +38,11 @@ import traceback import protocol # WeeChat/relay protocol +NAME = 'qweechat-testproto' -class TestProto: + +class TestProto(object): + """Test of WeeChat/relay protocol.""" def __init__(self, args): self.args = args @@ -110,11 +116,11 @@ class TestProto: Return True if OK (it's OK if stdin has no commands), False if error. """ - inr, outr, exceptr = select.select([sys.stdin], [], [], 0) + inr = select.select([sys.stdin], [], [], 0)[0] if inr: data = os.read(sys.stdin.fileno(), 4096) if data: - if not test.send(data.strip()): + if not self.send(data.strip()): #self.sock.close() return False # open stdin to read user commands @@ -136,11 +142,10 @@ class TestProto: sys.stdout.flush() try: while not self.has_quit: - inr, outr, exceptr = select.select([sys.stdin, self.sock], - [], [], 1) - for fd in inr: - if fd == sys.stdin: - buf = os.read(fd.fileno(), 4096) + inr = select.select([sys.stdin, self.sock], [], [], 1)[0] + for _file in inr: + if _file == sys.stdin: + buf = os.read(_file.fileno(), 4096) if buf: message += buf if '\n' in message: @@ -152,7 +157,7 @@ class TestProto: sys.stdout.write(prompt + message) sys.stdout.flush() else: - buf = fd.recv(4096) + buf = _file.recv(4096) if buf: recvbuf += buf while len(recvbuf) >= 4: @@ -186,19 +191,20 @@ class TestProto: self.sock.close() -if __name__ == "__main__": +def main(): + """Main function.""" # parse command line arguments parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, fromfile_prefix_chars='@', - description='Command-line program for testing protocol WeeChat/relay.', + description='Command-line program for testing WeeChat/relay protocol.', epilog=''' -Environment variable "TESTPROTO_OPTIONS" can be set with default options. +Environment variable "QWEECHAT_PROTO_OPTIONS" can be set with default options. Argument "@file.txt" can be used to read default options in a file. Some commands can be piped to the script, for example: - echo "init password=xxxx" | python {0} localhost 5000 - python {0} localhost 5000 < commands.txt + echo "init password=xxxx" | {name} localhost 5000 + {name} localhost 5000 < commands.txt The script returns: 0: OK @@ -206,7 +212,7 @@ The script returns: 3: connection error 4: send error (message sent to WeeChat) 5: decode error (message received from WeeChat) -'''.format(sys.argv[0])) +'''.format(name=NAME)) parser.add_argument('-6', '--ipv6', action='store_true', help='connect using IPv6') parser.add_argument('-v', '--verbose', action='count', default=0, @@ -220,10 +226,10 @@ The script returns: if len(sys.argv) == 1: parser.print_help() sys.exit(0) - args = parser.parse_args( - shlex.split(os.getenv('TESTPROTO_OPTIONS') or '') + sys.argv[1:]) + _args = parser.parse_args( + shlex.split(os.getenv('QWEECHAT_PROTO_OPTIONS') or '') + sys.argv[1:]) - test = TestProto(args) + test = TestProto(_args) # connect to WeeChat/relay if not test.connect(): @@ -234,6 +240,10 @@ The script returns: sys.exit(4) # main loop (wait commands, display messages received) - rc = test.mainloop() + returncode = test.mainloop() del test - sys.exit(rc) + sys.exit(returncode) + + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 index 3ad80e0..8b31709 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2011-2014 Sébastien Helleu @@ -19,35 +18,40 @@ # along with QWeeChat. If not, see . # -import os -from distutils.core import setup +from setuptools import setup -def listfiles(dir): - return ['%s/%s' % (dir, f) for f in os.listdir(dir)] +DESCRIPTION = 'Qt remote GUI for WeeChat' -setup(name='qweechat', - version='0.0.1-dev', - description='Qt remote GUI for WeeChat', - long_description='Qt remote GUI for WeeChat', - author='Sébastien Helleu', - author_email='flashcode@flashtux.org', - url='http://weechat.org/', - license='GPL3', - classifiers = ['Development Status :: 2 - Pre-Alpha', - 'Environment :: X11 Applications :: Qt', - 'Intended Audience :: End Users/Desktop', - 'License :: OSI Approved :: GNU General Public License (GPL)', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Topic :: Communications :: Chat', - ], - platforms='OS Independent', - packages=['qweechat', - 'qweechat.weechat', - ], - package_dir={'qweechat': 'src/qweechat', - 'qweechat.weechat': 'src/qweechat/weechat', - }, - data_files=[('data/icons', listfiles('data/icons'))] - ) +setup( + name='qweechat', + version='0.0.1-dev', + description=DESCRIPTION, + long_description=DESCRIPTION, + author='Sébastien Helleu', + author_email='flashcode@flashtux.org', + url='http://weechat.org/', + license='GPL3', + keywords='weechat qt gui', + classifiers=[ + 'Development Status :: 4 - Beta', + 'Environment :: X11 Applications :: Qt', + 'Intended Audience :: End Users/Desktop', + 'License :: OSI Approved :: GNU General Public License v3 ' + 'or later (GPLv3+)', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Communications :: Chat', + ], + packages=['qweechat', 'qweechat.weechat'], + include_package_data=True, + package_data={'qweechat': ['data/icons/*.png']}, + entry_points = { + 'gui_scripts': [ + 'qweechat = qweechat.qweechat', + ], + 'console_scripts': [ + 'qweechat-testproto = qweechat.weechat.testproto:main', + ] + } +) diff --git a/src/qweechat/config.py b/src/qweechat/config.py deleted file mode 100644 index 6c01cc9..0000000 --- a/src/qweechat/config.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# config.py - configuration for QWeeChat (~/.qweechat/qweechat.conf) -# -# Copyright (C) 2011-2014 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 . -# - -import os, ConfigParser -import weechat.color as color - -CONFIG_DIR = '%s/.qweechat' % os.getenv('HOME') -CONFIG_FILENAME = '%s/qweechat.conf' % CONFIG_DIR - -CONFIG_DEFAULT_RELAY_LINES = 50 - -CONFIG_DEFAULT_SECTIONS = ('relay', 'look', 'color') -CONFIG_DEFAULT_OPTIONS = (('relay.server', ''), - ('relay.port', ''), - ('relay.ssl', 'off'), - ('relay.password', ''), - ('relay.autoconnect', 'off'), - ('relay.lines', str(CONFIG_DEFAULT_RELAY_LINES)), - ('look.debug', 'off'), - ('look.statusbar', 'off')) - -# Default colors for WeeChat color options (option name, #rgb value) -CONFIG_DEFAULT_COLOR_OPTIONS = (('separator', '#000066'), # 0 - ('chat', '#000000'), # 1 - ('chat_time', '#999999'), # 2 - ('chat_time_delimiters', '#000000'), # 3 - ('chat_prefix_error', '#FF6633'), # 4 - ('chat_prefix_network', '#990099'), # 5 - ('chat_prefix_action', '#000000'), # 6 - ('chat_prefix_join', '#00CC00'), # 7 - ('chat_prefix_quit', '#CC0000'), # 8 - ('chat_prefix_more', '#CC00FF'), # 9 - ('chat_prefix_suffix', '#330099'), # 10 - ('chat_buffer', '#000000'), # 11 - ('chat_server', '#000000'), # 12 - ('chat_channel', '#000000'), # 13 - ('chat_nick', '#000000'), # 14 - ('chat_nick_self', '*#000000'), # 15 - ('chat_nick_other', '#000000'), # 16 - ('', '#000000'), # 17 (nick1 -- obsolete) - ('', '#000000'), # 18 (nick2 -- obsolete) - ('', '#000000'), # 19 (nick3 -- obsolete) - ('', '#000000'), # 20 (nick4 -- obsolete) - ('', '#000000'), # 21 (nick5 -- obsolete) - ('', '#000000'), # 22 (nick6 -- obsolete) - ('', '#000000'), # 23 (nick7 -- obsolete) - ('', '#000000'), # 24 (nick8 -- obsolete) - ('', '#000000'), # 25 (nick9 -- obsolete) - ('', '#000000'), # 26 (nick10 -- obsolete) - ('chat_host', '#666666'), # 27 - ('chat_delimiters', '#9999FF'), # 28 - ('chat_highlight', '#3399CC'), # 29 - ('chat_read_marker', '#000000'), # 30 - ('chat_text_found', '#000000'), # 31 - ('chat_value', '#000000'), # 32 - ('chat_prefix_buffer', '#000000'), # 33 - ('chat_tags', '#000000'), # 34 - ('chat_inactive_window', '#000000'), # 35 - ('chat_inactive_buffer', '#000000'), # 36 - ('chat_prefix_buffer_inactive_buffer', '#000000'), # 37 - ('chat_nick_offline', '#000000'), # 38 - ('chat_nick_offline_highlight', '#000000'), # 39 - ('chat_nick_prefix', '#000000'), # 40 - ('chat_nick_suffix', '#000000'), # 41 - ('emphasis', '#000000'), # 42 - ('chat_day_change', '#000000'), #43 - ) -config_color_options = [] - - -def read(): - """Read config file.""" - global config_color_options - config = ConfigParser.RawConfigParser() - if os.path.isfile(CONFIG_FILENAME): - config.read(CONFIG_FILENAME) - - # add missing sections/options - for section in CONFIG_DEFAULT_SECTIONS: - if not config.has_section(section): - config.add_section(section) - for option in reversed(CONFIG_DEFAULT_OPTIONS): - section, name = option[0].split('.', 1) - if not config.has_option(section, name): - config.set(section, name, option[1]) - section = 'color' - for option in reversed(CONFIG_DEFAULT_COLOR_OPTIONS): - if option[0] and not config.has_option(section, option[0]): - config.set(section, option[0], option[1]) - - # build list of color options - config_color_options = [] - for option in CONFIG_DEFAULT_COLOR_OPTIONS: - if option[0]: - config_color_options.append(config.get('color', option[0])) - else: - config_color_options.append('#000000') - - return config - -def write(config): - """Write config file.""" - if not os.path.exists(CONFIG_DIR): - os.mkdir(CONFIG_DIR, 0o0755) - with open(CONFIG_FILENAME, 'wb') as cfg: - config.write(cfg) - -def color_options(): - global config_color_options - return config_color_options diff --git a/src/qweechat/qweechat.py b/src/qweechat/qweechat.py deleted file mode 100755 index da491a6..0000000 --- a/src/qweechat/qweechat.py +++ /dev/null @@ -1,419 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# qweechat.py - WeeChat remote GUI using Qt toolkit -# -# Copyright (C) 2011-2014 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 . -# - -# -# This script requires WeeChat 0.3.7 or newer, running on local or remote host. -# -# History: -# -# 2011-05-27, Sébastien Helleu : -# start dev -# - -import sys, struct, traceback -import qt_compat -QtCore = qt_compat.import_module('QtCore') -QtGui = qt_compat.import_module('QtGui') -import config -import weechat.protocol as protocol -from network import Network -from connection import ConnectionDialog -from buffer import BufferListWidget, Buffer -from debug import DebugDialog -from about import AboutDialog - -NAME = 'QWeeChat' -VERSION = '0.0.1-dev' -AUTHOR = 'Sébastien Helleu' -AUTHOR_MAIL= 'flashcode@flashtux.org' -WEECHAT_SITE = 'http://weechat.org/' - -# number of lines in buffer for debug window -DEBUG_NUM_LINES = 50 - - -class MainWindow(QtGui.QMainWindow): - """Main window.""" - - def __init__(self, *args): - QtGui.QMainWindow.__init__(*(self,) + args) - - self.config = config.read() - - self.resize(1000, 600) - self.setWindowTitle(NAME) - - self.debug_dialog = None - self.debug_lines = [] - - # network - self.network = Network() - self.network.statusChanged.connect(self.network_status_changed) - self.network.messageFromWeechat.connect(self.network_message_from_weechat) - - # list of buffers - self.list_buffers = BufferListWidget() - self.list_buffers.currentRowChanged.connect(self.buffer_switch) - - # default buffer - self.buffers = [Buffer()] - self.stacked_buffers = QtGui.QStackedWidget() - self.stacked_buffers.addWidget(self.buffers[0].widget) - - # splitter with buffers + chat/input - splitter = QtGui.QSplitter() - splitter.addWidget(self.list_buffers) - splitter.addWidget(self.stacked_buffers) - - self.setCentralWidget(splitter) - - if self.config.getboolean('look', 'statusbar'): - 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', 'Debug console window', 'Ctrl+B', self.open_debug_dialog], - 'preferences' : ['preferences-other.png', 'Preferences', 'Ctrl+P', self.open_preferences_dialog], - 'about' : ['help-about.png', 'About', '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] = QtGui.QAction(QtGui.QIcon('data/icons/%s' % action[0]), name.capitalize(), self) - self.actions[name].setStatusTip(action[1]) - 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']) - menu_help = self.menu.addMenu('&Help') - menu_help.addAction(self.actions['about']) - self.network_status = QtGui.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(self.network.status_disconnected, None) - - # 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.buffers[0].widget.input.setFocus() - - # open debug dialog - if self.config.getboolean('look', 'debug'): - self.open_debug_dialog() - - # auto-connect to relay - if self.config.getboolean('relay', 'autoconnect'): - self.network.connect_weechat(self.config.get('relay', 'server'), - self.config.get('relay', 'port'), - self.config.getboolean('relay', 'ssl'), - self.config.get('relay', 'password'), - self.config.get('relay', 'lines')) - - self.show() - - def buffer_switch(self, index): - if index >= 0: - self.stacked_buffers.setCurrentIndex(index) - self.stacked_buffers.widget(index).input.setFocus() - - def buffer_input(self, full_name, text): - if self.network.is_connected(): - message = 'input %s %s\n' % (full_name, text) - self.network.send_to_weechat(message) - self.debug_display(0, '<==', message, forcecolor='#AA0000') - - def open_preferences_dialog(self): - pass # TODO - - def save_connection(self): - if self.network: - options = self.network.get_options() - for option in options.keys(): - self.config.set('relay', option, options[option]) - - def debug_display(self, *args, **kwargs): - self.debug_lines.append((args, kwargs)) - self.debug_lines = self.debug_lines[-DEBUG_NUM_LINES:] - if self.debug_dialog: - self.debug_dialog.chat.display(*args, **kwargs) - - def open_debug_dialog(self): - if not self.debug_dialog: - self.debug_dialog = DebugDialog(self) - self.debug_dialog.input.textSent.connect(self.debug_input_text_sent) - self.debug_dialog.finished.connect(self.debug_dialog_closed) - self.debug_dialog.display_lines(self.debug_lines) - self.debug_dialog.chat.scroll_bottom() - - def debug_input_text_sent(self, text): - if self.network.is_connected(): - text = str(text) - pos = text.find(')') - if text.startswith('(') and pos >= 0: - text = '(debug_%s)%s' % (text[1:pos], text[pos+1:]) - else: - text = '(debug) %s' % text - self.debug_display(0, '<==', text, forcecolor='#AA0000') - self.network.send_to_weechat(text + '\n') - - def debug_dialog_closed(self, result): - self.debug_dialog = None - - def open_about_dialog(self): - messages = ['%s %s' % (NAME, VERSION), - '© 2011-2014 %s <%s>' % (AUTHOR, AUTHOR_MAIL, AUTHOR_MAIL), - '', - 'Running with %s' % ('PySide' if qt_compat.uses_pyside else 'PyQt4'), - '', - 'WeeChat site: %s' % (WEECHAT_SITE, WEECHAT_SITE), - ''] - self.about_dialog = AboutDialog(NAME, messages, self) - - def open_connection_dialog(self): - values = {} - for option in ('server', 'port', 'ssl', 'password', 'lines'): - values[option] = self.config.get('relay', option) - self.connection_dialog = ConnectionDialog(values, self) - self.connection_dialog.dialog_buttons.accepted.connect(self.connect_weechat) - - def connect_weechat(self): - self.network.connect_weechat(self.connection_dialog.fields['server'].text(), - self.connection_dialog.fields['port'].text(), - self.connection_dialog.fields['ssl'].isChecked(), - self.connection_dialog.fields['password'].text(), - int(self.connection_dialog.fields['lines'].text())) - self.connection_dialog.close() - - def network_status_changed(self, status, extra): - if self.config.getboolean('look', 'statusbar'): - self.statusBar().showMessage(status) - self.debug_display(0, '', status, forcecolor='#0000AA') - self.network_status_set(status, extra) - - def network_status_set(self, status, extra): - pal = self.network_status.palette() - if status == self.network.status_connected: - pal.setColor(self.network_status.foregroundRole(), QtGui.QColor('green')) - else: - pal.setColor(self.network_status.foregroundRole(), QtGui.QColor('#aa0000')) - ssl = ' (SSL)' if status != self.network.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' % (icon, status.capitalize() + ssl)) - else: - self.network_status.setText(status.capitalize()) - if status == self.network.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_message_from_weechat(self, message): - self.debug_display(0, '==>', - 'message (%d bytes):\n%s' - % (len(message), protocol.hex_and_ascii(message, 20)), - forcecolor='#008800') - try: - proto = protocol.Protocol() - message = proto.decode(str(message)) - if message.uncompressed: - self.debug_display(0, '==>', - 'message uncompressed (%d bytes):\n%s' - % (message.size_uncompressed, - protocol.hex_and_ascii(message.uncompressed, 20)), - forcecolor='#008800') - self.debug_display(0, '', 'Message: %s' % message) - self.parse_message(message) - except: - print('Error while decoding message from WeeChat:\n%s' % traceback.format_exc()) - self.network.disconnect_weechat() - - def parse_message(self, message): - if message.msgid.startswith('debug'): - self.debug_display(0, '', '(debug message, ignored)') - return - if message.msgid == 'listbuffers': - for obj in message.objects: - if obj.objtype == 'hda' and obj.value['path'][-1] == 'buffer': - 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() - elif message.msgid in ('listlines', '_buffer_line_added'): - for obj in message.objects: - lines = [] - if obj.objtype == 'hda' and obj.value['path'][-1] == 'line_data': - 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((item['date'], item['prefix'], item['message'])) - if message.msgid == 'listlines': - lines.reverse() - for line in lines: - self.buffers[index[0]].widget.chat.display(*line) - elif message.msgid in ('_nicklist', 'nicklist'): - buffer_refresh = {} - for obj in message.objects: - if obj.objtype == 'hda' and obj.value['path'][-1] == 'nicklist_item': - 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() - elif message.msgid == '_nicklist_diff': - buffer_refresh = {} - for obj in message.objects: - if obj.objtype == 'hda' and obj.value['path'][-1] == 'nicklist_item': - 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: - 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() - elif message.msgid == '_buffer_opened': - for obj in message.objects: - if obj.objtype == 'hda' and obj.value['path'][-1] == 'buffer': - 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) - elif message.msgid.startswith('_buffer_'): - for obj in message.objects: - if obj.objtype == 'hda' and obj.value['path'][-1] == 'buffer': - for item in obj.value['items']: - index = [i for i, b in enumerate(self.buffers) if b.pointer() == item['__path'][0]] - if index: - 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.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) - elif message.msgid == '_upgrade': - self.network.desync_weechat() - elif message.msgid == '_upgrade_ended': - self.network.sync_weechat() - - def create_buffer(self, item): - 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): - self.buffers.insert(index, buf) - self.list_buffers.insertItem(index, '%d. %s' % (buf.data['number'], buf.data['full_name'].decode('utf-8'))) - self.stacked_buffers.insertWidget(index, buf.widget) - - def remove_buffer(self, index): - 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): - index = -1 - if next_buffer == '0x0': - index = len(self.buffers) - else: - index = [i for i, b in enumerate(self.buffers) if b.pointer() == next_buffer] - if index: - index = index[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): - self.network.disconnect_weechat() - if self.debug_dialog: - self.debug_dialog.close() - config.write(self.config) - QtGui.QMainWindow.closeEvent(self, event) - - -app = QtGui.QApplication(sys.argv) -app.setStyle(QtGui.QStyleFactory.create('Cleanlooks')) -app.setWindowIcon(QtGui.QIcon('data/icons/weechat_icon_32.png')) -main = MainWindow() -sys.exit(app.exec_())