Some fixes
[galtack.git] / gtkgalcon.py
blobe115974fd1378b1224d41d16a42a9a93bc6d5a7b
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,1,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.send_commands(
225 (1, "logout"),
226 (1, "[PING]"),
229 def __cb_send(self, button):
230 msg = self.input_entry.get_text()
231 self.galcon_client.send_commands((1, "message", msg))
232 self.input_entry.set_text("")
234 def __cmd_message(self, command):
235 sender, message = command[3:]
236 snd = ""
237 if sender: snd = " " + sender
238 self.__buf("%s%s %s\n" % (time.strftime("%X"), snd, message))
239 if "BOTQUIT" in message:
240 self.galcon_client.send_commands(
241 (1, "logout"),
242 (1, "[PING]"),
244 w = Window()
245 w.set_position(gtk.WIN_POS_CENTER)
246 w.present()
247 o, i = gnome_hig(w)
248 i(Label("Bye!")).modify_font(pango.FontDescription("sans 32"))
249 gobject.timeout_add(2000, main_loop_quit)
251 def __cmd_close(self, command):
252 self.galcon_client.send_commands((1, "[CLOSE]"))
253 gobject.timeout_add(500, main_loop_quit) # TODO: await ACK
255 def __cmd_stop(self, command):
256 self.__buf("(stop)\n")
258 def __cmd_start(self, command):
259 self.__buf("(start)\n")
262 class GalconServerListWindow(WindowF12RawConsoleMixin):
264 Window displaying the list of Galcon servers.
267 def __init__(self, user_info):
268 super(GalconServerListWindow, self).__init__()
269 self.__user_info = user_info
270 self.set_title("Galcon Server List")
271 self.set_default_size(600, 400)
272 self.set_position(gtk.WIN_POS_CENTER)
274 outer_vbox, self.__inner_vbox = gnome_hig(self)
276 hbox = gnome_hig(self.__inner_vbox(HBox(), False, False))
277 self.__version_label = hbox(LeftLabel(""), False, False)
278 self.set_current_version()
279 self.__version_label.set_sensitive(False)
281 self.__refresh_button = hbox(Button("Refresh List"), False, False,
282 pack_end = True)
283 self.__refresh_button.connect("clicked", self.__cb_refresh)
285 sw = self.__inner_vbox(Frame())(ScrolledWindow())
286 self.__treeview = sw(TreeView())
287 self.__treeview.set_sensitive(False)
288 cb = self.__cb_selection_changed
289 self.__treeview.get_selection().connect("changed", cb)
291 for i, spec in enumerate(galcon.net.ServerInfo.COL_SPEC):
292 if not spec["visible"]: continue
293 col = gtk.TreeViewColumn(spec["caption"])
294 col.set_reorderable(True)
295 col.set_sort_column_id(i)
296 self.__treeview.append_column(col)
297 cell = gtk.CellRendererText()
298 col.pack_start(cell, True)
299 col.add_attribute(cell, "text", i)
300 col.add_attribute(cell, "sensitive", 0)
302 self.__join_button = self.__inner_vbox(Button("Join"), False,
303 False)
304 self.__join_button.set_sensitive(False)
305 self.__join_button.connect("clicked", self.__cb_join)
307 self.__statusbar = outer_vbox(Statusbar(), False, False)
308 self.__statusbar_cid = cid = self.__statusbar.get_context_id("msg")
310 self.__server_password_prompt = dlg = Dialog(
311 "Server Password Required", self, gtk.DIALOG_MODAL,
312 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
313 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
315 dlg.set_default_response(gtk.RESPONSE_ACCEPT)
316 dlg.set_default_size(300, -1)
317 vbox = gnome_hig(VBox()) # I want my own VBox here ;-)
318 vbox.set_border_width(6)
319 dlg.vbox.pack_start(vbox)
320 self.__server_password_prompt_label = vbox(LeftLabel())
321 self.__server_password_prompt_entry = vbox(Entry())
322 self.__server_password_prompt_entry.set_visibility(False)
324 def set_current_version(self, current_version = None):
325 self.__version_label.set_sensitive(False)
326 if current_version is None:
327 self.__version_label.set_text("Current version: ???")
328 else:
329 self.__version_label.set_text("Current version: %s" %
330 ".".join(current_version))
331 self.__version_label.set_sensitive(True)
333 def set_server_info_list(self, server_info_list = None):
334 types = galcon.net.ServerInfo.get_col_types()
335 model = gtk.ListStore(*types)
336 self.__treeview.set_sensitive(False)
337 if server_info_list is not None:
338 for server_info in server_info_list:
339 model.append(server_info.get_data_tuple())
340 self.__treeview.set_sensitive(True)
341 self.__treeview.set_model(model)
343 def __cb_refresh(self, button):
344 self.window.set_cursor(watch_cursor)
345 self.__inner_vbox.set_sensitive(False)
346 self.__statusbar.push(self.__statusbar_cid,
347 "Retrieving server list...")
348 deferred = galcon.net.get_server_list(self.__user_info)
349 deferred.addCallback(self.__server_list_callback)
350 deferred.addErrback(self.__server_list_errback)
352 def __server_list_callback(self, (current_version, server_info_list)):
353 self.set_current_version(current_version)
354 self.set_server_info_list(server_info_list)
355 self.__statusbar.pop(self.__statusbar_cid)
356 self.__inner_vbox.set_sensitive(True)
357 self.window.set_cursor(arrow_cursor)
359 def __server_list_errback(self, failure):
360 self.set_sensitive(True)
361 self.window.set_cursor(arrow_cursor)
362 cid = self.__statusbar_cid
363 sbar = self.__statusbar
364 sbar.pop(cid)
365 gtk.gdk.beep()
366 mid = sbar.push(cid, "Retrieving server list failed")
367 gobject.timeout_add(4000, lambda: sbar.remove(cid, mid))
368 return failure
370 def __cb_selection_changed(self, treesel):
371 model, iter = treesel.get_selected()
372 if iter is None:
373 ok_to_join = False
374 else:
375 data = galcon.net.ServerInfo(*model[iter])
376 ok_to_join = data.bots_ok
377 self.__join_button.set_sensitive(ok_to_join)
379 def __cb_join(self, button):
380 treesel = self.__treeview.get_selection()
381 model, iter = treesel.get_selected()
382 server_info = galcon.net.ServerInfo(*model[iter])
383 if server_info.pwd_protected:
384 self.__run_server_password_prompt(server_info)
385 return None
386 self.join_with_server_info(server_info)
388 def __run_server_password_prompt(self, server_info):
389 n, o = server_info["name"], server_info["owner"]
390 if n: n += " - "
391 s = "Password for %s%s:" % (n, o)
392 self.__server_password_prompt_label.set_text(s)
393 response_id = self.__server_password_prompt.run()
394 if response_id != gtk.RESPONSE_ACCEPT: return None
395 passwd = self.__server_password_prompt_entry.get_text()
396 server_info["passwd"] = passwd
397 self.join_with_server_info(server_info)
399 def join_with_server_info(self, server_info):
400 chat_window = GalconChatWindow(self.__user_info, server_info)
401 gobject.idle_add(self.destroy)
404 class GalconLoginWindow(WindowF12RawConsoleMixin):
406 A Window asking the user for Galcon login details and log in.
409 def __init__(self):
410 super(GalconLoginWindow, self).__init__()
411 self.set_title("GtkGalcon Login")
412 self.set_default_size(400, -1)
413 self.set_position(gtk.WIN_POS_CENTER)
415 outer_vbox, self.inner_vbox = gnome_hig(self)
417 table = gnome_hig(self.inner_vbox(Table(), False, False))
419 xop = {"xoptions": gtk.FILL}
420 table.add_rows()
421 table.attach_cell(LeftLabel("Email Address:"), **xop)
422 self.email_entry = table.attach_cell(Entry())
424 table.add_rows()
425 table.attach_cell(LeftLabel("Username:"), **xop)
426 self.name_entry = table.attach_cell(Entry())
427 self.name_entry.set_text("gtkgalcon")
429 table.add_rows()
430 table.attach_cell(LeftLabel("Password:"), **xop)
431 self.passwd_entry = table.attach_cell(Entry())
432 self.passwd_entry.set_visibility(False)
434 table.add_rows()
435 self.login_button = table.attach_row(Button("Sign In"))
436 self.login_button.connect("clicked", self.__login)
437 self.login_button.set_property("can-default", True)
438 gobject.idle_add(self.login_button.grab_default)
440 self.statusbar = outer_vbox(Statusbar(), False, False)
441 self.statusbar_cid = cid = self.statusbar.get_context_id("msg")
442 mid = self.statusbar.push(cid, "Enter your login details")
443 gobject.timeout_add(4000, lambda: self.statusbar.remove(cid, mid))
445 def __get_user_info(self):
446 email = self.email_entry.get_text()
447 name = self.name_entry.get_text()
448 passwd = self.passwd_entry.get_text()
449 platform = "linux2"
450 version = "1.2.1"
451 return galcon.net.UserInfo(email, name, passwd, platform, version)
453 def __login(self, button):
454 self.window.set_cursor(watch_cursor)
455 self.inner_vbox.set_sensitive(False)
456 self.statusbar.push(self.statusbar_cid,
457 "Retrieving server list...")
458 user_info = self.__get_user_info()
459 deferred = galcon.net.get_server_list(user_info)
460 deferred.addCallbacks(self.__server_list_callback,
461 self.__server_list_errback)
462 deferred.addErrback(print_errback)
464 def __server_list_callback(self, (current_version, server_info_list)):
465 w = GalconServerListWindow(self.__get_user_info())
466 w.set_current_version(current_version)
467 w.set_server_info_list(server_info_list)
468 gobject.idle_add(self.destroy)
470 def __server_list_errback(self, failure):
471 failure.trap(galcon.net.ServerListException)
472 self.inner_vbox.set_sensitive(True)
473 self.window.set_cursor(arrow_cursor)
474 cid = self.statusbar_cid
475 self.statusbar.pop(cid)
476 gtk.gdk.beep()
477 mid = self.statusbar.push(cid, "Failure: %s" %
478 failure.getErrorMessage())
479 gobject.timeout_add(4000, lambda: self.statusbar.remove(cid, mid))
480 return None
483 def main(argv):
484 GalconLoginWindow()
485 main_loop_run()
486 return 0
488 if __name__ == "__main__":
489 import sys
490 sys.exit(main(sys.argv))