Merge branch 'master' into website
[galtack.git] / gtkgalcon.py
blobcb885a2ef7f9cdc9b3fc2324bfaa3cab5a2f38dc
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
12 # copy of this software and associated documentation files (the
13 # "Software"), to deal in the Software without restriction, including
14 # without limitation the rights to use, copy, modify, merge, publish,
15 # distribute, sublicense, and/or sell copies of the Software, and to permit
16 # persons to whom the Software is furnished to do so, subject to the
17 # following conditions:
19 # The above copyright notice and this permission notice shall be included
20 # in all copies or substantial portions of the Software.
22 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
26 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
27 # OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
28 # THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 # Recommended line length or text width: 75 characters.
32 gtkgalcon_version = (0,2,0)
33 gtkgalcon_version_str = ".".join(map(str, gtkgalcon_version))
35 import optparse
36 import math
37 PI2 = math.pi * 2
38 import time
40 from PyGTKShell.Config import _config
41 _config["main-loop-integrate-twisted"] = True
42 from PyGTKShell.RawConsole import *
43 arrow_cursor = gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW)
44 watch_cursor = gtk.gdk.Cursor(gtk.gdk.WATCH)
46 from twisted.internet import reactor
48 import galcon.net
49 class GalconClient(
50 galcon.net.GalconClientRecvCmdLoggerMixin,
51 galcon.net.GalconClientHousekeeperMixin,
52 galcon.net.GalconClientUniverseTrackerMixin,
53 galcon.net.GalconClientBase,
54 ): pass
57 ### CUSTOMIZED STUFF FROM PYGTK SHELL (RAWCONSOLE) [START]
60 class RawConsoleWithIntroMixin(RawConsoleBase):
61 """
62 Raw console running an example script on initialization.
64 Customized for GtkGalcon.
65 """
67 def __init__(self, *a, **kw):
68 super(RawConsoleWithIntroMixin, self).__init__(*a, **kw)
69 buf = self.code_view.get_buffer()
70 msg = a_("Press F5 or Ctrl+E to execute this code.")
71 buf('# -*- coding: utf-8 -*-\n' +
72 '# %s\n' % msg +
73 'from PyGTKShell.API import *\n' +
74 'o = console.output_view.get_buffer()\n' +
75 'o.set_text("")\n' +
76 'c = window.galcon_client\n' +
77 'c.send_commands((1, "message", "Hello, World!"))\n' +
78 'o(str(dir(c)))\n')
81 class RawConsoleWithIntro(
82 RawConsoleCenterInitMixin,
83 RawConsoleWithIntroMixin,
84 RawConsole,
85 ): pass
88 class WindowWithRawConsole(Window):
89 """
90 Window with a RawConsole in it and starting at a reasonable size.
91 """
93 def __init__(self, *a, **kw):
94 super(WindowWithRawConsole, self).__init__(*a, **kw)
95 self.set_title("PyGTK Shell RawConsole")
96 self.set_default_size(400, 400)
97 self.raw_console = self(RawConsoleWithIntro())
100 class WindowF12RawConsoleMixin(Window):
102 Window opening a Window containing a RawConsole when F12 is pressed.
105 def __init__(self, *a, **kw):
106 super(WindowF12RawConsoleMixin, self).__init__(*a, **kw)
107 self.connect("key-press-event", self.__cb_key_press_event)
109 def __cb_key_press_event(self, window, event):
110 if (KeyPressEval("F12"))(event):
111 rc = WindowWithRawConsole().raw_console
112 rc.code_view.textview_userexec_namespace["window"] = self
113 return True
114 return False
117 class WindowF12RawConsole(
118 WindowF12RawConsoleMixin,
119 Window,
120 ): pass
123 ### CUSTOMIZED STUFF FROM PYGTK SHELL (RAWCONSOLE) [END]
126 def print_errback(failure):
127 failure.printTraceback()
128 return None
131 class UniverseView(DrawingArea):
133 Universe canvas.
136 def __init__(self):
137 super(UniverseView, self).__init__()
138 self.reset()
139 self.connect("expose-event", self.__cb_expose_event)
140 self.set_size_request(640, 480)
142 def reset(self):
143 self.planet_info_list = []
145 def __cb_expose_event(self, widget, event):
146 x, y, w, h = self.allocation
147 cr = self.window.cairo_create()
149 # background
150 cr.rectangle(0, 0, w, h)
151 cr.set_source_rgba(0, 0, 0, 1)
152 cr.fill()
154 # planets
155 cr.set_source_rgba(1, 1, 1, 1)
156 for planet_info in self.planet_info_list:
157 cr.arc(planet_info.x, planet_info.y, 10, 0, PI2)
158 cr.fill()
160 def redraw(self):
161 if self.window is not None:
162 self.window.invalidate_rect(self.allocation, False)
163 return False
166 class GalconUniverseWindow(WindowF12RawConsoleMixin):
168 A Window displaying a view of the universe.
171 def __init__(self):
172 super(GalconUniverseWindow, self).__init__()
173 self.set_title("Universe Window")
174 self.universe_view = self(UniverseView())
177 class GalconChatWindow(WindowF12RawConsoleMixin):
179 A Window to allow chatting in Galcon, following the Galcon Client
180 protocol.
183 def __init__(self, options, user_info, server_info):
184 self.__options = options
185 self.__user_info = user_info
186 self.__server_info = server_info
188 super(GalconChatWindow, self).__init__()
189 self.set_default_size(400, 300)
190 n, o = server_info["name"], server_info["owner"]
191 if n: n += " - "
192 self.set_title("GtkGalcon Chat (%s%s)" % (n, o))
194 outer_vbox, inner_vbox = gnome_hig(self)
196 sw = inner_vbox(Frame())(ScrolledWindow())
197 self.output_view = sw(TextView())
198 self.__buf = self.output_view.get_buffer()
199 self.output_view.set_editable(False)
200 hbox = gnome_hig(inner_vbox(HBox(), False, False))
201 self.input_entry = hbox(Entry())
202 self.send_button = hbox(Button("Send"), False, False)
203 self.send_button.connect("clicked", self.__cb_send)
204 self.leave_button = hbox(Button("Leave"), False, False)
205 self.leave_button.connect("clicked", self.__cb_leave)
207 self.send_button.set_property("can-default", True)
208 gobject.idle_add(self.send_button.grab_default)
209 gobject.idle_add(self.input_entry.grab_focus)
211 C = GalconClient
212 self.galcon_client = C(self.__options,
213 self.__user_info, self.__server_info)
214 r = self.galcon_client.register_command_callback
215 r("close", self.__cmd_close)
216 r("message", self.__cmd_message)
217 r("start", self.__cmd_start)
218 r("stop", self.__cmd_stop)
219 reactor.listenUDP(0, self.galcon_client)
221 def f(): # TODO: implement this properly after login
222 self.galcon_client.send_commands((1, "status", "away"))
223 return False
224 gobject.timeout_add(500, f)
226 def __cb_leave(self, button):
227 deferred = self.galcon_client.logout()
228 deferred.addCallback(self.__cb_left)
230 def __cb_left(self, no_args):
231 # GalconLoginWindow._instance.show()
232 GalconServerListWindow._instance.show()
233 self.destroy()
235 def __cb_send(self, button):
236 msg = self.input_entry.get_text()
237 self.galcon_client.send_commands((1, "message", msg))
238 self.input_entry.set_text("")
240 def __cmd_message(self, command):
241 sender, message = command[3:]
242 snd = ""
243 if sender: snd = " " + sender
244 self.__buf("%s%s %s\n" % (time.strftime("%X"), snd, message))
245 if "BOTQUIT" in message:
246 self.galcon_client.logout()
247 w = Window()
248 w.set_position(gtk.WIN_POS_CENTER)
249 w.present()
250 o, i = gnome_hig(w)
251 i(Label("Bye!")).modify_font(pango.FontDescription("sans 32"))
252 gobject.timeout_add(2000, main_loop_quit)
254 def __cmd_close(self, command):
255 self.galcon_client.send_commands((1, "[CLOSE]"))
256 gobject.timeout_add(500, main_loop_quit) # TODO: await ACK
258 def __cmd_stop(self, command):
259 self.__buf("(stop)\n")
261 def __cmd_start(self, command):
262 self.__buf("(start)\n")
265 class GalconServerListWindow(WindowF12RawConsoleMixin):
267 Window displaying the list of Galcon servers.
270 def __init__(self, options, user_info):
271 self.__options = options
272 super(GalconServerListWindow, self).__init__()
273 self.__user_info = user_info
274 self.set_title("Galcon Server List")
275 self.set_default_size(600, 400)
276 self.set_position(gtk.WIN_POS_CENTER)
278 outer_vbox, self.__inner_vbox = gnome_hig(self)
280 hbox = gnome_hig(self.__inner_vbox(HBox(), False, False))
281 self.__version_label = hbox(LeftLabel(""), False, False)
282 self.set_current_version()
283 self.__version_label.set_sensitive(False)
285 self.__refresh_button = hbox(Button("Refresh List"), False, False,
286 pack_end = True)
287 self.__refresh_button.connect("clicked", self.__cb_refresh)
289 sw = self.__inner_vbox(Frame())(ScrolledWindow())
290 self.__treeview = sw(TreeView())
291 self.__treeview.set_sensitive(False)
292 cb = self.__cb_selection_changed
293 self.__treeview.get_selection().connect("changed", cb)
295 for i, spec in enumerate(galcon.net.ServerInfo.COL_SPEC):
296 if not spec["visible"]: continue
297 col = gtk.TreeViewColumn(spec["caption"])
298 col.set_reorderable(True)
299 col.set_sort_column_id(i)
300 self.__treeview.append_column(col)
301 cell = gtk.CellRendererText()
302 col.pack_start(cell, True)
303 col.add_attribute(cell, "text", i)
304 col.add_attribute(cell, "sensitive", 0)
306 hbox = gnome_hig(self.__inner_vbox(HBox(), False, False))
307 self.__join_button = hbox(Button("Join"))
308 self.__join_button.set_sensitive(False)
309 self.__join_button.connect("clicked", self.__cb_join)
311 self.back_button = hbox(Button("Back"), False, False)
312 self.back_button.connect("clicked", self.__cb_back)
314 self.__statusbar = outer_vbox(Statusbar(), False, False)
315 self.__statusbar_cid = cid = self.__statusbar.get_context_id("msg")
317 self.__server_password_prompt = dlg = Dialog(
318 "Server Password Required", self, gtk.DIALOG_MODAL,
319 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
320 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
322 dlg.set_default_response(gtk.RESPONSE_ACCEPT)
323 dlg.set_default_size(300, -1)
324 vbox = gnome_hig(VBox()) # I want my own VBox here ;-)
325 vbox.set_border_width(6)
326 dlg.vbox.pack_start(vbox)
327 self.__server_password_prompt_label = vbox(LeftLabel())
328 self.__server_password_prompt_entry = vbox(Entry())
329 self.__server_password_prompt_entry.set_visibility(False)
331 def set_current_version(self, current_version = None):
332 self.__version_label.set_sensitive(False)
333 if current_version is None:
334 self.__version_label.set_text("Current version: ???")
335 else:
336 self.__version_label.set_text("Current version: %s" %
337 ".".join(current_version))
338 self.__version_label.set_sensitive(True)
340 def set_server_info_list(self, server_info_list = None):
341 types = galcon.net.ServerInfo.get_col_types()
342 model = gtk.ListStore(*types)
343 self.__treeview.set_sensitive(False)
344 if server_info_list is not None:
345 for server_info in server_info_list:
346 model.append(server_info.get_data_tuple())
347 self.__treeview.set_sensitive(True)
348 self.__treeview.set_model(model)
350 def __cb_refresh(self, button):
351 self.window.set_cursor(watch_cursor)
352 self.__inner_vbox.set_sensitive(False)
353 self.__statusbar.push(self.__statusbar_cid,
354 "Retrieving server list...")
355 deferred = galcon.net.get_server_list(self.__user_info)
356 deferred.addCallback(self.__server_list_callback)
357 deferred.addErrback(self.__server_list_errback)
359 def __server_list_callback(self, (current_version, server_info_list)):
360 self.set_current_version(current_version)
361 self.set_server_info_list(server_info_list)
362 self.__statusbar.pop(self.__statusbar_cid)
363 self.__inner_vbox.set_sensitive(True)
364 self.window.set_cursor(arrow_cursor)
366 def __server_list_errback(self, failure):
367 self.set_sensitive(True)
368 self.window.set_cursor(arrow_cursor)
369 cid = self.__statusbar_cid
370 sbar = self.__statusbar
371 sbar.pop(cid)
372 gtk.gdk.beep()
373 mid = sbar.push(cid, "Retrieving server list failed")
374 gobject.timeout_add(4000, lambda: sbar.remove(cid, mid))
375 return failure
377 def __cb_selection_changed(self, treesel):
378 model, iter = treesel.get_selected()
379 if iter is None:
380 ok_to_join = False
381 else:
382 data = galcon.net.ServerInfo(*model[iter])
383 ok_to_join = data.bots_ok
384 self.__join_button.set_sensitive(ok_to_join)
386 def __cb_join(self, button):
387 treesel = self.__treeview.get_selection()
388 model, iter = treesel.get_selected()
389 server_info = galcon.net.ServerInfo(*model[iter])
390 if server_info.pwd_protected:
391 self.__run_server_password_prompt(server_info)
392 return None
393 self.join_with_server_info(server_info)
395 def __cb_back(self, button):
396 GalconLoginWindow._instance.show()
397 self.destroy()
399 def __run_server_password_prompt(self, server_info):
400 n, o = server_info["name"], server_info["owner"]
401 if n: n += " - "
402 s = "Password for %s%s:" % (n, o)
403 self.__server_password_prompt_label.set_text(s)
404 response_id = self.__server_password_prompt.run()
405 if response_id != gtk.RESPONSE_ACCEPT: return None
406 passwd = self.__server_password_prompt_entry.get_text()
407 server_info["passwd"] = passwd
408 self.join_with_server_info(server_info)
410 def join_with_server_info(self, server_info):
411 chat_window = GalconChatWindow(self.__options,
412 self.__user_info, server_info)
413 gobject.idle_add(self.hide)
416 class GalconLoginWindow(WindowF12RawConsoleMixin):
418 A Window asking the user for Galcon login details and log in.
421 def __init__(self, options):
422 self.__options = options
423 super(GalconLoginWindow, self).__init__()
424 self.set_title("GtkGalcon Login")
425 self.set_default_size(400, -1)
426 self.set_position(gtk.WIN_POS_CENTER)
428 outer_vbox, self.inner_vbox = gnome_hig(self)
430 table = gnome_hig(self.inner_vbox(Table(), False, False))
432 xop = {"xoptions": gtk.FILL}
433 table.add_rows()
434 table.attach_cell(LeftLabel("Email Address:"), **xop)
435 self.email_entry = table.attach_cell(Entry())
437 table.add_rows()
438 table.attach_cell(LeftLabel("Username:"), **xop)
439 self.name_entry = table.attach_cell(Entry())
440 self.name_entry.set_text("gtkgalcon")
442 table.add_rows()
443 table.attach_cell(LeftLabel("Password:"), **xop)
444 self.passwd_entry = table.attach_cell(Entry())
445 self.passwd_entry.set_visibility(False)
447 table.add_rows()
448 hbox = gnome_hig(table.attach_row(HBox()))
449 self.__login_button = hbox(Button("Sign In"))
450 self.__login_button.connect("clicked", self.__cb_login)
451 self.__login_button.set_property("can-default", True)
452 gobject.idle_add(self.__login_button.grab_default)
454 self.__quit_button = hbox(Button("Quit"), False, False)
455 self.__quit_button.connect("clicked", self.__cb_quit)
457 self.statusbar = outer_vbox(Statusbar(), False, False)
458 self.statusbar_cid = cid = self.statusbar.get_context_id("msg")
459 mid = self.statusbar.push(cid, "Enter your login details")
460 gobject.timeout_add(4000, lambda: self.statusbar.remove(cid, mid))
462 def __get_user_info(self):
463 email = self.email_entry.get_text()
464 name = self.name_entry.get_text()
465 passwd = self.passwd_entry.get_text()
466 platform = "linux2"
467 version = "1.2.1"
468 return galcon.net.UserInfo(email, name, passwd, platform, version)
470 def __cb_login(self, button):
471 self.window.set_cursor(watch_cursor)
472 self.inner_vbox.set_sensitive(False)
473 self.statusbar.push(self.statusbar_cid,
474 "Retrieving server list...")
475 user_info = self.__get_user_info()
476 deferred = galcon.net.get_server_list(user_info)
477 deferred.addCallbacks(self.__server_list_callback,
478 self.__server_list_errback)
479 deferred.addErrback(print_errback)
481 def __cb_quit(self, button):
482 main_loop_quit()
484 def __server_list_callback(self, (current_version, server_info_list)):
485 w = GalconServerListWindow(self.__options, self.__get_user_info())
486 GalconServerListWindow._instance = w
487 w.set_current_version(current_version)
488 w.set_server_info_list(server_info_list)
489 gobject.idle_add(self.hide)
490 self.inner_vbox.set_sensitive(True)
491 self.window.set_cursor(arrow_cursor)
492 cid = self.statusbar_cid
493 self.statusbar.pop(cid)
495 def __server_list_errback(self, failure):
496 failure.trap(galcon.net.ServerListException)
497 self.inner_vbox.set_sensitive(True)
498 self.window.set_cursor(arrow_cursor)
499 cid = self.statusbar_cid
500 self.statusbar.pop(cid)
501 gtk.gdk.beep()
502 mid = self.statusbar.push(cid, "Failure: %s" %
503 failure.getErrorMessage())
504 gobject.timeout_add(4000, lambda: self.statusbar.remove(cid, mid))
505 return None
508 def main(argv):
509 option_parser = optparse.OptionParser(prog = "GtkGalcon",
510 version = "%%prog %s" %
511 gtkgalcon_version_str)
512 option_parser = GalconClient._modify_option_parser(option_parser)
513 options, arguments = option_parser.parse_args(argv[1:])
514 GalconLoginWindow._instance = GalconLoginWindow(options)
515 main_loop_run()
516 return 0
518 if __name__ == "__main__":
519 import sys
520 sys.exit(main(sys.argv))