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
							
								
								
									
										5
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						|  | @ -1,6 +1,9 @@ | |||
| # Ignored files for Git | ||||
| 
 | ||||
| *.pyc | ||||
| *.pyo | ||||
| MANIFEST | ||||
| build/* | ||||
| dist/* | ||||
| *.pyc | ||||
| qweechat.egg-info/* | ||||
| src/qweechat.egg-info/* | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ | |||
| # along with QWeeChat.  If not, see <http://www.gnu.org/licenses/>. | ||||
| # | ||||
| 
 | ||||
| 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() | ||||
|  | @ -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) | ||||
							
								
								
									
										133
									
								
								qweechat/config.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -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 <flashcode@flashtux.org> | ||||
| # | ||||
| # 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 <http://www.gnu.org/licenses/>. | ||||
| # | ||||
| 
 | ||||
| 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 | ||||
|  | @ -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) | ||||
| Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 384 B After Width: | Height: | Size: 384 B | 
| Before Width: | Height: | Size: 375 B After Width: | Height: | Size: 375 B | 
| Before Width: | Height: | Size: 813 B After Width: | Height: | Size: 813 B | 
| Before Width: | Height: | Size: 597 B After Width: | Height: | Size: 597 B | 
| Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB | 
| Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB | 
| Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB | 
| Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB | 
| Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB | 
| Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB | 
|  | @ -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: | ||||
|  | @ -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')) | ||||
|  | @ -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 | ||||
| # | ||||
							
								
								
									
										545
									
								
								qweechat/qweechat.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,545 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # qweechat.py - WeeChat remote GUI using Qt toolkit | ||||
| # | ||||
| # Copyright (C) 2011-2014 Sébastien Helleu <flashcode@flashtux.org> | ||||
| # | ||||
| # 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 <http://www.gnu.org/licenses/>. | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| 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 <flashcode@flashtux.org>: | ||||
| #     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 = ['<b>%s</b> %s' % (NAME, VERSION), | ||||
|                     '© 2011-2014 %s <<a href="mailto:%s">%s</a>>' | ||||
|                     % (AUTHOR, AUTHOR_MAIL, AUTHOR_MAIL), | ||||
|                     '', | ||||
|                     'Running with %s' % ('PySide' if qt_compat.uses_pyside | ||||
|                                          else 'PyQt4'), | ||||
|                     '', | ||||
|                     'WeeChat site: <a href="%s">%s</a>' | ||||
|                     % (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( | ||||
|                 '<img src="%s"> %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_()) | ||||
|  | @ -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: | ||||
|  | @ -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): | ||||
							
								
								
									
										54
									
								
								src/qweechat/weechat/testproto.py → qweechat/weechat/testproto.py
									
										
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						|  | @ -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 <flashcode@flashtux.org> | ||||
| # | ||||
|  | @ -21,6 +20,10 @@ | |||
| # along with QWeeChat.  If not, see <http://www.gnu.org/licenses/>. | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| 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() | ||||
							
								
								
									
										66
									
								
								setup.py
									
										
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						|  | @ -1,4 +1,3 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Copyright (C) 2011-2014 Sébastien Helleu <flashcode@flashtux.org> | ||||
|  | @ -19,35 +18,40 @@ | |||
| # along with QWeeChat.  If not, see <http://www.gnu.org/licenses/>. | ||||
| # | ||||
| 
 | ||||
| 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', | ||||
|         ] | ||||
|     } | ||||
| ) | ||||
|  |  | |||
|  | @ -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 <flashcode@flashtux.org> | ||||
| # | ||||
| # 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 <http://www.gnu.org/licenses/>. | ||||
| # | ||||
| 
 | ||||
| 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 | ||||
|  | @ -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 <flashcode@flashtux.org> | ||||
| # | ||||
| # 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 <http://www.gnu.org/licenses/>. | ||||
| # | ||||
| 
 | ||||
| # | ||||
| # This script requires WeeChat 0.3.7 or newer, running on local or remote host. | ||||
| # | ||||
| # History: | ||||
| # | ||||
| # 2011-05-27, Sébastien Helleu <flashcode@flashtux.org>: | ||||
| #     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 = ['<b>%s</b> %s' % (NAME, VERSION), | ||||
|                     '© 2011-2014 %s <<a href="mailto:%s">%s</a>>' % (AUTHOR, AUTHOR_MAIL, AUTHOR_MAIL), | ||||
|                     '', | ||||
|                     'Running with %s' % ('PySide' if qt_compat.uses_pyside else 'PyQt4'), | ||||
|                     '', | ||||
|                     'WeeChat site: <a href="%s">%s</a>' % (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('<img src="data/icons/%s"> %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_()) | ||||
 Sébastien Helleu
						Sébastien Helleu