MANIFEST.in: Include doxygen.conf
[galtack.git] / gtkgalcon.py
blobc9d7a6d4f45c4e16a83fd43b9b7de05c34238216
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)
34 import math
35 PI2 = math.pi * 2
36 import time
38 from PyGTKShell.Config import _config
39 _config["main-loop-integrate-twisted"] = True
40 from PyGTKShell.RawConsole import *
41 arrow_cursor = gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW)
42 watch_cursor = gtk.gdk.Cursor(gtk.gdk.WATCH)
44 from twisted.internet import reactor
46 import galcon.net
47 class GalconClient(
48 galcon.net.GalconClientRecvCmdLoggerMixin,
49 galcon.net.GalconClientHousekeeperMixin,
50 galcon.net.GalconClientUniverseTrackerMixin,
51 galcon.net.GalconClientBase,
52 ): pass
55 ### CUSTOMIZED STUFF FROM PYGTK SHELL (RAWCONSOLE) [START]
58 class RawConsoleWithIntroMixin(RawConsoleBase):
59 """
60 Raw console running an example script on initialization.
62 Customized for GtkGalcon.
63 """
65 def __init__(self, *a, **kw):
66 super(RawConsoleWithIntroMixin, self).__init__(*a, **kw)
67 buf = self.code_view.get_buffer()
68 msg = a_("Press F5 or Ctrl+E to execute this code.")
69 buf('# -*- coding: utf-8 -*-\n' +
70 '# %s\n' % msg +
71 'from PyGTKShell.API import *\n' +
72 'o = console.output_view.get_buffer()\n' +
73 'o.set_text("")\n' +
74 'c = window.galcon_client\n' +
75 'c.send_commands((1, "message", "Hello, World!"))\n' +
76 'o(str(dir(c)))\n')
77 # buf.select_range(*buf.get_bounds())
80 class RawConsoleWithIntro(
81 RawConsoleCenterInitMixin,
82 RawConsoleWithIntroMixin,
83 RawConsole,
84 ): pass
87 class WindowWithRawConsole(Window):
88 """
89 Window with a RawConsole in it and starting at a reasonable size.
90 """
92 def __init__(self, *a, **kw):
93 super(WindowWithRawConsole, self).__init__(*a, **kw)
94 self.set_title("PyGTK Shell RawConsole")
95 self.set_default_size(400, 400)
96 self.raw_console = self(RawConsoleWithIntro())
99 class WindowF12RawConsoleMixin(Window):
101 Window opening a Window containing a RawConsole when F12 is pressed.
104 def __init__(self, *a, **kw):
105 super(WindowF12RawConsoleMixin, self).__init__(*a, **kw)
106 self.connect("key-press-event", self.__cb_key_press_event)
108 def __cb_key_press_event(self, window, event):
109 if (KeyPressEval("F12"))(event):
110 rc = WindowWithRawConsole().raw_console
111 rc.code_view.textview_userexec_namespace["window"] = self
112 return True
113 return False
116 class WindowF12RawConsole(
117 WindowF12RawConsoleMixin,
118 Window,
119 ): pass
122 ### CUSTOMIZED STUFF FROM PYGTK SHELL (RAWCONSOLE) [END]
125 def print_errback(failure):
126 failure.printTraceback()
127 return None
130 class UniverseView(DrawingArea):
132 Universe canvas.
135 def __init__(self):
136 super(UniverseView, self).__init__()
137 self.reset()
138 self.connect("expose-event", self.__cb_expose_event)
139 self.set_size_request(640, 480)
141 def reset(self):
142 self.planet_info_list = []
144 def __cb_expose_event(self, widget, event):
145 x, y, w, h = self.allocation
146 cr = self.window.cairo_create()
148 # background
149 cr.rectangle(0, 0, w, h)
150 cr.set_source_rgba(0, 0, 0, 1)
151 cr.fill()
153 # planets
154 cr.set_source_rgba(1, 1, 1, 1)
155 for planet_info in self.planet_info_list:
156 cr.arc(planet_info.x, planet_info.y, 10, 0, PI2)
157 cr.fill()
159 def redraw(self):
160 if self.window is not None:
161 self.window.invalidate_rect(self.allocation, False)
162 return False
165 class GalconUniverseWindow(WindowF12RawConsoleMixin):
167 A Window displaying a view of the universe.
170 def __init__(self):
171 super(GalconUniverseWindow, self).__init__()
172 self.set_title("Universe Window")
173 self.universe_view = self(UniverseView())
176 class GalconChatWindow(WindowF12RawConsoleMixin):
178 A Window to allow chatting in Galcon, following the Galcon Client
179 protocol.
182 def __init__(self, user_info, server_info):
183 self.__user_info = user_info
184 self.__server_info = server_info
186 super(GalconChatWindow, self).__init__()
187 self.set_default_size(400, 300)
188 n, o = server_info["name"], server_info["owner"]
189 if n: n += " - "
190 self.set_title("GtkGalcon Chat (%s%s)" % (n, o))
192 outer_vbox, inner_vbox = gnome_hig(self)
194 sw = inner_vbox(Frame())(ScrolledWindow())
195 self.output_view = sw(TextView())
196 self.__buf = self.output_view.get_buffer()
197 self.output_view.set_editable(False)
198 hbox = gnome_hig(inner_vbox(HBox(), False, False))
199 self.input_entry = hbox(Entry())
200 self.send_button = hbox(Button("Send"), False, False)
201 self.send_button.connect("clicked", self.__cb_send)
202 self.leave_button = hbox(Button("Leave"), False, False)
203 self.leave_button.connect("clicked", self.__cb_leave)
205 self.send_button.set_property("can-default", True)
206 gobject.idle_add(self.send_button.grab_default)
207 gobject.idle_add(self.input_entry.grab_focus)
209 C = GalconClient
210 self.galcon_client = C(self.__user_info, self.__server_info)
211 r = self.galcon_client.register_command_callback
212 r("close", self.__cmd_close)
213 r("message", self.__cmd_message)
214 r("start", self.__cmd_start)
215 r("stop", self.__cmd_stop)
216 reactor.listenUDP(0, self.galcon_client)
218 def f(): # TODO: implement this properly after login
219 self.galcon_client.send_commands((1, "status", "away"))
220 return False
221 gobject.timeout_add(500, f)
223 def __cb_leave(self, button):
224 self.galcon_client.logout()
226 def __cb_send(self, button):
227 msg = self.input_entry.get_text()
228 self.galcon_client.send_commands((1, "message", msg))
229 self.input_entry.set_text("")
231 def __cmd_message(self, command):
232 sender, message = command[3:]
233 snd = ""
234 if sender: snd = " " + sender
235 self.__buf("%s%s %s\n" % (time.strftime("%X"), snd, message))
236 if "BOTQUIT" in message:
237 self.galcon_client.logout()
238 w = Window()
239 w.set_position(gtk.WIN_POS_CENTER)
240 w.present()
241 o, i = gnome_hig(w)
242 i(Label("Bye!")).modify_font(pango.FontDescription("sans 32"))
243 gobject.timeout_add(2000, main_loop_quit)
245 def __cmd_close(self, command):
246 self.galcon_client.send_commands((1, "[CLOSE]"))
247 gobject.timeout_add(500, main_loop_quit) # TODO: await ACK
249 def __cmd_stop(self, command):
250 self.__buf("(stop)\n")
252 def __cmd_start(self, command):
253 self.__buf("(start)\n")
256 class GalconServerListWindow(WindowF12RawConsoleMixin):
258 Window displaying the list of Galcon servers.
261 def __init__(self, user_info):
262 super(GalconServerListWindow, self).__init__()
263 self.__user_info = user_info
264 self.set_title("Galcon Server List")
265 self.set_default_size(600, 400)
266 self.set_position(gtk.WIN_POS_CENTER)
268 outer_vbox, self.__inner_vbox = gnome_hig(self)
270 hbox = gnome_hig(self.__inner_vbox(HBox(), False, False))
271 self.__version_label = hbox(LeftLabel(""), False, False)
272 self.set_current_version()
273 self.__version_label.set_sensitive(False)
275 self.__refresh_button = hbox(Button("Refresh List"), False, False,
276 pack_end = True)
277 self.__refresh_button.connect("clicked", self.__cb_refresh)
279 sw = self.__inner_vbox(Frame())(ScrolledWindow())
280 self.__treeview = sw(TreeView())
281 self.__treeview.set_sensitive(False)
282 cb = self.__cb_selection_changed
283 self.__treeview.get_selection().connect("changed", cb)
285 for i, spec in enumerate(galcon.net.ServerInfo.COL_SPEC):
286 if not spec["visible"]: continue
287 col = gtk.TreeViewColumn(spec["caption"])
288 col.set_reorderable(True)
289 col.set_sort_column_id(i)
290 self.__treeview.append_column(col)
291 cell = gtk.CellRendererText()
292 col.pack_start(cell, True)
293 col.add_attribute(cell, "text", i)
294 col.add_attribute(cell, "sensitive", 0)
296 self.__join_button = self.__inner_vbox(Button("Join"), False,
297 False)
298 self.__join_button.set_sensitive(False)
299 self.__join_button.connect("clicked", self.__cb_join)
301 self.__statusbar = outer_vbox(Statusbar(), False, False)
302 self.__statusbar_cid = cid = self.__statusbar.get_context_id("msg")
304 self.__server_password_prompt = dlg = Dialog(
305 "Server Password Required", self, gtk.DIALOG_MODAL,
306 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
307 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
309 dlg.set_default_response(gtk.RESPONSE_ACCEPT)
310 dlg.set_default_size(300, -1)
311 vbox = gnome_hig(VBox()) # I want my own VBox here ;-)
312 vbox.set_border_width(6)
313 dlg.vbox.pack_start(vbox)
314 self.__server_password_prompt_label = vbox(LeftLabel())
315 self.__server_password_prompt_entry = vbox(Entry())
316 self.__server_password_prompt_entry.set_visibility(False)
318 def set_current_version(self, current_version = None):
319 self.__version_label.set_sensitive(False)
320 if current_version is None:
321 self.__version_label.set_text("Current version: ???")
322 else:
323 self.__version_label.set_text("Current version: %s" %
324 ".".join(current_version))
325 self.__version_label.set_sensitive(True)
327 def set_server_info_list(self, server_info_list = None):
328 types = galcon.net.ServerInfo.get_col_types()
329 model = gtk.ListStore(*types)
330 self.__treeview.set_sensitive(False)
331 if server_info_list is not None:
332 for server_info in server_info_list:
333 model.append(server_info.get_data_tuple())
334 self.__treeview.set_sensitive(True)
335 self.__treeview.set_model(model)
337 def __cb_refresh(self, button):
338 self.window.set_cursor(watch_cursor)
339 self.__inner_vbox.set_sensitive(False)
340 self.__statusbar.push(self.__statusbar_cid,
341 "Retrieving server list...")
342 deferred = galcon.net.get_server_list(self.__user_info)
343 deferred.addCallback(self.__server_list_callback)
344 deferred.addErrback(self.__server_list_errback)
346 def __server_list_callback(self, (current_version, server_info_list)):
347 self.set_current_version(current_version)
348 self.set_server_info_list(server_info_list)
349 self.__statusbar.pop(self.__statusbar_cid)
350 self.__inner_vbox.set_sensitive(True)
351 self.window.set_cursor(arrow_cursor)
353 def __server_list_errback(self, failure):
354 self.set_sensitive(True)
355 self.window.set_cursor(arrow_cursor)
356 cid = self.__statusbar_cid
357 sbar = self.__statusbar
358 sbar.pop(cid)
359 gtk.gdk.beep()
360 mid = sbar.push(cid, "Retrieving server list failed")
361 gobject.timeout_add(4000, lambda: sbar.remove(cid, mid))
362 return failure
364 def __cb_selection_changed(self, treesel):
365 model, iter = treesel.get_selected()
366 if iter is None:
367 ok_to_join = False
368 else:
369 data = galcon.net.ServerInfo(*model[iter])
370 ok_to_join = data.bots_ok
371 self.__join_button.set_sensitive(ok_to_join)
373 def __cb_join(self, button):
374 treesel = self.__treeview.get_selection()
375 model, iter = treesel.get_selected()
376 server_info = galcon.net.ServerInfo(*model[iter])
377 if server_info.pwd_protected:
378 self.__run_server_password_prompt(server_info)
379 return None
380 self.join_with_server_info(server_info)
382 def __run_server_password_prompt(self, server_info):
383 n, o = server_info["name"], server_info["owner"]
384 if n: n += " - "
385 s = "Password for %s%s:" % (n, o)
386 self.__server_password_prompt_label.set_text(s)
387 response_id = self.__server_password_prompt.run()
388 if response_id != gtk.RESPONSE_ACCEPT: return None
389 passwd = self.__server_password_prompt_entry.get_text()
390 server_info["passwd"] = passwd
391 self.join_with_server_info(server_info)
393 def join_with_server_info(self, server_info):
394 chat_window = GalconChatWindow(self.__user_info, server_info)
395 gobject.idle_add(self.destroy)
398 class GalconLoginWindow(WindowF12RawConsoleMixin):
400 A Window asking the user for Galcon login details and log in.
403 def __init__(self):
404 super(GalconLoginWindow, self).__init__()
405 self.set_title("GtkGalcon Login")
406 self.set_default_size(400, -1)
407 self.set_position(gtk.WIN_POS_CENTER)
409 outer_vbox, self.inner_vbox = gnome_hig(self)
411 table = gnome_hig(self.inner_vbox(Table(), False, False))
413 xop = {"xoptions": gtk.FILL}
414 table.add_rows()
415 table.attach_cell(LeftLabel("Email Address:"), **xop)
416 self.email_entry = table.attach_cell(Entry())
418 table.add_rows()
419 table.attach_cell(LeftLabel("Username:"), **xop)
420 self.name_entry = table.attach_cell(Entry())
421 self.name_entry.set_text("gtkgalcon")
423 table.add_rows()
424 table.attach_cell(LeftLabel("Password:"), **xop)
425 self.passwd_entry = table.attach_cell(Entry())
426 self.passwd_entry.set_visibility(False)
428 table.add_rows()
429 self.login_button = table.attach_row(Button("Sign In"))
430 self.login_button.connect("clicked", self.__login)
431 self.login_button.set_property("can-default", True)
432 gobject.idle_add(self.login_button.grab_default)
434 self.statusbar = outer_vbox(Statusbar(), False, False)
435 self.statusbar_cid = cid = self.statusbar.get_context_id("msg")
436 mid = self.statusbar.push(cid, "Enter your login details")
437 gobject.timeout_add(4000, lambda: self.statusbar.remove(cid, mid))
439 def __get_user_info(self):
440 email = self.email_entry.get_text()
441 name = self.name_entry.get_text()
442 passwd = self.passwd_entry.get_text()
443 platform = "linux2"
444 version = "1.2.1"
445 return galcon.net.UserInfo(email, name, passwd, platform, version)
447 def __login(self, button):
448 self.window.set_cursor(watch_cursor)
449 self.inner_vbox.set_sensitive(False)
450 self.statusbar.push(self.statusbar_cid,
451 "Retrieving server list...")
452 user_info = self.__get_user_info()
453 deferred = galcon.net.get_server_list(user_info)
454 deferred.addCallbacks(self.__server_list_callback,
455 self.__server_list_errback)
456 deferred.addErrback(print_errback)
458 def __server_list_callback(self, (current_version, server_info_list)):
459 w = GalconServerListWindow(self.__get_user_info())
460 w.set_current_version(current_version)
461 w.set_server_info_list(server_info_list)
462 gobject.idle_add(self.destroy)
464 def __server_list_errback(self, failure):
465 failure.trap(galcon.net.ServerListException)
466 self.inner_vbox.set_sensitive(True)
467 self.window.set_cursor(arrow_cursor)
468 cid = self.statusbar_cid
469 self.statusbar.pop(cid)
470 gtk.gdk.beep()
471 mid = self.statusbar.push(cid, "Failure: %s" %
472 failure.getErrorMessage())
473 gobject.timeout_add(4000, lambda: self.statusbar.remove(cid, mid))
474 return None
477 def main(argv):
478 GalconLoginWindow()
479 main_loop_run()
480 return 0
482 if __name__ == "__main__":
483 import sys
484 sys.exit(main(sys.argv))