5 from gi.repository import GObject, Gdk, Gio, Gtk, Chimara
8 # FIXME: Dummy translation function, for now
11 class Player(GObject.GObject):
12 __gtype_name__ = 'ChimaraPlayer'
15 super(Player, self).__init__()
17 # FIXME: should use the Keyfile backend, but that's not available from
19 self.prefs_settings = Gio.Settings('org.chimara-if.player.preferences')
20 self.state_settings = Gio.Settings('org.chimara-if.player.state')
22 builder = Gtk.Builder()
23 builder.add_from_file('chimara.ui')
24 self.window = builder.get_object('chimara')
25 self.aboutwindow = builder.get_object('aboutwindow')
26 self.prefswindow = builder.get_object('prefswindow')
27 actiongroup = builder.get_object('actiongroup')
29 # Set the default value of the "View/Toolbar" menu item upon creation
30 # of a new window to the "show-toolbar-default" setting, but bind the
31 # setting one-way only - we don't want toolbars to disappear suddenly
32 toolbar_action = builder.get_object('toolbar')
33 toolbar_action.active = \
34 self.state_settings.get_boolean('show-toolbar-default')
35 self.state_settings.bind('show-toolbar-default', toolbar_action,
36 'active', Gio.SettingsBindFlags.SET)
38 filt = Gtk.RecentFilter()
39 for pattern in ['*.z[1-8]', '*.[zg]lb', '*.[zg]blorb', '*.ulx', '*.blb',
41 filt.add_pattern(pattern)
42 recent = builder.get_object('recent')
43 recent.add_filter(filt)
45 uimanager = Gtk.UIManager()
46 uimanager.add_ui_from_file('chimara.menus')
47 uimanager.insert_action_group(actiongroup, 0)
48 menubar = uimanager.get_widget('/menubar')
49 toolbar = uimanager.get_widget('/toolbar')
50 toolbar.no_show_all = True
51 if toolbar_action.active:
56 # Connect the accelerators
57 accels = uimanager.get_accel_group()
58 self.window.add_accel_group(accels)
60 self.glk = Chimara.IF()
61 self.glk.props.ignore_errors = True
62 self.glk.set_css_from_file('style.css')
64 vbox = builder.get_object('vbox')
65 vbox.pack_end(self.glk, True, True, 0)
66 vbox.pack_start(menubar, False, False, 0)
67 vbox.pack_start(toolbar, False, False, 0)
69 #builder.connect_signals(self) # FIXME Segfaults?!
70 builder.get_object('open').connect('activate', self.on_open_activate)
71 builder.get_object('restore').connect('activate',
72 self.on_restore_activate)
73 builder.get_object('save').connect('activate', self.on_save_activate)
74 builder.get_object('stop').connect('activate', self.on_stop_activate)
75 builder.get_object('recent').connect('item-activated',
76 self.on_recent_item_activated)
77 builder.get_object('undo').connect('activate', self.on_undo_activate)
78 builder.get_object('quit').connect('activate', self.on_quit_activate)
79 builder.get_object('copy').connect('activate', self.on_copy_activate)
80 builder.get_object('paste').connect('activate', self.on_paste_activate)
81 builder.get_object('preferences').connect('activate',
82 self.on_preferences_activate)
83 builder.get_object('about').connect('activate', self.on_about_activate)
84 toolbar_action.connect('toggled', self.on_toolbar_toggled)
85 self.aboutwindow.connect('response', lambda x, *args: x.hide())
86 self.aboutwindow.connect('delete-event',
87 lambda x, *args: x.hide_on_delete())
88 self.window.connect('delete-event', self.on_window_delete_event)
89 self.prefswindow.connect('response', lambda x, *args: x.hide())
90 self.prefswindow.connect('delete-event',
91 lambda x, *args: x.hide_on_delete())
92 # FIXME Delete to here when above bug is fixed
94 self.glk.connect('notify::program-name', self.change_window_title)
95 self.glk.connect('notify::story-name', self.change_window_title)
97 # Create preferences window
100 def change_window_title(self, glk, pspec, data=None):
101 if glk.props.program_name is None:
103 elif glk.props.story_name is None:
104 title = "{interp} - Chimara".format(interp=glk.props.program_name)
106 title = "{interp} - {story} - Chimara".format(
107 interp=glk.props.program_name,
108 story=glk.props.story_name)
109 self.window.props.title = title
111 def on_open_activate(self, action, data=None):
112 if not self.confirm_open_new_game(): return
114 dialog = Gtk.FileChooserDialog(_('Open Game'), self.window,
115 Gtk.FileChooserAction.OPEN,
116 (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
117 Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT))
119 # Get last opened path
120 path = _maybe(self.state_settings.get_value('last-open-path'))
122 dialog.set_current_folder(path)
124 response = dialog.run()
126 if response != Gtk.ResponseType.ACCEPT:
129 gamefile = dialog.get_file()
130 self.search_for_graphics_file(gamefile.get_path())
132 self.glk.run_game_file(gamefile)
133 except GLib.Error as e:
134 error_dialog(self.window, _('Could not open game file {filename}: {errmsg}').format(filename=gamefile.get_path(), errmsg=e.message))
137 path = dialog.get_current_folder()
139 self.state_settings.last_open_path = path
141 # Add file to recent files list
142 manager = Gtk.RecentManager.get_default()
143 uri = gamefile.get_uri()
144 manager.add_item(uri)
148 def on_recent_item_activated(self, chooser, data=None):
149 if not self.confirm_open_new_game(): return
151 uri = chooser.get_current_uri()
152 gamefile = Gio.file_new_for_uri(uri)
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,
159 _('Could not open game file {filename}: {errmsg}').format(
160 filename=gamefile.get_basename(),
164 # Add file to recent files list again, this updates it to most recently
166 manager = Gtk.RecentManager.get_default()
167 manager.add_item(uri)
169 def on_stop_activate(self, action, data=None):
172 def on_quit_chimara_activate(self, action, data=None):
175 def on_copy_activate(self, action, data=None):
176 focus = self.window.get_focus()
177 # Call "copy clipboard" on any widget that defines it
178 if (isinstance(focus, Gtk.Label)
179 or isinstance(focus, Gtk.Entry)
180 or isinstance(focus, Gtk.TextView)):
181 focus.emit('copy-clipboard')
183 def on_paste_activate(self, action, data=None):
184 focus = self.window.get_focus()
185 # Call "paste clipboard" on any widget that defines it
186 if isinstance(focus, Gtk.Entry) or isinstance(focus, Gtk.TextView):
187 focus.emit('paste-clipboard')
189 def on_preferences_activate(self, action, data=None):
190 self.prefswindow.present()
192 def on_toolbar_toggled(self, action, data=None):
193 if action.get_active():
198 def on_undo_activate(self, action, data=None):
199 self.glk.feed_line_input('undo')
201 def on_save_activate(self, action, data=None):
202 self.glk.feed_line_input('save')
204 def on_restore_activate(self, action, data=None):
205 self.glk.feed_line_input('restore')
207 def on_restart_activate(self, action, data=None):
208 self.glk.feed_line_input('restart')
210 def on_quit_activate(self, action, data=None):
211 self.glk.feed_line_input('quit')
213 def on_about_activate(self, action, data=None):
214 self.aboutwindow.set_version(config.PACKAGE_VERSION)
215 self.aboutwindow.present()
217 def on_window_delete_event(self, widget, event, data=None):
221 def confirm_open_new_game(self):
223 If a game is running in the Glk widget, warn the user that they will
224 quit the currently running game if they open a new one. Returns True if
225 no game was running. Returns False if the user cancelled. Returns True
226 and shuts down the running game if the user wishes to continue.
228 if not self.glk.props.running: return True
230 dialog = Gtk.MessageDialog(self.window,
231 Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
232 Gtk.MessageType.WARNING, Gtk.ButtonsType.CANCEL,
233 _("Are you sure you want to open a new game?"))
234 dialog.format_secondary_text(
235 _("If you open a new game, you will quit the one you are "
236 "currently playing."))
237 dialog.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
238 response = dialog.run()
241 if response != Gtk.ResponseType.OK:
248 def search_for_graphics_file(self, filename):
249 """Internal function: See if there is a corresponding graphics file"""
251 # First get the name of the story file
252 base = os.path.basename(filename)
253 base_noext = os.path.splitext(base)[0]
255 # Check in the stored resource path, if set
256 resource_path = _maybe(self.prefs_settings.get_value('resource-path'))
258 # Otherwise check in the current directory
259 if resource_path is None:
260 resource_path = os.path.dirname(filename)
262 blorbfile = os.path.join(resource_path, base_noext + '.blb')
263 if os.path.exists(blorbfile):
264 glk.graphics_file = blorbfile
267 """Gets a maybe value from a GVariant - not handled in PyGI"""
268 v = variant.get_maybe()
269 if v is None: return None
272 def error_dialog(parent, message):
273 dialog = Gtk.MessageDialog(parent, Gtk.DialogFlags.DESTROY_WITH_PARENT,
274 Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, message)
278 if __name__ == '__main__':
282 player.window.show_all()
284 if len(sys.argv) == 3:
285 player.glk.props.graphics_file = sys.argv[2]
286 if len(sys.argv) >= 2:
288 player.glk.run_game(sys.argv[1])
289 except GLib.Error as e:
290 error_dialog(player.window,
291 _("Error starting Glk library: {errmsg}").format(