PEP8 cleanup in player.py
[projects/chimara/chimara.git] / player / player.py
1 #!/usr/bin/env python
2
3 import sys
4 import os.path
5 from gi.repository import GObject, GLib, Gdk, Gio, Gtk, Chimara
6 import config
7
8 # FIXME: Dummy translation function, for now
9 _ = lambda x: x
10
11
12 class Player(GObject.GObject):
13         __gtype_name__ = 'ChimaraPlayer'
14
15         def __init__(self):
16                 super(Player, self).__init__()
17
18                 # FIXME: should use the Keyfile backend, but that's not available from
19                 # Python
20                 self.prefs_settings = Gio.Settings('org.chimara-if.player.preferences')
21                 self.state_settings = Gio.Settings('org.chimara-if.player.state')
22
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')
29
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)
38
39                 filt = Gtk.RecentFilter()
40                 for pattern in ['*.z[1-8]', '*.[zg]lb', '*.[zg]blorb', '*.ulx', '*.blb',
41                         '*.blorb']:
42                         filt.add_pattern(pattern)
43                 recent = builder.get_object('recent')
44                 recent.add_filter(filt)
45
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:
53                         toolbar.show()
54                 else:
55                         toolbar.hide()
56
57                 # Connect the accelerators
58                 accels = uimanager.get_accel_group()
59                 self.window.add_accel_group(accels)
60
61                 self.glk = Chimara.IF()
62                 self.glk.props.ignore_errors = True
63                 self.glk.set_css_from_file('style.css')
64
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)
69
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
94
95                 self.glk.connect('notify::program-name', self.change_window_title)
96                 self.glk.connect('notify::story-name', self.change_window_title)
97
98                 # Create preferences window
99                 # TODO
100
101         def change_window_title(self, glk, pspec, data=None):
102                 if glk.props.program_name is None:
103                         title = "Chimara"
104                 elif glk.props.story_name is None:
105                         title = "{interp} - Chimara".format(interp=glk.props.program_name)
106                 else:
107                         title = "{interp} - {story} - Chimara".format(
108                                 interp=glk.props.program_name,
109                                 story=glk.props.story_name)
110                 self.window.props.title = title
111
112         def on_open_activate(self, action, data=None):
113                 if not self.confirm_open_new_game():
114                         return
115
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))
120
121                 # Get last opened path
122                 path = _maybe(self.state_settings.get_value('last-open-path'))
123                 if path is not None:
124                         dialog.set_current_folder(path)
125
126                 response = dialog.run()
127                 dialog.hide()
128                 if response != Gtk.ResponseType.ACCEPT:
129                         return
130
131                 gamefile = dialog.get_file()
132                 self.search_for_graphics_file(gamefile.get_path())
133                 try:
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))
137                         return
138
139                 path = dialog.get_current_folder()
140                 if path is not None:
141                         self.state_settings.last_open_path = path
142
143                 # Add file to recent files list
144                 manager = Gtk.RecentManager.get_default()
145                 uri = gamefile.get_uri()
146                 manager.add_item(uri)
147
148                 dialog.destroy()
149
150         def on_recent_item_activated(self, chooser, data=None):
151                 if not self.confirm_open_new_game():
152                         return
153
154                 uri = chooser.get_current_uri()
155                 gamefile = Gio.file_new_for_uri(uri)
156
157                 self.search_for_graphics_file(gamefile.get_path())
158                 try:
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(),
164                                         errmsg=e.message))
165                         return
166
167                 # Add file to recent files list again, this updates it to most recently
168                 # used
169                 manager = Gtk.RecentManager.get_default()
170                 manager.add_item(uri)
171
172         def on_stop_activate(self, action, data=None):
173                 self.glk.stop()
174
175         def on_quit_chimara_activate(self, action, data=None):
176                 Gtk.main_quit()
177
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')
185
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')
191
192         def on_preferences_activate(self, action, data=None):
193                 self.prefswindow.present()
194
195         def on_toolbar_toggled(self, action, data=None):
196                 if action.get_active():
197                         self.toolbar.show()
198                 else:
199                         self.toolbar.hide()
200
201         def on_undo_activate(self, action, data=None):
202                 self.glk.feed_line_input('undo')
203
204         def on_save_activate(self, action, data=None):
205                 self.glk.feed_line_input('save')
206
207         def on_restore_activate(self, action, data=None):
208                 self.glk.feed_line_input('restore')
209
210         def on_restart_activate(self, action, data=None):
211                 self.glk.feed_line_input('restart')
212
213         def on_quit_activate(self, action, data=None):
214                 self.glk.feed_line_input('quit')
215
216         def on_about_activate(self, action, data=None):
217                 self.aboutwindow.set_version(config.PACKAGE_VERSION)
218                 self.aboutwindow.present()
219
220         def on_window_delete_event(self, widget, event, data=None):
221                 Gtk.main_quit()
222                 return True
223
224         def confirm_open_new_game(self):
225                 """
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.
230                 """
231                 if not self.glk.props.running:
232                         return True
233
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()
243                 dialog.hide()
244
245                 if response != Gtk.ResponseType.OK:
246                         return False
247
248                 self.glk.stop()
249                 self.glk.wait()
250                 return True
251
252         def search_for_graphics_file(self, filename):
253                 """Internal function: See if there is a corresponding graphics file"""
254
255                 # First get the name of the story file
256                 base = os.path.basename(filename)
257                 base_noext = os.path.splitext(base)[0]
258
259                 # Check in the stored resource path, if set
260                 resource_path = _maybe(self.prefs_settings.get_value('resource-path'))
261
262                 # Otherwise check in the current directory
263                 if resource_path is None:
264                         resource_path = os.path.dirname(filename)
265
266                 blorbfile = os.path.join(resource_path, base_noext + '.blb')
267                 if os.path.exists(blorbfile):
268                         self.glk.graphics_file = blorbfile
269
270
271 def _maybe(variant):
272         """Gets a maybe value from a GVariant - not handled in PyGI"""
273         v = variant.get_maybe()
274         if v is None:
275                 return None
276         return v.unpack()
277
278
279 def error_dialog(parent, message):
280         dialog = Gtk.MessageDialog(parent, Gtk.DialogFlags.DESTROY_WITH_PARENT,
281                 Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, message)
282         dialog.run()
283         dialog.destroy()
284
285 if __name__ == '__main__':
286         Gdk.threads_init()
287
288         player = Player()
289         player.window.show_all()
290
291         if len(sys.argv) == 3:
292                 player.glk.props.graphics_file = sys.argv[2]
293         if len(sys.argv) >= 2:
294                 try:
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(
299                                         errmsg=e.message))
300                         sys.exit(1)
301
302         Gdk.threads_enter()
303         Gtk.main()
304         Gdk.threads_leave()
305
306         player.glk.stop()
307         player.glk.wait()
308
309         sys.exit(0)