6 from gi.repository import GObject, GLib, Gdk, Gio, Gtk, Chimara
11 gettext.install(config.GETTEXT_PACKAGE, config.PACKAGE_LOCALE_DIR,
12 unicode=True, codeset='UTF-8')
17 class Player(GObject.GObject):
18 __gtype_name__ = 'ChimaraPlayer'
20 def __init__(self, graphics_file=None):
21 super(Player, self).__init__()
23 # Initialize settings file; it can be overridden by a "chimara-config"
24 # file in the current directory
25 if os.path.exists('chimara-config'):
26 keyfile = 'chimara-config'
28 keyfile = os.path.expanduser('~/.chimara/config')
30 # This only works on my custom-built gobject-introspection; opened
32 backend = Gio.keyfile_settings_backend_new(keyfile,
33 "/org/chimara-if/player/", None)
34 except AttributeError:
36 self.prefs_settings = Gio.Settings('org.chimara-if.player.preferences',
38 self.state_settings = Gio.Settings('org.chimara-if.player.state',
41 builder = Gtk.Builder()
43 builder.add_from_file(os.path.join(config.PACKAGE_DATA_DIR,
47 builder.add_from_file(os.path.join(config.PACKAGE_SRC_DIR,
51 self.window = builder.get_object('chimara')
52 self.aboutwindow = builder.get_object('aboutwindow')
53 self.prefswindow = builder.get_object('prefswindow')
54 actiongroup = builder.get_object('actiongroup')
56 # Set the default value of the "View/Toolbar" menu item upon creation
57 # of a new window to the "show-toolbar-default" setting, but bind the
58 # setting one-way only - we don't want toolbars to disappear suddenly
59 toolbar_action = builder.get_object('toolbar')
60 toolbar_action.active = \
61 self.state_settings.get_boolean('show-toolbar-default')
62 self.state_settings.bind('show-toolbar-default', toolbar_action,
63 'active', Gio.SettingsBindFlags.SET)
65 filt = Gtk.RecentFilter()
66 # TODO: Use mimetypes and construct the filter dynamically depending on
67 # what plugins are installed
68 for pattern in ['*.z[1-8]', '*.[zg]lb', '*.[zg]blorb', '*.ulx', '*.blb',
70 filt.add_pattern(pattern)
71 recent = builder.get_object('recent')
72 recent.add_filter(filt)
74 uimanager = Gtk.UIManager()
76 uimanager.add_ui_from_file(os.path.join(config.PACKAGE_DATA_DIR,
80 uimanager.add_ui_from_file(os.path.join(config.PACKAGE_SRC_DIR,
84 uimanager.insert_action_group(actiongroup)
85 menubar = uimanager.get_widget('/menubar')
86 toolbar = uimanager.get_widget('/toolbar')
87 toolbar.no_show_all = True
88 if toolbar_action.active:
93 # Connect the accelerators
94 accels = uimanager.get_accel_group()
95 self.window.add_accel_group(accels)
97 self.glk = Chimara.IF(ignore_errors=True,
98 # interpreter_number=Chimara.IFZmachineVersion.TANDY_COLOR,
99 graphics_file=graphics_file)
100 css_file = _maybe(self.prefs_settings.get_value('css-file'))
102 css_file = 'style.css'
103 self.glk.set_css_from_file(css_file)
105 # DON'T UNCOMMENT THIS your eyes will burn
106 # but it is a good test of programmatically altering just one style
107 # self.glk.set_css_from_string("buffer{font-family: 'Comic Sans MS';}")
109 vbox = builder.get_object('vbox')
110 vbox.pack_end(self.glk, True, True, 0)
111 vbox.pack_start(menubar, False, False, 0)
112 vbox.pack_start(toolbar, False, False, 0)
114 builder.connect_signals(self)
115 self.glk.connect('notify::program-name', self.change_window_title)
116 self.glk.connect('notify::story-name', self.change_window_title)
118 # Create preferences window
121 def change_window_title(self, glk, pspec, data=None):
122 if glk.props.program_name is None:
124 elif glk.props.story_name is None:
125 title = "{interp} - Chimara".format(interp=glk.props.program_name)
127 title = "{interp} - {story} - Chimara".format(
128 interp=glk.props.program_name,
129 story=glk.props.story_name)
130 self.window.props.title = title
132 def on_open_activate(self, action, data=None):
133 if not self.confirm_open_new_game():
136 dialog = Gtk.FileChooserDialog(_('Open Game'), self.window,
137 Gtk.FileChooserAction.OPEN,
138 (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
139 Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT))
141 # Get last opened path
142 path = _maybe(self.state_settings.get_value('last-open-path'))
144 dialog.set_current_folder(path)
146 response = dialog.run()
148 if response != Gtk.ResponseType.ACCEPT:
151 gamefile = dialog.get_file()
152 self.search_for_graphics_file(gamefile.get_path())
154 self.glk.run_game_file(gamefile)
155 except GLib.Error as e:
156 error_dialog(self.window, _('Could not open game file {filename}: {errmsg}').format(filename=gamefile.get_path(), errmsg=e.message))
159 path = dialog.get_current_folder()
161 self.state_settings.last_open_path = path
163 # Add file to recent files list
164 manager = Gtk.RecentManager.get_default()
165 uri = gamefile.get_uri()
166 manager.add_item(uri)
170 def on_recent_item_activated(self, chooser, data=None):
171 if not self.confirm_open_new_game():
174 uri = chooser.get_current_uri()
175 gamefile = Gio.file_new_for_uri(uri)
177 self.search_for_graphics_file(gamefile.get_path())
179 self.glk.run_game_file(gamefile)
180 except GLib.Error as e:
181 error_dialog(self.window,
182 _('Could not open game file {filename}: {errmsg}').format(
183 filename=gamefile.get_basename(),
187 # Add file to recent files list again, this updates it to most recently
189 manager = Gtk.RecentManager.get_default()
190 manager.add_item(uri)
192 def on_stop_activate(self, *args):
195 def on_quit_chimara_activate(self, *args):
198 def on_copy_activate(self, *args):
199 focus = self.window.get_focus()
200 # Call "copy clipboard" on any widget that defines it
201 if (isinstance(focus, Gtk.Label)
202 or isinstance(focus, Gtk.Entry)
203 or isinstance(focus, Gtk.TextView)):
204 focus.emit('copy-clipboard')
206 def on_paste_activate(self, *args):
207 focus = self.window.get_focus()
208 # Call "paste clipboard" on any widget that defines it
209 if isinstance(focus, Gtk.Entry) or isinstance(focus, Gtk.TextView):
210 focus.emit('paste-clipboard')
212 def on_preferences_activate(self, *args):
213 self.prefswindow.present()
215 def on_toolbar_toggled(self, action, *args):
216 if action.get_active():
221 def on_undo_activate(self, *args):
222 self.glk.feed_line_input('undo')
224 def on_save_activate(self, *args):
225 self.glk.feed_line_input('save')
227 def on_restore_activate(self, *args):
228 self.glk.feed_line_input('restore')
230 def on_restart_activate(self, *args):
231 self.glk.feed_line_input('restart')
233 def on_quit_activate(self, *args):
234 self.glk.feed_line_input('quit')
236 def on_about_activate(self, *args):
237 self.aboutwindow.set_version(config.PACKAGE_VERSION)
238 self.aboutwindow.present()
240 def on_window_delete_event(self, *args):
244 def confirm_open_new_game(self):
246 If a game is running in the Glk widget, warn the user that they will
247 quit the currently running game if they open a new one. Returns True if
248 no game was running. Returns False if the user cancelled. Returns True
249 and shuts down the running game if the user wishes to continue.
251 if not self.glk.props.running:
254 dialog = Gtk.MessageDialog(self.window,
255 Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
256 Gtk.MessageType.WARNING, Gtk.ButtonsType.CANCEL,
257 _("Are you sure you want to open a new game?"))
258 dialog.format_secondary_text(
259 _("If you open a new game, you will quit the one you are "
260 "currently playing."))
261 dialog.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
262 response = dialog.run()
265 if response != Gtk.ResponseType.OK:
272 def search_for_graphics_file(self, filename):
273 """Internal function: See if there is a corresponding graphics file"""
275 # First get the name of the story file
276 base = os.path.basename(filename)
277 base_noext = os.path.splitext(base)[0]
279 # Check in the stored resource path, if set
280 resource_path = _maybe(self.prefs_settings.get_value('resource-path'))
282 # Otherwise check in the current directory
283 if resource_path is None:
284 resource_path = os.path.dirname(filename)
286 blorbfile = os.path.join(resource_path, base_noext + '.blb')
287 if os.path.exists(blorbfile):
288 self.glk.graphics_file = blorbfile
290 # Various signal handlers for GtkBuilder file
291 def gtk_widget_hide(self, widget, *args):
292 return Gtk.Widget.hide(widget)
294 def gtk_widget_hide_on_delete(self, widget, *args):
295 return Gtk.Widget.hide_on_delete(widget)
297 def dummy_handler(self, *args):
300 on_resource_file_set = dummy_handler
301 on_interpreter_cell_changed = dummy_handler
302 on_toggle_underline = dummy_handler
303 on_toggle_italic = dummy_handler
304 on_toggle_bold = dummy_handler
305 on_toggle_justify = dummy_handler
306 on_toggle_right = dummy_handler
307 on_toggle_center = dummy_handler
308 on_toggle_left = dummy_handler
309 on_background_color_set = dummy_handler
310 on_foreground_color_set = dummy_handler
311 on_font_set = dummy_handler
312 on_css_filechooser_file_set = dummy_handler
316 """Gets a maybe value from a GVariant - not handled in PyGI"""
317 v = variant.get_maybe()
323 def error_dialog(parent, message):
324 dialog = Gtk.MessageDialog(parent, Gtk.DialogFlags.DESTROY_WITH_PARENT,
325 Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, message)
329 if __name__ == '__main__':
330 parser = argparse.ArgumentParser()
331 parser.add_argument('game_file', nargs='?', default=None,
332 metavar='GAME FILE', help='the game file to load and start')
333 parser.add_argument('graphics_file', nargs='?', default=None,
334 metavar='GRAPHICS FILE', help='a Blorb resource file to include')
335 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(