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