]> jfr.im git - irc/weechat/qweechat.git/blob - src/qweechat/qweechat.py
Fix crash with PySide when sending unicode data on socket
[irc/weechat/qweechat.git] / src / qweechat / qweechat.py
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 #
4 # Copyright (C) 2011 Sebastien Helleu <flashcode@flashtux.org>
5 #
6 # This file is part of QWeeChat, a Qt remote GUI for WeeChat.
7 #
8 # QWeeChat is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # QWeeChat is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
20 #
21
22 #
23 # QWeeChat - WeeChat remote GUI using Qt toolkit.
24 # (this script requires WeeChat 0.3.7 or newer, running on local or remote host)
25 #
26 # History:
27 #
28 # 2011-05-27, Sebastien Helleu <flashcode@flashtux.org>:
29 # start dev
30 #
31
32 import sys, struct
33 import qt_compat
34 QtCore = qt_compat.import_module('QtCore')
35 QtGui = qt_compat.import_module('QtGui')
36 import config
37 import weechat.protocol as protocol
38 import weechat.color as color
39 from network import Network
40 from connection import ConnectionDialog
41 from buffer import BufferListWidget, Buffer
42 from debug import DebugDialog
43 from about import AboutDialog
44
45 NAME = 'QWeeChat'
46 VERSION = '0.1-dev'
47 AUTHOR = 'Sébastien Helleu'
48 AUTHOR_MAIL= 'flashcode@flashtux.org'
49 WEECHAT_SITE = 'http://www.weechat.org/'
50
51
52 class MainWindow(QtGui.QMainWindow):
53 """Main window."""
54
55 def __init__(self, *args):
56 apply(QtGui.QMainWindow.__init__, (self,) + args)
57
58 self.config = config.read()
59
60 self.debug_dialog = DebugDialog(self)
61 self.debug_dialog.input.textSent.connect(self.debug_input_text_sent)
62 self.debug_dialog.finished.connect(self.debug_dialog_closed)
63
64 self.resize(1000, 600)
65 self.setWindowTitle(NAME)
66
67 # network
68 self.network = Network()
69 self.network.statusChanged.connect(self.network_status_changed)
70 self.network.messageFromWeechat.connect(self.network_message_from_weechat)
71
72 # list of buffers
73 self.list_buffers = BufferListWidget()
74 self.list_buffers.currentRowChanged.connect(self.buffer_switch)
75
76 # default buffer
77 self.buffers = [Buffer()]
78 self.stacked_buffers = QtGui.QStackedWidget()
79 self.stacked_buffers.addWidget(self.buffers[0].widget)
80
81 # splitter with buffers + chat/input
82 splitter = QtGui.QSplitter()
83 splitter.addWidget(self.list_buffers)
84 splitter.addWidget(self.stacked_buffers)
85
86 self.setCentralWidget(splitter)
87
88 if self.config.getboolean('look', 'statusbar'):
89 self.statusBar().visible = True
90
91 # actions for menu and toolbar
92 actions_def = {'connect' : ['network-connect.png', 'Connect to WeeChat', 'Ctrl+O', self.open_connection_dialog],
93 'disconnect' : ['network-disconnect.png', 'Disconnect from WeeChat', 'Ctrl+D', self.network.disconnect_weechat],
94 'debug' : ['edit-find.png', 'Debug console window', 'Ctrl+B', self.open_debug_dialog],
95 'preferences': ['preferences-other.png', 'Preferences', 'Ctrl+P', self.open_preferences_dialog],
96 'about' : ['help-about.png', 'About', 'Ctrl+H', self.open_about_dialog],
97 'quit' : ['application-exit.png', 'Quit application', 'Ctrl+Q', self.close],
98 }
99 self.actions = {}
100 for name, action in actions_def.iteritems():
101 self.actions[name] = QtGui.QAction(QtGui.QIcon('data/icons/%s' % action[0]), name.capitalize(), self)
102 self.actions[name].setStatusTip(action[1])
103 self.actions[name].setShortcut(action[2])
104 self.actions[name].triggered.connect(action[3])
105
106 # menu
107 self.menu = self.menuBar()
108 menu_file = self.menu.addMenu('&File')
109 menu_file.addActions([self.actions['connect'], self.actions['disconnect'],
110 self.actions['preferences'], self.actions['quit']])
111 menu_window = self.menu.addMenu('&Window')
112 menu_window.addAction(self.actions['debug'])
113 menu_help = self.menu.addMenu('&Help')
114 menu_help.addAction(self.actions['about'])
115 self.network_status = QtGui.QLabel()
116 self.network_status.setFixedHeight(20)
117 self.network_status.setFixedWidth(200)
118 self.network_status.setContentsMargins(0, 0, 10, 0)
119 self.network_status.setAlignment(QtCore.Qt.AlignRight)
120 if hasattr(self.menu, 'setCornerWidget'):
121 self.menu.setCornerWidget(self.network_status, QtCore.Qt.TopRightCorner)
122 self.network_status_set(self.network.status_disconnected, None)
123
124 # toolbar
125 toolbar = self.addToolBar('toolBar')
126 toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
127 toolbar.addActions([self.actions['connect'], self.actions['disconnect'],
128 self.actions['debug'], self.actions['preferences'],
129 self.actions['about'], self.actions['quit']])
130
131 self.buffers[0].widget.input.setFocus()
132
133 # open debug dialog
134 if self.config.getboolean('look', 'debug'):
135 self.open_debug_dialog()
136
137 # auto-connect to relay
138 if self.config.getboolean('relay', 'autoconnect'):
139 self.network.connect_weechat(self.config.get('relay', 'server'),
140 self.config.get('relay', 'port'),
141 self.config.get('relay', 'password'))
142
143 self.show()
144
145 def buffer_switch(self, index):
146 if index >= 0:
147 self.stacked_buffers.setCurrentIndex(index)
148 self.stacked_buffers.widget(index).input.setFocus()
149
150 def buffer_input(self, full_name, text):
151 if self.network.is_connected():
152 message = 'input %s %s\n' % (full_name, text)
153 self.network.send_to_weechat(message)
154 self.debug_dialog.chat.display(0, '<==', message, color='red')
155
156 def open_preferences_dialog(self):
157 pass # TODO
158
159 def open_debug_dialog(self):
160 self.debug_dialog.chat.scroll_bottom()
161 self.debug_dialog.show()
162
163 def debug_input_text_sent(self, text):
164 if self.network.is_connected():
165 text = str(text)
166 pos = text.find(')')
167 if text.startswith('(') and pos >= 0:
168 text = '(debug_%s)%s' % (text[1:pos], text[pos+1:])
169 else:
170 text = '(debug) %s' % text
171 self.network.send_to_weechat(text + '\n')
172 self.debug_dialog.chat.display(0, '<==', text, color='red')
173
174 def debug_dialog_closed(self, result):
175 self.debug_dialog.hide()
176
177 def open_about_dialog(self):
178 messages = ['<b>%s</b> %s' % (NAME, VERSION),
179 '&copy; 2011 %s &lt;<a href="mailto:%s">%s</a>&gt;' % (AUTHOR, AUTHOR_MAIL, AUTHOR_MAIL),
180 '',
181 'WeeChat site: <a href="%s">%s</a>' % (WEECHAT_SITE, WEECHAT_SITE),
182 '']
183 self.about_dialog = AboutDialog(NAME, messages, self)
184
185 def open_connection_dialog(self):
186 values = {}
187 for option in ('server', 'port', 'password'):
188 values[option] = self.config.get('relay', option)
189 self.connection_dialog = ConnectionDialog(values, self)
190 self.connection_dialog.dialog_buttons.accepted.connect(self.connect_weechat)
191
192 def connect_weechat(self):
193 self.network.connect_weechat(self.connection_dialog.fields['server'].text(),
194 self.connection_dialog.fields['port'].text(),
195 self.connection_dialog.fields['password'].text())
196 self.connection_dialog.close()
197
198 def network_status_changed(self, status, extra):
199 if self.config.getboolean('look', 'statusbar'):
200 self.statusBar().showMessage(status)
201 self.debug_dialog.chat.display(0, '', status, color='blue')
202 self.network_status_set(status, extra)
203
204 def network_status_set(self, status, extra):
205 pal = self.network_status.palette()
206 if self.network.is_connected():
207 pal.setColor(self.network_status.foregroundRole(), QtGui.QColor('green'))
208 else:
209 pal.setColor(self.network_status.foregroundRole(), QtGui.QColor('#aa0000'))
210 self.network_status.setPalette(pal)
211 icon = self.network.status_icon(status)
212 if icon:
213 self.network_status.setText('<img src="data/icons/%s"> %s' % (icon, status.capitalize()))
214 else:
215 self.network_status.setText(status.capitalize())
216 if status == self.network.status_disconnected:
217 self.actions['connect'].setEnabled(True)
218 self.actions['disconnect'].setEnabled(False)
219 else:
220 self.actions['connect'].setEnabled(False)
221 self.actions['disconnect'].setEnabled(True)
222
223 def network_message_from_weechat(self, message):
224 self.debug_dialog.chat.display(0, '==>',
225 'message (%d bytes):\n%s'
226 % (len(message), protocol.hex_and_ascii(message, 20)),
227 color='green')
228 proto = protocol.Protocol()
229 message = proto.decode(str(message))
230 if message.uncompressed:
231 self.debug_dialog.chat.display(0, '==>',
232 'message uncompressed (%d bytes):\n%s'
233 % (message.size_uncompressed,
234 protocol.hex_and_ascii(message.uncompressed, 20)),
235 color='green')
236 self.debug_dialog.chat.display(0, '', 'Message: %s' % message)
237 self.parse_message(message)
238
239 def parse_message(self, message):
240 if message.msgid.startswith('debug'):
241 self.debug_dialog.chat.display(0, '', '(debug message, ignored)')
242 return
243 if message.msgid == 'listbuffers':
244 for obj in message.objects:
245 if obj.objtype == 'hda' and obj.value['path'][-1] == 'buffer':
246 self.list_buffers.clear()
247 while self.stacked_buffers.count() > 0:
248 buf = self.stacked_buffers.widget(0)
249 self.stacked_buffers.removeWidget(buf)
250 self.buffers = []
251 for item in obj.value['items']:
252 self.list_buffers.addItem('%d. %s' % (item['number'], item['full_name']))
253 self.buffers.append(Buffer(item))
254 self.stacked_buffers.addWidget(self.buffers[-1].widget)
255 self.buffers[-1].bufferInput.connect(self.buffer_input)
256 self.buffers[-1].widget.input.bufferSwitchPrev.connect(self.list_buffers.switch_prev_buffer)
257 self.buffers[-1].widget.input.bufferSwitchNext.connect(self.list_buffers.switch_next_buffer)
258 self.list_buffers.setCurrentRow(0)
259 self.buffers[0].widget.input.setFocus()
260 elif message.msgid == 'listlines':
261 for obj in message.objects:
262 if obj.objtype == 'hda' and obj.value['path'][-1] == 'line_data':
263 for item in obj.value['items']:
264 pointer = item['__path'][0]
265 buf = filter(lambda b: b.pointer() == pointer, self.buffers)
266 if buf:
267 buf[0].widget.chat.display(item['date'],
268 color.remove(item['prefix']),
269 color.remove(item['message']))
270 elif message.msgid == 'nicklist':
271 for obj in message.objects:
272 if obj.objtype == 'hda' and obj.value['path'][-1] == 'nick_group':
273 for item in obj.value['items']:
274 if not item['group'] and item['visible']:
275 pointer = item['__path'][0]
276 buf = filter(lambda b: b.pointer() == pointer, self.buffers)
277 if buf:
278 buf[0].add_nick(item['prefix'], item['name'])
279
280 def closeEvent(self, event):
281 self.network.disconnect_weechat()
282 self.debug_dialog.close()
283 config.write(self.config)
284 QtGui.QMainWindow.closeEvent(self, event)
285
286
287 app = QtGui.QApplication(sys.argv)
288 app.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))
289 main = MainWindow()
290 sys.exit(app.exec_())