Indexable TableRowData; Name changes; *Info changes
[galtack.git] / gtkgalcon.py
blob03aba464abb027502ffca242838d031f75291729
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """
4 GtkGalcon.
6 Depends on PyGTK Shell.
7 """
8 # Copyright (C) 2007 Michael Carter
9 # Copyright (C) 2007 Felix Rabe <public@felixrabe.textdriven.com>
11 # Permission is hereby granted, free of charge, to any person obtaining a copy
12 # of this software and associated documentation files (the "Software"), to deal
13 # in the Software without restriction, including without limitation the rights
14 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 # copies of the Software, and to permit persons to whom the Software is
16 # furnished to do so, subject to the following conditions:
18 # The above copyright notice and this permission notice shall be included in
19 # all copies or substantial portions of the Software.
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 # THE SOFTWARE.
29 gtkgalcon_version = (0,1,0)
31 import time
33 from PyGTKShell.Config import _config
34 _config["main-loop-integrate-twisted"] = True
35 from PyGTKShell.RawConsole import *
37 from twisted.internet import reactor
38 import galcon.net
40 arrow_cursor = gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW)
41 watch_cursor = gtk.gdk.Cursor(gtk.gdk.WATCH)
44 ### CUSTOMIZED STUFF FROM PYGTK SHELL (RAWCONSOLE) [START]
47 class RawConsoleWithIntroMixin(RawConsoleBase):
48 """
49 Raw console running an example script on initialization.
51 Customized for GtkGalcon.
52 """
54 def __init__(self, *a, **kw):
55 super(RawConsoleWithIntroMixin, self).__init__(*a, **kw)
56 buf = self.code_view.get_buffer()
57 msg = a_("Press F5 or Ctrl+E to execute this code.")
58 buf('# -*- coding: utf-8 -*-\n' +
59 '# %s\n' % msg +
60 'from PyGTKShell.API import *\n' +
61 'o = console.output_view.get_buffer()\n' +
62 'o.set_text("")\n' +
63 'c = window.galcon_client\n' +
64 'c.send_commands((1, "message", "Hello, World!"))\n' +
65 'o(str(dir(c)))\n')
66 # buf.select_range(*buf.get_bounds())
69 class RawConsoleWithIntro(
70 RawConsoleCenterInitMixin,
71 RawConsoleWithIntroMixin,
72 RawConsole,
73 ): pass
76 class WindowWithRawConsole(Window):
77 """
78 Window with a RawConsole in it and starting at a reasonable size.
79 """
81 def __init__(self, *a, **kw):
82 super(WindowWithRawConsole, self).__init__(*a, **kw)
83 self.set_title("PyGTK Shell RawConsole")
84 self.set_default_size(400, 400)
85 self.raw_console = self(RawConsoleWithIntro())
88 class WindowF12RawConsoleMixin(Window):
89 """
90 Window opening a Window containing a RawConsole when F12 is pressed.
91 """
93 def __init__(self, *a, **kw):
94 super(WindowF12RawConsoleMixin, self).__init__(*a, **kw)
95 self.connect("key-press-event", self.__cb_key_press_event)
97 def __cb_key_press_event(self, window, event):
98 if (KeyPressEval("F12"))(event):
99 rc = WindowWithRawConsole().raw_console
100 rc.code_view.textview_userexec_namespace["window"] = self
101 return True
102 return False
105 class WindowF12RawConsole(
106 WindowF12RawConsoleMixin,
107 Window,
108 ): pass
111 ### CUSTOMIZED STUFF FROM PYGTK SHELL (RAWCONSOLE) [END]
114 def print_errback(failure):
115 failure.printTraceback()
116 return None
119 class GalconChatWindow(WindowF12RawConsoleMixin):
121 A Window to allow chatting in Galcon, following the Galcon Client
122 protocol.
125 def __init__(self, user_info, server_info):
126 self.__user_info = user_info
127 self.__server_info = server_info
129 super(GalconChatWindow, self).__init__()
130 self.set_default_size(400, 300)
131 n, o = server_info["name"], server_info["owner"]
132 if n: n += " - "
133 self.set_title("GtkGalcon Chat (%s%s)" % (n, o))
135 outer_vbox, inner_vbox = gnome_hig(self)
137 sw = inner_vbox(Frame())(ScrolledWindow())
138 self.output_view = sw(TextView())
139 self.__buf = self.output_view.get_buffer()
140 self.output_view.set_editable(False)
141 hbox = gnome_hig(inner_vbox(HBox(), False, False))
142 self.input_entry = hbox(Entry())
143 self.send_button = hbox(Button("Send"), False, False)
144 self.send_button.connect("clicked", self.__cb_send)
145 self.leave_button = hbox(Button("Leave"), False, False)
146 self.leave_button.connect("clicked", self.__cb_leave)
148 self.send_button.set_property("can-default", True)
149 gobject.idle_add(self.send_button.grab_default)
150 gobject.idle_add(self.input_entry.grab_focus)
152 C = galcon.net.GalconClient
153 self.galcon_client = C(self.__user_info, self.__server_info)
154 r = self.galcon_client.register_command_callback
155 r("close", self.__cmd_close)
156 r("message", self.__cmd_message)
157 r("start", self.__cmd_start)
158 r("stop", self.__cmd_stop)
159 reactor.listenUDP(0, self.galcon_client)
161 def f(): # TODO: implement this properly after login
162 self.galcon_client.send_commands((1, "status", "away"))
163 return False
164 gobject.timeout_add(500, f)
166 def __cb_leave(self, button):
167 self.galcon_client.send_commands(
168 (1, "logout"),
169 (1, "[PING]"),
172 def __cb_send(self, button):
173 msg = self.input_entry.get_text()
174 self.galcon_client.send_commands((1, "message", msg))
175 self.input_entry.set_text("")
177 def __cmd_message(self, command):
178 sender, message = command[3:]
179 snd = ""
180 if sender: snd = " " + sender
181 self.__buf("%s%s %s\n" % (time.strftime("%X"), snd, message))
182 if "BOTQUIT" in message:
183 self.galcon_client.send_commands(
184 (1, "logout"),
185 (1, "[PING]"),
187 w = Window()
188 w.set_position(gtk.WIN_POS_CENTER)
189 w.present()
190 o, i = gnome_hig(w)
191 i(Label("Bye!")).modify_font(pango.FontDescription("sans 32"))
192 gobject.timeout_add(2000, main_loop_quit)
194 def __cmd_close(self, command):
195 self.galcon_client.send_commands((1, "[CLOSE]"))
196 gobject.timeout_add(500, main_loop_quit) # TODO: await ACK
198 def __cmd_stop(self, command):
199 self.__buf("(stop)\n")
201 def __cmd_start(self, command):
202 self.__buf("(start)\n")
205 class GalconServerListWindow(WindowF12RawConsoleMixin):
207 Window displaying the list of Galcon servers.
210 def __init__(self, user_info):
211 super(GalconServerListWindow, self).__init__()
212 self.__user_info = user_info
213 self.set_title("Galcon Server List")
214 self.set_default_size(600, 400)
215 self.set_position(gtk.WIN_POS_CENTER)
217 outer_vbox, self.__inner_vbox = gnome_hig(self)
219 hbox = gnome_hig(self.__inner_vbox(HBox(), False, False))
220 self.__version_label = hbox(LeftLabel(""), False, False)
221 self.set_current_version()
222 self.__version_label.set_sensitive(False)
224 self.__refresh_button = hbox(Button("Refresh List"), False, False,
225 pack_end = True)
226 self.__refresh_button.connect("clicked", self.__cb_refresh)
228 sw = self.__inner_vbox(Frame())(ScrolledWindow())
229 self.__treeview = sw(TreeView())
230 self.__treeview.set_sensitive(False)
231 cb = self.__cb_selection_changed
232 self.__treeview.get_selection().connect("changed", cb)
234 for i, spec in enumerate(galcon.net.ServerInfo.COL_SPEC):
235 if not spec["visible"]: continue
236 col = gtk.TreeViewColumn(spec["caption"])
237 col.set_reorderable(True)
238 col.set_sort_column_id(i)
239 self.__treeview.append_column(col)
240 cell = gtk.CellRendererText()
241 col.pack_start(cell, True)
242 col.add_attribute(cell, "text", i)
243 col.add_attribute(cell, "sensitive", 0)
245 self.__join_button = self.__inner_vbox(Button("Join"), False,
246 False)
247 self.__join_button.set_sensitive(False)
248 self.__join_button.connect("clicked", self.__cb_join)
250 self.__statusbar = outer_vbox(Statusbar(), False, False)
251 self.__statusbar_cid = cid = self.__statusbar.get_context_id("msg")
253 self.__server_password_prompt = dlg = Dialog(
254 "Server Password Required", self, gtk.DIALOG_MODAL,
255 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
256 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
258 dlg.set_default_response(gtk.RESPONSE_ACCEPT)
259 dlg.set_default_size(300, -1)
260 vbox = gnome_hig(VBox()) # I want my own VBox here ;-)
261 vbox.set_border_width(6)
262 dlg.vbox.pack_start(vbox)
263 self.__server_password_prompt_label = vbox(LeftLabel())
264 self.__server_password_prompt_entry = vbox(Entry())
265 self.__server_password_prompt_entry.set_visibility(False)
267 def set_current_version(self, current_version = None):
268 self.__version_label.set_sensitive(False)
269 if current_version is None:
270 self.__version_label.set_text("Current version: ???")
271 else:
272 self.__version_label.set_text("Current version: %s" %
273 ".".join(current_version))
274 self.__version_label.set_sensitive(True)
276 def set_server_info_list(self, server_info_list = None):
277 types = galcon.net.ServerInfo.get_col_types()
278 model = gtk.ListStore(*types)
279 self.__treeview.set_sensitive(False)
280 if server_info_list is not None:
281 for server_info in server_info_list:
282 model.append(server_info.get_data_tuple())
283 self.__treeview.set_sensitive(True)
284 self.__treeview.set_model(model)
286 def __cb_refresh(self, button):
287 self.window.set_cursor(watch_cursor)
288 self.__inner_vbox.set_sensitive(False)
289 self.__statusbar.push(self.__statusbar_cid,
290 "Retrieving server list...")
291 deferred = galcon.net.get_server_list(self.__user_info)
292 deferred.addCallback(self.__server_list_callback)
293 deferred.addErrback(self.__server_list_errback)
295 def __server_list_callback(self, (current_version, server_info_list)):
296 self.set_current_version(current_version)
297 self.set_server_info_list(server_info_list)
298 self.__statusbar.pop(self.__statusbar_cid)
299 self.__inner_vbox.set_sensitive(True)
300 self.window.set_cursor(arrow_cursor)
302 def __server_list_errback(self, failure):
303 self.set_sensitive(True)
304 self.window.set_cursor(arrow_cursor)
305 cid = self.__statusbar_cid
306 sbar = self.__statusbar
307 sbar.pop(cid)
308 gtk.gdk.beep()
309 mid = sbar.push(cid, "Retrieving server list failed")
310 gobject.timeout_add(4000, lambda: sbar.remove(cid, mid))
311 return failure
313 def __cb_selection_changed(self, treesel):
314 model, iter = treesel.get_selected()
315 if iter is None:
316 ok_to_join = False
317 else:
318 data = galcon.net.ServerInfo(*model[iter])
319 ok_to_join = data.bots_ok
320 self.__join_button.set_sensitive(ok_to_join)
322 def __cb_join(self, button):
323 treesel = self.__treeview.get_selection()
324 model, iter = treesel.get_selected()
325 server_info = galcon.net.ServerInfo(*model[iter])
326 if server_info.pwd_protected:
327 self.__run_server_password_prompt(server_info)
328 return None
329 self.join_with_server_info(server_info)
331 def __run_server_password_prompt(self, server_info):
332 n, o = server_info["name"], server_info["owner"]
333 if n: n += " - "
334 s = "Password for %s%s:" % (n, o)
335 self.__server_password_prompt_label.set_text(s)
336 response_id = self.__server_password_prompt.run()
337 if response_id != gtk.RESPONSE_ACCEPT: return None
338 passwd = self.__server_password_prompt_entry.get_text()
339 server_info["passwd"] = passwd
340 self.join_with_server_info(server_info)
342 def join_with_server_info(self, server_info):
343 chat_window = GalconChatWindow(self.__user_info, server_info)
344 gobject.idle_add(self.destroy)
347 class GalconLoginWindow(WindowF12RawConsoleMixin):
349 A Window asking the user for Galcon login details and log in.
352 def __init__(self):
353 super(GalconLoginWindow, self).__init__()
354 self.set_title("GtkGalcon Login")
355 self.set_default_size(400, -1)
356 self.set_position(gtk.WIN_POS_CENTER)
358 outer_vbox, self.inner_vbox = gnome_hig(self)
360 table = gnome_hig(self.inner_vbox(Table(), False, False))
362 xop = {"xoptions": gtk.FILL}
363 table.add_rows()
364 table.attach_cell(LeftLabel("Email Address:"), **xop)
365 self.email_entry = table.attach_cell(Entry())
367 table.add_rows()
368 table.attach_cell(LeftLabel("Username:"), **xop)
369 self.name_entry = table.attach_cell(Entry())
370 self.name_entry.set_text("gtkgalcon")
372 table.add_rows()
373 table.attach_cell(LeftLabel("Password:"), **xop)
374 self.passwd_entry = table.attach_cell(Entry())
375 self.passwd_entry.set_visibility(False)
377 table.add_rows()
378 self.login_button = table.attach_row(Button("Sign In"))
379 self.login_button.connect("clicked", self.__login)
380 self.login_button.set_property("can-default", True)
381 gobject.idle_add(self.login_button.grab_default)
383 self.statusbar = outer_vbox(Statusbar(), False, False)
384 self.statusbar_cid = cid = self.statusbar.get_context_id("msg")
385 mid = self.statusbar.push(cid, "Enter your login details")
386 gobject.timeout_add(4000, lambda: self.statusbar.remove(cid, mid))
388 def __get_user_info(self):
389 email = self.email_entry.get_text()
390 name = self.name_entry.get_text()
391 passwd = self.passwd_entry.get_text()
392 platform = "linux2"
393 version = "1.2.1"
394 return galcon.net.UserInfo(email, name, passwd, platform, version)
396 def __login(self, button):
397 self.window.set_cursor(watch_cursor)
398 self.inner_vbox.set_sensitive(False)
399 self.statusbar.push(self.statusbar_cid,
400 "Retrieving server list...")
401 user_info = self.__get_user_info()
402 deferred = galcon.net.get_server_list(user_info)
403 deferred.addCallbacks(self.__server_list_callback,
404 self.__server_list_errback)
405 deferred.addErrback(print_errback)
407 def __server_list_callback(self, (current_version, server_info_list)):
408 w = GalconServerListWindow(self.__get_user_info())
409 w.set_current_version(current_version)
410 w.set_server_info_list(server_info_list)
411 gobject.idle_add(self.destroy)
413 def __server_list_errback(self, failure):
414 failure.trap(galcon.net.ServerListException)
415 self.inner_vbox.set_sensitive(True)
416 self.window.set_cursor(arrow_cursor)
417 cid = self.statusbar_cid
418 self.statusbar.pop(cid)
419 gtk.gdk.beep()
420 mid = self.statusbar.push(cid, "Failure: %s" %
421 failure.getErrorMessage())
422 gobject.timeout_add(4000, lambda: self.statusbar.remove(cid, mid))
423 return None
426 def main(argv):
427 GalconLoginWindow()
428 main_loop_run()
429 return 0
431 if __name__ == "__main__":
432 import sys
433 sys.exit(main(sys.argv))