]> jfr.im git - irc/weechat/qweechat.git/blame - src/qweechat/qweechat.py
Add signals "upgrade" and "upgrade_ended"
[irc/weechat/qweechat.git] / src / qweechat / qweechat.py
CommitLineData
7dcf23b1
SH
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3#
beaa8775 4# Copyright (C) 2011-2012 Sebastien Helleu <flashcode@flashtux.org>
7dcf23b1
SH
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
32import sys, struct
33import qt_compat
34QtCore = qt_compat.import_module('QtCore')
35QtGui = qt_compat.import_module('QtGui')
36import config
37import weechat.protocol as protocol
7dcf23b1
SH
38from network import Network
39from connection import ConnectionDialog
40from buffer import BufferListWidget, Buffer
41from debug import DebugDialog
42from about import AboutDialog
43
f4848c2e 44NAME = 'QWeeChat'
8320f3a8 45VERSION = '0.0.1-dev'
7dcf23b1
SH
46AUTHOR = 'Sébastien Helleu'
47AUTHOR_MAIL= 'flashcode@flashtux.org'
48WEECHAT_SITE = 'http://www.weechat.org/'
49
cc80cd81
SH
50# number of lines in buffer for debug window
51DEBUG_NUM_LINES = 50
52
7dcf23b1
SH
53
54class MainWindow(QtGui.QMainWindow):
55 """Main window."""
56
57 def __init__(self, *args):
67ae3204 58 QtGui.QMainWindow.__init__(*(self,) + args)
7dcf23b1
SH
59
60 self.config = config.read()
61
7dcf23b1
SH
62 self.resize(1000, 600)
63 self.setWindowTitle(NAME)
64
cc80cd81
SH
65 self.debug_dialog = None
66 self.debug_lines = []
67
7dcf23b1
SH
68 # network
69 self.network = Network()
70 self.network.statusChanged.connect(self.network_status_changed)
71 self.network.messageFromWeechat.connect(self.network_message_from_weechat)
72
73 # list of buffers
74 self.list_buffers = BufferListWidget()
75 self.list_buffers.currentRowChanged.connect(self.buffer_switch)
76
77 # default buffer
78 self.buffers = [Buffer()]
79 self.stacked_buffers = QtGui.QStackedWidget()
80 self.stacked_buffers.addWidget(self.buffers[0].widget)
81
82 # splitter with buffers + chat/input
83 splitter = QtGui.QSplitter()
84 splitter.addWidget(self.list_buffers)
85 splitter.addWidget(self.stacked_buffers)
86
87 self.setCentralWidget(splitter)
88
89 if self.config.getboolean('look', 'statusbar'):
90 self.statusBar().visible = True
91
92 # actions for menu and toolbar
93 actions_def = {'connect' : ['network-connect.png', 'Connect to WeeChat', 'Ctrl+O', self.open_connection_dialog],
94 'disconnect' : ['network-disconnect.png', 'Disconnect from WeeChat', 'Ctrl+D', self.network.disconnect_weechat],
95 'debug' : ['edit-find.png', 'Debug console window', 'Ctrl+B', self.open_debug_dialog],
96 'preferences': ['preferences-other.png', 'Preferences', 'Ctrl+P', self.open_preferences_dialog],
97 'about' : ['help-about.png', 'About', 'Ctrl+H', self.open_about_dialog],
98 'quit' : ['application-exit.png', 'Quit application', 'Ctrl+Q', self.close],
99 }
100 self.actions = {}
773ee7bb 101 for name, action in list(actions_def.items()):
7dcf23b1
SH
102 self.actions[name] = QtGui.QAction(QtGui.QIcon('data/icons/%s' % action[0]), name.capitalize(), self)
103 self.actions[name].setStatusTip(action[1])
104 self.actions[name].setShortcut(action[2])
105 self.actions[name].triggered.connect(action[3])
106
107 # menu
108 self.menu = self.menuBar()
109 menu_file = self.menu.addMenu('&File')
110 menu_file.addActions([self.actions['connect'], self.actions['disconnect'],
111 self.actions['preferences'], self.actions['quit']])
112 menu_window = self.menu.addMenu('&Window')
113 menu_window.addAction(self.actions['debug'])
114 menu_help = self.menu.addMenu('&Help')
115 menu_help.addAction(self.actions['about'])
116 self.network_status = QtGui.QLabel()
117 self.network_status.setFixedHeight(20)
118 self.network_status.setFixedWidth(200)
119 self.network_status.setContentsMargins(0, 0, 10, 0)
120 self.network_status.setAlignment(QtCore.Qt.AlignRight)
121 if hasattr(self.menu, 'setCornerWidget'):
122 self.menu.setCornerWidget(self.network_status, QtCore.Qt.TopRightCorner)
123 self.network_status_set(self.network.status_disconnected, None)
124
125 # toolbar
126 toolbar = self.addToolBar('toolBar')
127 toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
128 toolbar.addActions([self.actions['connect'], self.actions['disconnect'],
129 self.actions['debug'], self.actions['preferences'],
130 self.actions['about'], self.actions['quit']])
131
132 self.buffers[0].widget.input.setFocus()
133
134 # open debug dialog
135 if self.config.getboolean('look', 'debug'):
136 self.open_debug_dialog()
137
138 # auto-connect to relay
139 if self.config.getboolean('relay', 'autoconnect'):
140 self.network.connect_weechat(self.config.get('relay', 'server'),
141 self.config.get('relay', 'port'),
142 self.config.get('relay', 'password'))
143
144 self.show()
145
146 def buffer_switch(self, index):
147 if index >= 0:
148 self.stacked_buffers.setCurrentIndex(index)
149 self.stacked_buffers.widget(index).input.setFocus()
150
151 def buffer_input(self, full_name, text):
152 if self.network.is_connected():
153 message = 'input %s %s\n' % (full_name, text)
154 self.network.send_to_weechat(message)
3a5ec0c1 155 self.debug_display(0, '<==', message, forcecolor='#AA0000')
7dcf23b1
SH
156
157 def open_preferences_dialog(self):
158 pass # TODO
159
cc80cd81
SH
160 def debug_display(self, *args, **kwargs):
161 self.debug_lines.append((args, kwargs))
162 self.debug_lines = self.debug_lines[-DEBUG_NUM_LINES:]
163 if self.debug_dialog:
164 self.debug_dialog.chat.display(*args, **kwargs)
165
7dcf23b1 166 def open_debug_dialog(self):
cc80cd81
SH
167 if not self.debug_dialog:
168 self.debug_dialog = DebugDialog(self)
169 self.debug_dialog.input.textSent.connect(self.debug_input_text_sent)
170 self.debug_dialog.finished.connect(self.debug_dialog_closed)
171 self.debug_dialog.display_lines(self.debug_lines)
172 self.debug_dialog.chat.scroll_bottom()
7dcf23b1
SH
173
174 def debug_input_text_sent(self, text):
175 if self.network.is_connected():
176 text = str(text)
177 pos = text.find(')')
178 if text.startswith('(') and pos >= 0:
179 text = '(debug_%s)%s' % (text[1:pos], text[pos+1:])
180 else:
181 text = '(debug) %s' % text
3a5ec0c1 182 self.debug_display(0, '<==', text, forcecolor='#AA0000')
7dcf23b1 183 self.network.send_to_weechat(text + '\n')
7dcf23b1
SH
184
185 def debug_dialog_closed(self, result):
cc80cd81 186 self.debug_dialog = None
7dcf23b1
SH
187
188 def open_about_dialog(self):
189 messages = ['<b>%s</b> %s' % (NAME, VERSION),
190 '&copy; 2011 %s &lt;<a href="mailto:%s">%s</a>&gt;' % (AUTHOR, AUTHOR_MAIL, AUTHOR_MAIL),
191 '',
192 'WeeChat site: <a href="%s">%s</a>' % (WEECHAT_SITE, WEECHAT_SITE),
193 '']
194 self.about_dialog = AboutDialog(NAME, messages, self)
195
196 def open_connection_dialog(self):
197 values = {}
198 for option in ('server', 'port', 'password'):
199 values[option] = self.config.get('relay', option)
200 self.connection_dialog = ConnectionDialog(values, self)
201 self.connection_dialog.dialog_buttons.accepted.connect(self.connect_weechat)
202
203 def connect_weechat(self):
204 self.network.connect_weechat(self.connection_dialog.fields['server'].text(),
205 self.connection_dialog.fields['port'].text(),
206 self.connection_dialog.fields['password'].text())
207 self.connection_dialog.close()
208
209 def network_status_changed(self, status, extra):
210 if self.config.getboolean('look', 'statusbar'):
211 self.statusBar().showMessage(status)
3a5ec0c1 212 self.debug_display(0, '', status, forcecolor='#0000AA')
7dcf23b1
SH
213 self.network_status_set(status, extra)
214
215 def network_status_set(self, status, extra):
216 pal = self.network_status.palette()
217 if self.network.is_connected():
218 pal.setColor(self.network_status.foregroundRole(), QtGui.QColor('green'))
219 else:
220 pal.setColor(self.network_status.foregroundRole(), QtGui.QColor('#aa0000'))
221 self.network_status.setPalette(pal)
222 icon = self.network.status_icon(status)
223 if icon:
224 self.network_status.setText('<img src="data/icons/%s"> %s' % (icon, status.capitalize()))
225 else:
226 self.network_status.setText(status.capitalize())
227 if status == self.network.status_disconnected:
228 self.actions['connect'].setEnabled(True)
229 self.actions['disconnect'].setEnabled(False)
230 else:
231 self.actions['connect'].setEnabled(False)
232 self.actions['disconnect'].setEnabled(True)
233
234 def network_message_from_weechat(self, message):
cc80cd81
SH
235 self.debug_display(0, '==>',
236 'message (%d bytes):\n%s'
237 % (len(message), protocol.hex_and_ascii(message, 20)),
3a5ec0c1 238 forcecolor='#008800')
7dcf23b1
SH
239 proto = protocol.Protocol()
240 message = proto.decode(str(message))
241 if message.uncompressed:
cc80cd81
SH
242 self.debug_display(0, '==>',
243 'message uncompressed (%d bytes):\n%s'
244 % (message.size_uncompressed,
245 protocol.hex_and_ascii(message.uncompressed, 20)),
3a5ec0c1 246 forcecolor='#008800')
cc80cd81 247 self.debug_display(0, '', 'Message: %s' % message)
7dcf23b1
SH
248 self.parse_message(message)
249
250 def parse_message(self, message):
251 if message.msgid.startswith('debug'):
cc80cd81 252 self.debug_display(0, '', '(debug message, ignored)')
7dcf23b1
SH
253 return
254 if message.msgid == 'listbuffers':
255 for obj in message.objects:
256 if obj.objtype == 'hda' and obj.value['path'][-1] == 'buffer':
257 self.list_buffers.clear()
258 while self.stacked_buffers.count() > 0:
259 buf = self.stacked_buffers.widget(0)
260 self.stacked_buffers.removeWidget(buf)
261 self.buffers = []
262 for item in obj.value['items']:
ca67c3a7
SH
263 buf = self.create_buffer(item)
264 self.insert_buffer(len(self.buffers), buf)
7dcf23b1
SH
265 self.list_buffers.setCurrentRow(0)
266 self.buffers[0].widget.input.setFocus()
ca67c3a7 267 elif message.msgid in ('listlines', '_buffer_line_added'):
7dcf23b1
SH
268 for obj in message.objects:
269 if obj.objtype == 'hda' and obj.value['path'][-1] == 'line_data':
270 for item in obj.value['items']:
ca67c3a7
SH
271 if message.msgid == 'listlines':
272 ptrbuf = item['__path'][0]
273 else:
274 ptrbuf = item['buffer']
275 index = [i for i, b in enumerate(self.buffers) if b.pointer() == ptrbuf]
276 if index:
277 self.buffers[index[0]].widget.chat.display(item['date'],
3a5ec0c1
SH
278 item['prefix'],
279 item['message'])
ca67c3a7
SH
280 elif message.msgid in ('_nicklist', 'nicklist'):
281 buffer_nicklist = {}
7dcf23b1 282 for obj in message.objects:
ca67c3a7 283 if obj.objtype == 'hda' and obj.value['path'][-1] == 'nicklist_item':
7dcf23b1 284 for item in obj.value['items']:
ca67c3a7
SH
285 index = [i for i, b in enumerate(self.buffers) if b.pointer() == item['__path'][0]]
286 if index:
287 if not item['__path'][0] in buffer_nicklist:
288 self.buffers[index[0]].remove_all_nicks()
289 buffer_nicklist[item['__path'][0]] = True
290 if not item['group'] and item['visible']:
291 self.buffers[index[0]].add_nick(item['prefix'], item['name'])
292 elif message.msgid == '_buffer_opened':
293 for obj in message.objects:
294 if obj.objtype == 'hda' and obj.value['path'][-1] == 'buffer':
295 for item in obj.value['items']:
296 buf = self.create_buffer(item)
297 index = self.find_buffer_index_for_insert(item['next_buffer'])
298 self.insert_buffer(index, buf)
299 elif message.msgid.startswith('_buffer_'):
300 for obj in message.objects:
301 if obj.objtype == 'hda' and obj.value['path'][-1] == 'buffer':
302 for item in obj.value['items']:
303 index = [i for i, b in enumerate(self.buffers) if b.pointer() == item['__path'][0]]
304 if index:
305 index = index[0]
97936653
SH
306 if message.msgid == '_buffer_type_changed':
307 self.buffers[index].data['type'] = item['type']
308 elif message.msgid in ('_buffer_moved', '_buffer_merged', '_buffer_unmerged'):
ca67c3a7
SH
309 buf = self.buffers[index]
310 buf.data['number'] = item['number']
311 self.remove_buffer(index)
312 index2 = self.find_buffer_index_for_insert(item['next_buffer'])
313 self.insert_buffer(index2, buf)
314 elif message.msgid == '_buffer_renamed':
315 self.buffers[index].data['full_name'] = item['full_name']
316 self.buffers[index].data['short_name'] = item['short_name']
317 elif message.msgid == '_buffer_title_changed':
318 self.buffers[index].data['title'] = item['title']
c728febd
SH
319 self.buffers[index].update_title()
320 elif message.msgid.startswith('_buffer_localvar_'):
321 self.buffers[index].data['local_variables'] = item['local_variables']
322 self.buffers[index].update_prompt()
ca67c3a7
SH
323 elif message.msgid == '_buffer_closing':
324 self.remove_buffer(index)
beaa8775
SH
325 elif message.msgid == '_upgrade':
326 self.network.desync_weechat()
327 elif message.msgid == '_upgrade_ended':
328 self.network.sync_weechat()
ca67c3a7
SH
329
330 def create_buffer(self, item):
331 buf = Buffer(item)
332 buf.bufferInput.connect(self.buffer_input)
333 buf.widget.input.bufferSwitchPrev.connect(self.list_buffers.switch_prev_buffer)
334 buf.widget.input.bufferSwitchNext.connect(self.list_buffers.switch_next_buffer)
335 return buf
336
337 def insert_buffer(self, index, buf):
338 self.buffers.insert(index, buf)
339 self.list_buffers.insertItem(index, '%d. %s' % (buf.data['number'], buf.data['full_name']))
340 self.stacked_buffers.insertWidget(index, buf.widget)
341
342 def remove_buffer(self, index):
343 if self.list_buffers.currentRow == index and index > 0:
344 self.list_buffers.setCurrentRow(index - 1)
345 self.list_buffers.takeItem(index)
346 self.stacked_buffers.removeWidget(self.stacked_buffers.widget(index))
347 self.buffers.pop(index)
348
349 def find_buffer_index_for_insert(self, next_buffer):
350 index = -1
351 if next_buffer == '0x0':
352 index = len(self.buffers)
353 else:
354 index = [i for i, b in enumerate(self.buffers) if b.pointer() == next_buffer]
355 if index:
356 index = index[0]
357 if index < 0:
358 print('Warning: unable to find position for buffer, using end of list by default')
359 index = len(self.buffers)
360 return index
7dcf23b1
SH
361
362 def closeEvent(self, event):
363 self.network.disconnect_weechat()
cc80cd81
SH
364 if self.debug_dialog:
365 self.debug_dialog.close()
7dcf23b1
SH
366 config.write(self.config)
367 QtGui.QMainWindow.closeEvent(self, event)
368
369
370app = QtGui.QApplication(sys.argv)
371app.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))
93865c21 372app.setWindowIcon(QtGui.QIcon('data/icons/weechat_icon_32.png'))
7dcf23b1
SH
373main = MainWindow()
374sys.exit(app.exec_())