6 from gi.repository import Gdk
8 from gi.repository import GObject, GLib, Gdk, Gio, Gtk, Chimara
13 gettext.install(config.GETTEXT_PACKAGE, config.PACKAGE_LOCALE_DIR,
14 unicode=True, codeset='UTF-8')
19 class Player(GObject.GObject):
20 __gtype_name__ = 'ChimaraPlayer'
22 def __init__(self, graphics_file=None):
23 super(Player, self).__init__()
25 # Initialize settings file; it can be overridden by a "chimara-config"
26 # file in the current directory
27 if os.path.exists('chimara-config'):
28 keyfile = 'chimara-config'
30 keyfile = os.path.expanduser('~/.chimara/config')
32 # This only works on my custom-built gobject-introspection; opened
34 backend = Gio.keyfile_settings_backend_new(keyfile,
35 "/org/chimara-if/player/", None)
36 except AttributeError:
38 self.prefs_settings = Gio.Settings('org.chimara-if.player.preferences',
40 self.state_settings = Gio.Settings('org.chimara-if.player.state',
43 builder = Gtk.Builder()
45 builder.add_from_file(os.path.join(config.PACKAGE_DATA_DIR,
49 builder.add_from_file(os.path.join(config.PACKAGE_SRC_DIR,
53 self.window = builder.get_object('chimara')
54 self.aboutwindow = builder.get_object('aboutwindow')
55 self.prefswindow = builder.get_object('prefswindow')
56 actiongroup = builder.get_object('actiongroup')
58 # Set the default value of the "View/Toolbar" menu item upon creation
59 # of a new window to the "show-toolbar-default" setting, but bind the
60 # setting one-way only - we don't want toolbars to disappear suddenly
61 toolbar_action = builder.get_object('toolbar')
62 toolbar_action.active = \
63 self.state_settings.get_boolean('show-toolbar-default')
64 self.state_settings.bind('show-toolbar-default', toolbar_action,
65 'active', Gio.SettingsBindFlags.SET)
67 filt = Gtk.RecentFilter()
68 # TODO: Use mimetypes and construct the filter dynamically depending on
69 # what plugins are installed
70 for pattern in ['*.z[1-8]', '*.[zg]lb', '*.[zg]blorb', '*.ulx', '*.blb',
72 filt.add_pattern(pattern)
73 recent = builder.get_object('recent')
74 recent.add_filter(filt)
76 uimanager = Gtk.UIManager()
78 uimanager.add_ui_from_file(os.path.join(config.PACKAGE_DATA_DIR,
82 uimanager.add_ui_from_file(os.path.join(config.PACKAGE_SRC_DIR,
86 uimanager.insert_action_group(actiongroup)
87 menubar = uimanager.get_widget('/menubar')
88 toolbar = uimanager.get_widget('/toolbar')
89 toolbar.no_show_all = True
90 if toolbar_action.active:
95 # Connect the accelerators
96 accels = uimanager.get_accel_group()
97 self.window.add_accel_group(accels)
99 self.glk = Chimara.IF(ignore_errors=True,
100 # interpreter_number=Chimara.IFZmachineVersion.TANDY_COLOR,
101 graphics_file=graphics_file)
102 css_file = _maybe(self.prefs_settings.get_value('css-file'))
104 css_file = 'style.css'
105 self.glk.set_css_from_file(css_file)
107 # DON'T UNCOMMENT THIS your eyes will burn
108 # but it is a good test of programmatically altering just one style
109 # self.glk.set_css_from_string("buffer{font-family: 'Comic Sans MS';}")
111 vbox = builder.get_object('vbox')
112 vbox.pack_end(self.glk, True, True, 0)
113 vbox.pack_start(menubar, False, False, 0)
114 vbox.pack_start(toolbar, False, False, 0)
116 builder.connect_signals(self)
117 self.glk.connect('notify::program-name', self.change_window_title)
118 self.glk.connect('notify::story-name', self.change_window_title)
120 # Create preferences window
123 def change_window_title(self, glk, pspec, data=None):
124 if glk.props.program_name is None:
126 elif glk.props.story_name is None:
127 title = "{interp} - Chimara".format(interp=glk.props.program_name)
129 title = "{interp} - {story} - Chimara".format(
130 interp=glk.props.program_name,
131 story=glk.props.story_name)
132 self.window.props.title = title
134 def on_open_activate(self, action, data=None):
135 if not self.confirm_open_new_game():
138 dialog = Gtk.FileChooserDialog(_('Open Game'), self.window,
139 Gtk.FileChooserAction.OPEN,
140 (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
141 Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT))
143 # Get last opened path
144 path = _maybe(self.state_settings.get_value('last-open-path'))
146 dialog.set_current_folder(path)
148 response = dialog.run()
150 if response != Gtk.ResponseType.ACCEPT:
153 gamefile = dialog.get_file()
154 self.search_for_graphics_file(gamefile.get_path())
156 self.glk.run_game_file(gamefile)
157 except GLib.Error as e:
158 error_dialog(self.window, _('Could not open game file {filename}: {errmsg}').format(filename=gamefile.get_path(), errmsg=e.message))
161 path = dialog.get_current_folder()
163 self.state_settings.last_open_path = path
165 # Add file to recent files list
166 manager = Gtk.RecentManager.get_default()
167 uri = gamefile.get_uri()
168 manager.add_item(uri)
172 def on_recent_item_activated(self, chooser, data=None):
173 if not self.confirm_open_new_game():
176 uri = chooser.get_current_uri()
177 gamefile = Gio.file_new_for_uri(uri)
179 self.search_for_graphics_file(gamefile.get_path())
181 self.glk.run_game_file(gamefile)
182 except GLib.Error as e:
183 error_dialog(self.window,
184 _('Could not open game file {filename}: {errmsg}').format(
185 filename=gamefile.get_basename(),
189 # Add file to recent files list again, this updates it to most recently
191 manager = Gtk.RecentManager.get_default()
192 manager.add_item(uri)
194 def on_stop_activate(self, *args):
197 def on_quit_chimara_activate(self, *args):
200 def on_copy_activate(self, *args):
201 focus = self.window.get_focus()
202 # Call "copy clipboard" on any widget that defines it
203 if (isinstance(focus, Gtk.Label)
204 or isinstance(focus, Gtk.Entry)
205 or isinstance(focus, Gtk.TextView)):
206 focus.emit('copy-clipboard')
208 def on_paste_activate(self, *args):
209 focus = self.window.get_focus()
210 # Call "paste clipboard" on any widget that defines it
211 if isinstance(focus, Gtk.Entry) or isinstance(focus, Gtk.TextView):
212 focus.emit('paste-clipboard')
214 def on_preferences_activate(self, *args):
215 self.prefswindow.present()
217 def on_toolbar_toggled(self, action, *args):
218 if action.get_active():
223 def on_undo_activate(self, *args):
224 self.glk.feed_line_input('undo')
226 def on_save_activate(self, *args):
227 self.glk.feed_line_input('save')
229 def on_restore_activate(self, *args):
230 self.glk.feed_line_input('restore')
232 def on_restart_activate(self, *args):
233 self.glk.feed_line_input('restart')
235 def on_quit_activate(self, *args):
236 self.glk.feed_line_input('quit')
238 def on_about_activate(self, *args):
239 self.aboutwindow.set_version(config.PACKAGE_VERSION)
240 self.aboutwindow.present()
242 def on_window_delete_event(self, *args):
246 def confirm_open_new_game(self):
248 If a game is running in the Glk widget, warn the user that they will
249 quit the currently running game if they open a new one. Returns True if
250 no game was running. Returns False if the user cancelled. Returns True
251 and shuts down the running game if the user wishes to continue.
253 if not self.glk.props.running:
256 dialog = Gtk.MessageDialog(self.window,
257 Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
258 Gtk.MessageType.WARNING, Gtk.ButtonsType.CANCEL,
259 _("Are you sure you want to open a new game?"))
260 dialog.format_secondary_text(
261 _("If you open a new game, you will quit the one you are "
262 "currently playing."))
263 dialog.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
264 response = dialog.run()
267 if response != Gtk.ResponseType.OK:
274 def search_for_graphics_file(self, filename):
275 """Internal function: See if there is a corresponding graphics file"""
277 # First get the name of the story file
278 base = os.path.basename(filename)
279 base_noext = os.path.splitext(base)[0]
281 # Check in the stored resource path, if set
282 resource_path = _maybe(self.prefs_settings.get_value('resource-path'))
284 # Otherwise check in the current directory
285 if resource_path is None:
286 resource_path = os.path.dirname(filename)
288 blorbfile = os.path.join(resource_path, base_noext + '.blb')
289 if os.path.exists(blorbfile):
290 self.glk.graphics_file = blorbfile
292 # Various signal handlers for GtkBuilder file
293 def gtk_widget_hide(self, widget, *args):
294 return Gtk.Widget.hide(widget)
296 def gtk_widget_hide_on_delete(self, widget, *args):
297 return Gtk.Widget.hide_on_delete(widget)
299 def dummy_handler(self, *args):
302 on_resource_file_set = dummy_handler
303 on_interpreter_cell_changed = dummy_handler
304 on_toggle_underline = dummy_handler
305 on_toggle_italic = dummy_handler
306 on_toggle_bold = dummy_handler
307 on_toggle_justify = dummy_handler
308 on_toggle_right = dummy_handler
309 on_toggle_center = dummy_handler
310 on_toggle_left = dummy_handler
311 on_background_color_set = dummy_handler
312 on_foreground_color_set = dummy_handler
313 on_font_set = dummy_handler
314 on_css_filechooser_file_set = dummy_handler
318 """Gets a maybe value from a GVariant - not handled in PyGI"""
319 v = variant.get_maybe()
325 def error_dialog(parent, message):
326 dialog = Gtk.MessageDialog(parent, Gtk.DialogFlags.DESTROY_WITH_PARENT,
327 Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, message)
331 if __name__ == '__main__':
332 parser = argparse.ArgumentParser()
333 parser.add_argument('game_file', nargs='?', default=None,
334 metavar='GAME FILE', help='the game file to load and start')
335 parser.add_argument('graphics_file', nargs='?', default=None,
336 metavar='GRAPHICS FILE', help='a Blorb resource file to include')
337 args = parser.parse_args()
339 # Create configuration dir ~/.chimara
341 os.mkdir(os.path.expanduser('~/.chimara'))
344 assert os.path.isdir(os.path.expanduser('~/.chimara'))
346 player = Player(graphics_file=args.graphics_file)
347 player.window.show_all()
349 if args.game_file is not None:
351 player.glk.run_game(args.game_file)
352 except GLib.Error as e:
353 error_dialog(player.window,
354 _("Error starting Glk library: {errmsg}").format(