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_())