2 # -*- coding: utf-8 -*-
4 GalTacK - An unofficial PyGTK client to the Galcon multiplayer game.
6 # Copyright (C) 2007 Michael Carter
7 # Copyright (C) 2007 Felix Rabe <public@felixrabe.textdriven.com>
9 # Permission is hereby granted, free of charge, to any person obtaining a
10 # copy of this software and associated documentation files (the
11 # "Software"), to deal in the Software without restriction, including
12 # without limitation the rights to use, copy, modify, merge, publish,
13 # distribute, sublicense, and/or sell copies of the Software, and to permit
14 # persons to whom the Software is furnished to do so, subject to the
15 # following conditions:
17 # The above copyright notice and this permission notice shall be included
18 # in all copies or substantial portions of the Software.
20 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
25 # OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
26 # THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 # Recommended line length or text width: 75 characters.
30 galtack_version
= (0,2,0)
31 galtack_version_str
= ".".join(map(str, galtack_version
))
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
48 galtack
.net
.GaltackClientRecvCmdLoggerMixin
,
49 galtack
.net
.GaltackClientHousekeeperMixin
,
50 galtack
.net
.GaltackClientUniverseTrackerMixin
,
51 galtack
.net
.GaltackClientBase
,
55 ### CUSTOMIZED STUFF FROM PYGTK SHELL [START]
61 class RawConsoleWithIntroMixin(RawConsoleBase
):
63 Raw console running an example script on initialization.
65 Customized for GalTacK.
68 def __init__(self
, *a
, **kw
):
69 super(RawConsoleWithIntroMixin
, self
).__init
__(*a
, **kw
)
70 buf
= self
.code_view
.get_buffer()
71 msg
= a_("Press F5 or Ctrl+E to execute this code.")
72 buf('# -*- coding: utf-8 -*-\n' +
74 'from galtack_client import *\n' +
75 'o = console.output_view.get_buffer()\n' +
77 'c = window.galtack_client\n' +
78 'c.send_commands((1, "message", "Hello, World!"))\n' +
82 class RawConsoleWithIntro(
83 RawConsoleCenterInitMixin
,
84 RawConsoleWithIntroMixin
,
89 class WindowWithRawConsole(Window
):
91 Window with a RawConsole in it and starting at a reasonable size.
94 def __init__(self
, *a
, **kw
):
95 super(WindowWithRawConsole
, self
).__init
__(*a
, **kw
)
96 self
.set_title("PyGTK Shell RawConsole")
97 self
.set_default_size(400, 400)
98 self
.raw_console
= self(RawConsoleWithIntro())
101 class WindowF12RawConsoleMixin(Window
):
103 Window opening a Window containing a RawConsole when F12 is pressed.
106 def __init__(self
, *a
, **kw
):
107 super(WindowF12RawConsoleMixin
, self
).__init
__(*a
, **kw
)
108 self
.connect("key-press-event", self
.__cb
_key
_press
_event
)
110 def __cb_key_press_event(self
, window
, event
):
111 if (KeyPressEval("F12"))(event
):
112 rc
= WindowWithRawConsole().raw_console
113 rc
.code_view
.textview_userexec_namespace
["window"] = self
118 class WindowF12RawConsole(
119 WindowF12RawConsoleMixin
,
124 ### CUSTOMIZED STUFF FROM PYGTK SHELL [END]
127 def print_errback(failure
):
128 failure
.printTraceback()
132 class UniverseView(DrawingArea
):
138 super(UniverseView
, self
).__init
__()
140 self
.connect("expose-event", self
.__cb
_expose
_event
)
141 self
.set_size_request(640, 480)
144 self
.planet_info_list
= []
146 def __cb_expose_event(self
, widget
, event
):
147 x
, y
, w
, h
= self
.allocation
148 cr
= self
.window
.cairo_create()
151 cr
.rectangle(0, 0, w
, h
)
152 cr
.set_source_rgba(0, 0, 0, 1)
156 cr
.set_source_rgba(1, 1, 1, 1)
157 for planet_info
in self
.planet_info_list
:
158 cr
.arc(planet_info
.x
, planet_info
.y
, 10, 0, PI2
)
162 if self
.window
is not None:
163 self
.window
.invalidate_rect(self
.allocation
, False)
167 class GaltackUniverseWindow(WindowF12RawConsoleMixin
):
169 A Window displaying a view of the Galcon universe.
173 super(GaltackUniverseWindow
, self
).__init
__()
174 self
.set_title("Universe Window - GalTacK")
175 self
.universe_view
= self(UniverseView())
178 class GaltackChatWindow(WindowF12RawConsoleMixin
):
180 A Window to allow chatting in Galcon, following the Galcon client
184 def __init__(self
, options
, user_info
, server_info
):
185 self
.__options
= options
186 self
.__user
_info
= user_info
187 self
.__server
_info
= server_info
189 super(GaltackChatWindow
, self
).__init
__()
190 self
.set_default_size(400, 300)
191 n
, o
= server_info
["name"], server_info
["owner"]
193 self
.set_title("Chat (%s%s) - GalTacK" % (n
, o
))
194 self
.connect("delete-event", self
.__cb
_delete
_event
)
196 outer_vbox
, inner_vbox
= gnome_hig(self
)
198 sw
= inner_vbox(Frame())(ScrolledWindow())
199 self
.output_view
= sw(TextView())
200 self
.__buf
= self
.output_view
.get_buffer()
201 self
.output_view
.set_editable(False)
202 hbox
= gnome_hig(inner_vbox(HBox(), False, False))
203 self
.input_entry
= hbox(Entry())
204 self
.send_button
= hbox(Button("_Send"), False, False)
205 self
.send_button
.connect("clicked", self
.__cb
_send
_clicked
)
206 self
.leave_button
= hbox(Button("_Leave"), False, False)
207 self
.leave_button
.connect("clicked", self
.__cb
_leave
_clicked
)
208 self
.quit_button
= hbox(Button("_Quit"), False, False)
209 self
.quit_button
.connect("clicked", self
.__cb
_quit
_clicked
)
211 self
.send_button
.set_property("can-default", True)
212 gobject
.idle_add(self
.send_button
.grab_default
)
213 gobject
.idle_add(self
.input_entry
.grab_focus
)
216 self
.galtack_client
= C(self
.__options
,
217 self
.__user
_info
, self
.__server
_info
)
218 r
= self
.galtack_client
.register_command_callback
219 r("close", self
.__cmd
_close
)
220 r("message", self
.__cmd
_message
)
221 r("start", self
.__cmd
_start
)
222 r("stop", self
.__cmd
_stop
)
223 reactor
.listenUDP(0, self
.galtack_client
)
225 def f(): # TODO: implement this properly after login
226 self
.galtack_client
.send_commands((1, "status", "away"))
228 gobject
.timeout_add(500, f
)
230 def __cb_delete_event(self
, widget
, event
):
231 self
.leave_button
.clicked()
234 def __cb_leave_clicked(self
, button
):
235 self
.set_sensitive(False)
236 deferred
= self
.galtack_client
.logout()
237 deferred
.addCallback(self
.__cb
_left
)
238 deferred
.addErrback(self
.__eb
_left
)
240 def __cb_left(self
, ignored_arg
):
241 # GaltackLoginWindow._instance.show()
242 GaltackServerListWindow
._instance
.show()
245 def __eb_left(self
, ignored_arg
):
246 self
.set_sensitive(True)
248 def __cb_quit_clicked(self
, button
):
249 self
.set_sensitive(False)
250 deferred
= self
.galtack_client
.logout()
251 deferred
.addCallback(self
.__cb
_left
_quit
)
252 deferred
.addErrback(self
.__cb
_left
_quit
)
254 def __cb_left_quit(self
, ignored_arg
):
257 def __cb_send_clicked(self
, button
):
258 msg
= self
.input_entry
.get_text()
259 self
.galtack_client
.send_commands((1, "message", msg
))
260 self
.input_entry
.set_text("")
262 def __cmd_message(self
, command
):
263 sender
, message
= command
[3:]
265 if sender
: snd
= " " + sender
266 self
.__buf
("%s%s %s\n" % (time
.strftime("%X"), snd
, message
))
267 if "BOTQUIT" in message
:
268 self
.galtack_client
.logout()
270 w
.set_position(gtk
.WIN_POS_CENTER
)
273 i(Label("Bye!")).modify_font(pango
.FontDescription("sans 32"))
274 gobject
.timeout_add(2000, main_loop_quit
)
276 def __cmd_close(self
, command
):
277 self
.galtack_client
.send_commands((1, "[CLOSE]"))
278 gobject
.timeout_add(500, main_loop_quit
) # TODO: await ACK
280 def __cmd_stop(self
, command
):
281 self
.__buf
("(stop)\n")
283 def __cmd_start(self
, command
):
284 self
.__buf
("(start)\n")
287 class GaltackServerListWindow(WindowF12RawConsoleMixin
):
289 Window displaying the list of Galcon servers.
292 def __init__(self
, options
, user_info
):
293 self
.__options
= options
294 super(GaltackServerListWindow
, self
).__init
__()
295 self
.__user
_info
= user_info
296 self
.set_title("Galcon Server List - GalTacK")
297 self
.set_default_size(600, 400)
298 self
.set_position(gtk
.WIN_POS_CENTER
)
300 outer_vbox
, self
.__inner
_vbox
= gnome_hig(self
)
302 hbox
= gnome_hig(self
.__inner
_vbox
(HBox(), False, False))
303 self
.__version
_label
= hbox(LeftLabel(""), False, False)
304 self
.set_current_version()
305 self
.__version
_label
.set_sensitive(False)
307 self
.__refresh
_button
= hbox(Button("_Refresh List"), False, False,
309 self
.__refresh
_button
.connect("clicked", self
.__cb
_refresh
)
311 sw
= self
.__inner
_vbox
(Frame())(ScrolledWindow())
312 self
.__treeview
= sw(TreeView())
313 self
.__treeview
.set_sensitive(False)
314 self
.__treeview
.set_property("rules-hint", True)
315 cb
= self
.__cb
_selection
_changed
316 self
.__treeview
.get_selection().connect("changed", cb
)
318 for i
, spec
in enumerate(galtack
.net
.ServerInfo
.COL_SPEC
):
319 if not spec
["visible"]: continue
320 col
= gtk
.TreeViewColumn(spec
["caption"])
321 col
.set_reorderable(True)
322 col
.set_sort_column_id(i
)
323 self
.__treeview
.append_column(col
)
324 cell
= gtk
.CellRendererText()
325 col
.pack_start(cell
, True)
326 col
.add_attribute(cell
, "text", i
)
327 col
.add_attribute(cell
, "sensitive", 0)
329 hbox
= gnome_hig(self
.__inner
_vbox
(HBox(), False, False))
330 self
.__join
_button
= hbox(Button("_Join"))
331 self
.__join
_button
.set_sensitive(False)
332 self
.__join
_button
.connect("clicked", self
.__cb
_join
)
334 self
.back_button
= hbox(Button("_Back"), False, False)
335 self
.back_button
.connect("clicked", self
.__cb
_back
)
337 self
.__statusbar
= outer_vbox(Statusbar(), False, False)
338 self
.__statusbar
_cid
= cid
= self
.__statusbar
.get_context_id("msg")
340 self
.__server
_password
_prompt
= dlg
= Dialog(
341 "Server Password Required", self
, gtk
.DIALOG_MODAL
,
342 (gtk
.STOCK_CANCEL
, gtk
.RESPONSE_REJECT
,
343 gtk
.STOCK_OK
, gtk
.RESPONSE_ACCEPT
)
345 dlg
.set_default_response(gtk
.RESPONSE_ACCEPT
)
346 dlg
.set_default_size(300, -1)
347 vbox
= gnome_hig(VBox()) # I want my own VBox here ;-)
348 vbox
.set_border_width(6)
349 dlg
.vbox
.pack_start(vbox
)
350 self
.__server
_password
_prompt
_label
= vbox(LeftLabel())
351 self
.__server
_password
_prompt
_entry
= vbox(Entry())
352 self
.__server
_password
_prompt
_entry
.set_visibility(False)
354 def set_current_version(self
, current_version
= None):
355 self
.__version
_label
.set_sensitive(False)
356 if current_version
is None:
357 self
.__version
_label
.set_text("Current version: ???")
359 self
.__version
_label
.set_text("Current version: %s" %
360 ".".join(current_version
))
361 self
.__version
_label
.set_sensitive(True)
363 def set_server_info_list(self
, server_info_list
= None):
364 types
= galtack
.net
.ServerInfo
.get_col_types()
365 model
= gtk
.ListStore(*types
)
366 self
.__treeview
.set_sensitive(False)
367 if server_info_list
is not None:
368 for server_info
in server_info_list
:
369 model
.append(server_info
.get_data_tuple())
370 self
.__treeview
.set_sensitive(True)
371 self
.__treeview
.set_model(model
)
373 def __cb_refresh(self
, button
):
374 self
.window
.set_cursor(watch_cursor
)
375 self
.__inner
_vbox
.set_sensitive(False)
376 self
.__statusbar
.push(self
.__statusbar
_cid
,
377 "Retrieving server list...")
378 deferred
= galtack
.net
.get_server_list(self
.__user
_info
)
379 deferred
.addCallback(self
.__server
_list
_callback
)
380 deferred
.addErrback(self
.__server
_list
_errback
)
382 def __server_list_callback(self
, (current_version
, server_info_list
)):
383 self
.set_current_version(current_version
)
384 self
.set_server_info_list(server_info_list
)
385 self
.__statusbar
.pop(self
.__statusbar
_cid
)
386 self
.__inner
_vbox
.set_sensitive(True)
387 self
.window
.set_cursor(arrow_cursor
)
389 def __server_list_errback(self
, failure
):
390 self
.set_sensitive(True)
391 self
.window
.set_cursor(arrow_cursor
)
392 cid
= self
.__statusbar
_cid
393 sbar
= self
.__statusbar
396 mid
= sbar
.push(cid
, "Retrieving server list failed")
397 gobject
.timeout_add(4000, lambda: sbar
.remove(cid
, mid
))
400 def __cb_selection_changed(self
, treesel
):
401 model
, iter = treesel
.get_selected()
405 data
= galtack
.net
.ServerInfo(*model
[iter])
406 ok_to_join
= data
.bots_ok
407 self
.__join
_button
.set_sensitive(ok_to_join
)
409 def __cb_join(self
, button
):
410 treesel
= self
.__treeview
.get_selection()
411 model
, iter = treesel
.get_selected()
412 server_info
= galtack
.net
.ServerInfo(*model
[iter])
413 if server_info
.pwd_protected
:
414 self
.__run
_server
_password
_prompt
(server_info
)
416 self
.join_with_server_info(server_info
)
418 def __cb_back(self
, button
):
419 GaltackLoginWindow
._instance
.show()
422 def __run_server_password_prompt(self
, server_info
):
423 n
, o
= server_info
["name"], server_info
["owner"]
425 s
= "Password for %s%s:" % (n
, o
)
426 self
.__server
_password
_prompt
_label
.set_text(s
)
427 response_id
= self
.__server
_password
_prompt
.run()
428 if response_id
!= gtk
.RESPONSE_ACCEPT
: return None
429 passwd
= self
.__server
_password
_prompt
_entry
.get_text()
430 server_info
["passwd"] = passwd
431 self
.join_with_server_info(server_info
)
433 def join_with_server_info(self
, server_info
):
434 chat_window
= GaltackChatWindow(self
.__options
,
435 self
.__user
_info
, server_info
)
436 gobject
.idle_add(self
.hide
)
439 class GaltackLoginWindow(WindowF12RawConsoleMixin
):
441 A Window asking the user for Galcon login details and log in.
444 def __init__(self
, options
):
445 self
.__options
= options
446 super(GaltackLoginWindow
, self
).__init
__()
447 self
.set_title("GalTacK Login")
448 self
.set_default_size(400, -1)
449 self
.set_position(gtk
.WIN_POS_CENTER
)
451 outer_vbox
, self
.inner_vbox
= gnome_hig(self
)
453 table
= gnome_hig(self
.inner_vbox(Table(), False, False))
455 xop
= {"xoptions": gtk
.FILL
}
457 label
= table
.attach_cell(LeftLabel("_Email Address:"), **xop
)
458 self
.email_entry
= table
.attach_cell(Entry())
459 label
.set_mnemonic_widget(self
.email_entry
)
460 if self
.__options
.email
is not None:
461 self
.email_entry
.set_text(self
.__options
.email
)
464 label
= table
.attach_cell(LeftLabel("_Username:"), **xop
)
465 self
.name_entry
= table
.attach_cell(Entry())
466 label
.set_mnemonic_widget(self
.name_entry
)
467 if self
.__options
.user
is not None:
468 self
.name_entry
.set_text(self
.__options
.user
)
471 label
= table
.attach_cell(LeftLabel("_Password:"), **xop
)
472 self
.passwd_entry
= table
.attach_cell(Entry())
473 label
.set_mnemonic_widget(self
.passwd_entry
)
474 self
.passwd_entry
.set_visibility(False)
475 if self
.__options
.password
is not None:
476 self
.passwd_entry
.set_text(self
.__options
.password
)
479 hbox
= gnome_hig(table
.attach_row(HBox()))
480 self
.__login
_button
= hbox(Button("_Sign In"))
481 self
.__login
_button
.connect("clicked", self
.__cb
_login
)
482 self
.__login
_button
.set_property("can-default", True)
483 gobject
.idle_add(self
.__login
_button
.grab_default
)
485 self
.__quit
_button
= hbox(Button("_Quit"), False, False)
486 self
.__quit
_button
.connect("clicked", self
.__cb
_quit
)
488 self
.statusbar
= outer_vbox(Statusbar(), False, False)
489 self
.statusbar_cid
= cid
= self
.statusbar
.get_context_id("msg")
490 mid
= self
.statusbar
.push(cid
, "Enter your login details")
491 gobject
.timeout_add(4000, lambda: self
.statusbar
.remove(cid
, mid
))
493 def __get_user_info(self
):
494 email
= self
.email_entry
.get_text()
495 name
= self
.name_entry
.get_text()
496 passwd
= self
.passwd_entry
.get_text()
499 return galtack
.net
.UserInfo(email
, name
, passwd
, platform
, version
)
501 def __cb_login(self
, button
):
502 self
.window
.set_cursor(watch_cursor
)
503 self
.inner_vbox
.set_sensitive(False)
504 self
.statusbar
.push(self
.statusbar_cid
,
505 "Retrieving server list...")
506 user_info
= self
.__get
_user
_info
()
507 deferred
= galtack
.net
.get_server_list(user_info
)
508 deferred
.addCallbacks(self
.__server
_list
_callback
,
509 self
.__server
_list
_errback
)
510 deferred
.addErrback(print_errback
)
512 def __cb_quit(self
, button
):
515 def __server_list_callback(self
, (current_version
, server_info_list
)):
516 w
= GaltackServerListWindow(self
.__options
, self
.__get
_user
_info
())
517 GaltackServerListWindow
._instance
= w
518 w
.set_current_version(current_version
)
519 w
.set_server_info_list(server_info_list
)
520 gobject
.idle_add(self
.hide
)
521 self
.inner_vbox
.set_sensitive(True)
522 self
.window
.set_cursor(arrow_cursor
)
523 cid
= self
.statusbar_cid
524 self
.statusbar
.pop(cid
)
526 def __server_list_errback(self
, failure
):
527 failure
.trap(galtack
.net
.ServerListException
)
528 self
.inner_vbox
.set_sensitive(True)
529 self
.window
.set_cursor(arrow_cursor
)
530 cid
= self
.statusbar_cid
531 self
.statusbar
.pop(cid
)
533 mid
= self
.statusbar
.push(cid
, "Failure: %s" %
534 failure
.getErrorMessage())
535 gobject
.timeout_add(4000, lambda: self
.statusbar
.remove(cid
, mid
))
538 @classmethod # for potential inheritance
539 def _modify_option_parser(cls
, option_parser
):
540 option_parser
.add_option("-e", "--email", metavar
= "ADDRESS",
541 help = "login email address")
542 option_parser
.add_option("-u", "--user",
543 help = "login username")
544 option_parser
.add_option("-p", "--password",
545 help = "login password")
550 option_parser
= optparse
.OptionParser(prog
= "GalTacK",
551 version
= "%%prog %s" %
553 option_parser
= GaltackLoginWindow
._modify
_option
_parser
(option_parser
)
554 option_parser
= GaltackClient
._modify
_option
_parser
(option_parser
)
555 options
, arguments
= option_parser
.parse_args(argv
[1:])
556 GaltackLoginWindow
._instance
= GaltackLoginWindow(options
)
560 if __name__
== "__main__":
562 sys
.exit(main(sys
.argv
))