]> jfr.im git - irc/weechat/qweechat.git/blame - src/qweechat/qweechat.py
Add auto-resize of nicklist on insertItem/clear, set NoFocus on title, add bullets...
[irc/weechat/qweechat.git] / src / qweechat / qweechat.py
CommitLineData
7dcf23b1
SH
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
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
38import weechat.color as color
39from network import Network
40from connection import ConnectionDialog
41from buffer import BufferListWidget, Buffer
42from debug import DebugDialog
43from about import AboutDialog
44
f4848c2e 45NAME = 'QWeeChat'
7dcf23b1
SH
46VERSION = '0.1-dev'
47AUTHOR = 'Sébastien Helleu'
48AUTHOR_MAIL= 'flashcode@flashtux.org'
49WEECHAT_SITE = 'http://www.weechat.org/'
50
cc80cd81
SH
51# number of lines in buffer for debug window
52DEBUG_NUM_LINES = 50
53
7dcf23b1
SH
54
55class MainWindow(QtGui.QMainWindow):
56 """Main window."""
57
58 def __init__(self, *args):
59 apply(QtGui.QMainWindow.__init__, (self,) + args)
60
61 self.config = config.read()
62
7dcf23b1
SH
63 self.resize(1000, 600)
64 self.setWindowTitle(NAME)
65
cc80cd81
SH
66 self.debug_dialog = None
67 self.debug_lines = []
68
7dcf23b1
SH
69 # network
70 self.network = Network()
71 self.network.statusChanged.connect(self.network_status_changed)
72 self.network.messageFromWeechat.connect(self.network_message_from_weechat)
73
74 # list of buffers
75 self.list_buffers = BufferListWidget()
76 self.list_buffers.currentRowChanged.connect(self.buffer_switch)
77
78 # default buffer
79 self.buffers = [Buffer()]
80 self.stacked_buffers = QtGui.QStackedWidget()
81 self.stacked_buffers.addWidget(self.buffers[0].widget)
82
83 # splitter with buffers + chat/input
84 splitter = QtGui.QSplitter()
85 splitter.addWidget(self.list_buffers)
86 splitter.addWidget(self.stacked_buffers)
87
88 self.setCentralWidget(splitter)
89
90 if self.config.getboolean('look', 'statusbar'):
91 self.statusBar().visible = True
92
93 # actions for menu and toolbar
94 actions_def = {'connect' : ['network-connect.png', 'Connect to WeeChat', 'Ctrl+O', self.open_connection_dialog],
95 'disconnect' : ['network-disconnect.png', 'Disconnect from WeeChat', 'Ctrl+D', self.network.disconnect_weechat],
96 'debug' : ['edit-find.png', 'Debug console window', 'Ctrl+B', self.open_debug_dialog],
97 'preferences': ['preferences-other.png', 'Preferences', 'Ctrl+P', self.open_preferences_dialog],
98 'about' : ['help-about.png', 'About', 'Ctrl+H', self.open_about_dialog],
99 'quit' : ['application-exit.png', 'Quit application', 'Ctrl+Q', self.close],
100 }
101 self.actions = {}
102 for name, action in actions_def.iteritems():
103 self.actions[name] = QtGui.QAction(QtGui.QIcon('data/icons/%s' % action[0]), name.capitalize(), self)
104 self.actions[name].setStatusTip(action[1])
105 self.actions[name].setShortcut(action[2])
106 self.actions[name].triggered.connect(action[3])
107
108 # menu
109 self.menu = self.menuBar()
110 menu_file = self.menu.addMenu('&File')
111 menu_file.addActions([self.actions['connect'], self.actions['disconnect'],
112 self.actions['preferences'], self.actions['quit']])
113 menu_window = self.menu.addMenu('&Window')
114 menu_window.addAction(self.actions['debug'])
115 menu_help = self.menu.addMenu('&Help')
116 menu_help.addAction(self.actions['about'])
117 self.network_status = QtGui.QLabel()
118 self.network_status.setFixedHeight(20)
119 self.network_status.setFixedWidth(200)
120 self.network_status.setContentsMargins(0, 0, 10, 0)
121 self.network_status.setAlignment(QtCore.Qt.AlignRight)
122 if hasattr(self.menu, 'setCornerWidget'):
123 self.menu.setCornerWidget(self.network_status, QtCore.Qt.TopRightCorner)
124 self.network_status_set(self.network.status_disconnected, None)
125
126 # toolbar
127 toolbar = self.addToolBar('toolBar')
128 toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
129 toolbar.addActions([self.actions['connect'], self.actions['disconnect'],
130 self.actions['debug'], self.actions['preferences'],
131 self.actions['about'], self.actions['quit']])
132
133 self.buffers[0].widget.input.setFocus()
134
135 # open debug dialog
136 if self.config.getboolean('look', 'debug'):
137 self.open_debug_dialog()
138
139 # auto-connect to relay
140 if self.config.getboolean('relay', 'autoconnect'):
141 self.network.connect_weechat(self.config.get('relay', 'server'),
142 self.config.get('relay', 'port'),
143 self.config.get('relay', 'password'))
144
145 self.show()
146
147 def buffer_switch(self, index):
148 if index >= 0:
149 self.stacked_buffers.setCurrentIndex(index)
150 self.stacked_buffers.widget(index).input.setFocus()
151
152 def buffer_input(self, full_name, text):
153 if self.network.is_connected():
154 message = 'input %s %s\n' % (full_name, text)
155 self.network.send_to_weechat(message)
cc80cd81 156 self.debug_display(0, '<==', message, color='red')
7dcf23b1
SH
157
158 def open_preferences_dialog(self):
159 pass # TODO
160
cc80cd81
SH
161 def debug_display(self, *args, **kwargs):
162 self.debug_lines.append((args, kwargs))
163 self.debug_lines = self.debug_lines[-DEBUG_NUM_LINES:]
164 if self.debug_dialog:
165 self.debug_dialog.chat.display(*args, **kwargs)
166
7dcf23b1 167 def open_debug_dialog(self):
cc80cd81
SH
168 if not self.debug_dialog:
169 self.debug_dialog = DebugDialog(self)
170 self.debug_dialog.input.textSent.connect(self.debug_input_text_sent)
171 self.debug_dialog.finished.connect(self.debug_dialog_closed)
172 self.debug_dialog.display_lines(self.debug_lines)
173 self.debug_dialog.chat.scroll_bottom()
7dcf23b1
SH
174
175 def debug_input_text_sent(self, text):
176 if self.network.is_connected():
177 text = str(text)
178 pos = text.find(')')
179 if text.startswith('(') and pos >= 0:
180 text = '(debug_%s)%s' % (text[1:pos], text[pos+1:])
181 else:
182 text = '(debug) %s' % text
183 self.network.send_to_weechat(text + '\n')
cc80cd81 184 self.debug_display(0, '<==', text, color='red')
7dcf23b1
SH
185
186 def debug_dialog_closed(self, result):
cc80cd81 187 self.debug_dialog = None
7dcf23b1
SH
188
189 def open_about_dialog(self):
190 messages = ['<b>%s</b> %s' % (NAME, VERSION),
191 '&copy; 2011 %s &lt;<a href="mailto:%s">%s</a>&gt;' % (AUTHOR, AUTHOR_MAIL, AUTHOR_MAIL),
192 '',
193 'WeeChat site: <a href="%s">%s</a>' % (WEECHAT_SITE, WEECHAT_SITE),
194 '']
195 self.about_dialog = AboutDialog(NAME, messages, self)
196
197 def open_connection_dialog(self):
198 values = {}
199 for option in ('server', 'port', 'password'):
200 values[option] = self.config.get('relay', option)
201 self.connection_dialog = ConnectionDialog(values, self)
202 self.connection_dialog.dialog_buttons.accepted.connect(self.connect_weechat)
203
204 def connect_weechat(self):
205 self.network.connect_weechat(self.connection_dialog.fields['server'].text(),
206 self.connection_dialog.fields['port'].text(),
207 self.connection_dialog.fields['password'].text())
208 self.connection_dialog.close()
209
210 def network_status_changed(self, status, extra):
211 if self.config.getboolean('look', 'statusbar'):
212 self.statusBar().showMessage(status)
cc80cd81 213 self.debug_display(0, '', status, color='blue')
7dcf23b1
SH
214 self.network_status_set(status, extra)
215
216 def network_status_set(self, status, extra):
217 pal = self.network_status.palette()
218 if self.network.is_connected():
219 pal.setColor(self.network_status.foregroundRole(), QtGui.QColor('green'))
220 else:
221 pal.setColor(self.network_status.foregroundRole(), QtGui.QColor('#aa0000'))
222 self.network_status.setPalette(pal)
223 icon = self.network.status_icon(status)
224 if icon:
225 self.network_status.setText('<img src="data/icons/%s"> %s' % (icon, status.capitalize()))
226 else:
227 self.network_status.setText(status.capitalize())
228 if status == self.network.status_disconnected:
229 self.actions['connect'].setEnabled(True)
230 self.actions['disconnect'].setEnabled(False)
231 else:
232 self.actions['connect'].setEnabled(False)
233 self.actions['disconnect'].setEnabled(True)
234
235 def network_message_from_weechat(self, message):
cc80cd81
SH
236 self.debug_display(0, '==>',
237 'message (%d bytes):\n%s'
238 % (len(message), protocol.hex_and_ascii(message, 20)),
239 color='green')
7dcf23b1
SH
240 proto = protocol.Protocol()
241 message = proto.decode(str(message))
242 if message.uncompressed:
cc80cd81
SH
243 self.debug_display(0, '==>',
244 'message uncompressed (%d bytes):\n%s'
245 % (message.size_uncompressed,
246 protocol.hex_and_ascii(message.uncompressed, 20)),
247 color='green')
248 self.debug_display(0, '', 'Message: %s' % message)
7dcf23b1
SH
249 self.parse_message(message)
250
251 def parse_message(self, message):
252 if message.msgid.startswith('debug'):
cc80cd81 253 self.debug_display(0, '', '(debug message, ignored)')
7dcf23b1
SH
254 return
255 if message.msgid == 'listbuffers':
256 for obj in message.objects:
257 if obj.objtype == 'hda' and obj.value['path'][-1] == 'buffer':
258 self.list_buffers.clear()
259 while self.stacked_buffers.count() > 0:
260 buf = self.stacked_buffers.widget(0)
261 self.stacked_buffers.removeWidget(buf)
262 self.buffers = []
263 for item in obj.value['items']:
264 self.list_buffers.addItem('%d. %s' % (item['number'], item['full_name']))
265 self.buffers.append(Buffer(item))
266 self.stacked_buffers.addWidget(self.buffers[-1].widget)
267 self.buffers[-1].bufferInput.connect(self.buffer_input)
268 self.buffers[-1].widget.input.bufferSwitchPrev.connect(self.list_buffers.switch_prev_buffer)
269 self.buffers[-1].widget.input.bufferSwitchNext.connect(self.list_buffers.switch_next_buffer)
270 self.list_buffers.setCurrentRow(0)
271 self.buffers[0].widget.input.setFocus()
272 elif message.msgid == 'listlines':
273 for obj in message.objects:
274 if obj.objtype == 'hda' and obj.value['path'][-1] == 'line_data':
275 for item in obj.value['items']:
276 pointer = item['__path'][0]
277 buf = filter(lambda b: b.pointer() == pointer, self.buffers)
278 if buf:
279 buf[0].widget.chat.display(item['date'],
280 color.remove(item['prefix']),
281 color.remove(item['message']))
282 elif message.msgid == 'nicklist':
283 for obj in message.objects:
284 if obj.objtype == 'hda' and obj.value['path'][-1] == 'nick_group':
285 for item in obj.value['items']:
286 if not item['group'] and item['visible']:
287 pointer = item['__path'][0]
288 buf = filter(lambda b: b.pointer() == pointer, self.buffers)
289 if buf:
290 buf[0].add_nick(item['prefix'], item['name'])
291
292 def closeEvent(self, event):
293 self.network.disconnect_weechat()
cc80cd81
SH
294 if self.debug_dialog:
295 self.debug_dialog.close()
7dcf23b1
SH
296 config.write(self.config)
297 QtGui.QMainWindow.closeEvent(self, event)
298
299
300app = QtGui.QApplication(sys.argv)
301app.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))
302main = MainWindow()
303sys.exit(app.exec_())