10f45d53c4b724145163f66ea91c93e302d74553
[projects/chimara/chimara.git] / player / player.c
1 /*
2  * Copyright (C) 2008, 2009, 2010, 2011 Philip Chimento and Marijn van Vliet.
3  * All rights reserved.
4  *
5  * Chimara is free software copyrighted by Philip Chimento and Marijn van Vliet.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright notice,
11  *    this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright notice,
13  *    this list of conditions and the following disclaimer in the documentation
14  *    and/or other materials provided with the distribution.
15  * 3. Neither of the names Philip Chimento or Marijn van Vliet, nor the name of
16  *    any other contributor may be used to endorse or promote products derived
17  *    from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
20  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include <glib.h>
33 #include <glib-object.h>
34 #include <glib/gi18n.h>
35 #include <gtk/gtk.h>
36 #include <libchimara/chimara-glk.h>
37 #include <libchimara/chimara-if.h>
38 #include "player.h"
39 #include "app.h"
40 #include "error.h"
41 #include "util.h"
42
43 typedef struct _ChimaraPlayerPrivate {
44         int dummy;
45 } ChimaraPlayerPrivate;
46
47 #define CHIMARA_PLAYER_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), CHIMARA_TYPE_PLAYER, ChimaraPlayerPrivate))
48 #define CHIMARA_PLAYER_USE_PRIVATE ChimaraPlayerPrivate *priv = CHIMARA_PLAYER_PRIVATE(self)
49
50 G_DEFINE_TYPE(ChimaraPlayer, chimara_player, GTK_TYPE_WINDOW);
51
52 static void
53 change_window_title(ChimaraGlk *glk, GParamSpec *pspec, GtkWindow *window)
54 {
55         gchar *program_name, *story_name, *title;
56         g_object_get(glk, "program-name", &program_name, "story-name", &story_name, NULL);
57         if(!program_name) {
58                 gtk_window_set_title(window, "Chimara");
59                 return;
60         }
61         else if(!story_name)
62                 title = g_strdup_printf("%s - Chimara", program_name);
63         else
64                 title = g_strdup_printf("%s - %s - Chimara", program_name, story_name);
65         
66         g_free(program_name);
67         g_free(story_name);
68         gtk_window_set_title(window, title);
69         g_free(title);
70 }
71
72 static void
73 on_css_changed(GSettings *prefs_settings, char *key, ChimaraPlayer *self)
74 {
75         char *user_css;
76         g_settings_get(prefs_settings, "css-file", "ms", &user_css);
77         if(user_css) {
78                 if(!chimara_glk_set_css_from_file(CHIMARA_GLK(self->glk), user_css, NULL)) {
79                         /* If the setting didn't point to a CSS file, fail silently and
80                          null the setting */
81                         g_settings_set(prefs_settings, "css-file", "ms", NULL);
82                 }
83                 g_free(user_css);
84         }
85 }
86
87 static void
88 chimara_player_dispose(GObject *object)
89 {
90         ChimaraPlayer *self = CHIMARA_PLAYER(object);
91         if(chimara_glk_get_running(CHIMARA_GLK(self->glk))) {
92                 chimara_glk_stop(CHIMARA_GLK(self->glk));
93                 chimara_glk_wait(CHIMARA_GLK(self->glk));
94         }
95         
96         /* Chain up */
97         G_OBJECT_CLASS(chimara_player_parent_class)->dispose(object);
98 }
99
100 static void
101 chimara_player_finalize(GObject *object)
102 {
103         g_object_unref(CHIMARA_PLAYER(object)->glk);
104         
105         /* Chain up */
106         G_OBJECT_CLASS(chimara_player_parent_class)->finalize(object);
107 }
108
109 static void
110 chimara_player_class_init(ChimaraPlayerClass *klass)
111 {
112         /* Override methods of parent classes */
113         GObjectClass *object_class = G_OBJECT_CLASS(klass);
114         object_class->dispose = chimara_player_dispose;
115         object_class->finalize = chimara_player_finalize;
116
117         /* Private data */
118         g_type_class_add_private(klass, sizeof(ChimaraPlayerPrivate));
119 }
120
121 static void
122 chimara_player_init(ChimaraPlayer *self)
123 {       
124         GError *error = NULL;
125         ChimaraApp *theapp = chimara_app_get();
126
127         /* Set parent properties */
128         g_object_set(self,
129                 "title", _("Chimara"),
130                 "default-width", 600,
131                 "default-height", 800,
132                 NULL);
133
134         /* Construct user interface */
135         char *object_ids[] = {
136                 "actiongroup",
137                 "player-vbox",
138                 NULL
139         };
140         GtkBuilder *builder = new_builder_with_objects(object_ids);
141
142         GtkActionGroup *actiongroup = GTK_ACTION_GROUP(load_object(builder, "actiongroup"));
143
144         /* Set the default value of the "View/Toolbar" menu item upon creation of a
145          new window to the "show-toolbar-default" setting, but bind the setting
146          one-way only - we don't want toolbars to disappear suddenly */
147         GtkToggleAction *toolbar_action = GTK_TOGGLE_ACTION(load_object(builder, "toolbar"));
148         gtk_toggle_action_set_active(toolbar_action, g_settings_get_boolean(theapp->state_settings, "show-toolbar-default"));
149         g_settings_bind(theapp->state_settings, "show-toolbar-default", toolbar_action, "active", G_SETTINGS_BIND_SET);
150
151         self->glk = chimara_if_new();
152         g_object_set(self->glk,
153                                  "ignore-errors", TRUE,
154                                  /*"interpreter-number", CHIMARA_IF_ZMACHINE_TANDY_COLOR,*/
155                                  NULL);
156
157         /* Set the CSS styles for the interpreter */
158         char *default_css = get_data_file_path("style.css");
159         if( !chimara_glk_set_css_from_file(CHIMARA_GLK(self->glk), default_css, &error) ) {
160                 error_dialog(GTK_WINDOW(self), error, "Couldn't open default CSS file: ");
161         }
162         g_free(default_css);
163         on_css_changed(theapp->prefs_settings, "css-file", self);
164         
165         /* DON'T UNCOMMENT THIS your eyes will burn
166          but it is a good test of programmatically altering just one style
167          chimara_glk_set_css_from_string(CHIMARA_GLK(glk),
168          "buffer.normal { font-family: 'Comic Sans MS'; }");*/
169
170         GtkUIManager *uimanager = new_ui_manager("player.menus");
171         gtk_ui_manager_insert_action_group(uimanager, actiongroup, 0);
172         gtk_ui_manager_insert_action_group(uimanager, chimara_app_get_action_group(theapp), 1);
173         GtkWidget *menubar = gtk_ui_manager_get_widget(uimanager, "/player_menu");
174         self->toolbar = gtk_ui_manager_get_widget(uimanager, "/player_toolbar");
175         gtk_widget_set_no_show_all(self->toolbar, TRUE);
176         if(gtk_toggle_action_get_active(toolbar_action))
177                 gtk_widget_show(self->toolbar);
178         else
179                 gtk_widget_hide(self->toolbar);
180         
181         /* Connect the accelerators */
182         GtkAccelGroup *accels = gtk_ui_manager_get_accel_group(uimanager);
183         gtk_window_add_accel_group(GTK_WINDOW(self), accels);
184
185         GtkBox *vbox = GTK_BOX(load_object(builder, "player-vbox"));
186         gtk_box_pack_end(vbox, self->glk, TRUE, TRUE, 0);
187         g_object_ref(self->glk); /* add an extra reference to keep it alive while
188                                                           the Glk program shuts down */
189         gtk_box_pack_start(vbox, menubar, FALSE, FALSE, 0);
190         gtk_box_pack_start(vbox, self->toolbar, FALSE, FALSE, 0);
191         gtk_container_add(GTK_CONTAINER(self), GTK_WIDGET(vbox));
192         
193         gtk_builder_connect_signals(builder, self);
194         g_signal_connect(self->glk, "notify::program-name", G_CALLBACK(change_window_title), self);
195         g_signal_connect(self->glk, "notify::story-name", G_CALLBACK(change_window_title), self);
196         g_signal_connect(theapp->prefs_settings, "changed::css-file", G_CALLBACK(on_css_changed), self);
197
198         g_object_unref(builder);
199         g_object_unref(uimanager);
200 }
201
202 /* PUBLIC FUNCTIONS */
203
204 GtkWidget *
205 chimara_player_new(void)
206 {
207     return GTK_WIDGET(g_object_new(CHIMARA_TYPE_PLAYER,
208                 "type", GTK_WINDOW_TOPLEVEL,
209                 NULL));
210 }
211
212 void
213 chimara_player_set_user_css_file(ChimaraPlayer *self, const char *filename)
214 {
215         chimara_glk_set_css_to_default(CHIMARA_GLK(self->glk));
216         chimara_glk_set_css_from_file(CHIMARA_GLK(self->glk), filename, NULL);
217 }
218
219 /* GLADE CALLBACKS */
220
221 #if 0
222 /* If a game is running in @glk, warn the user that they will quit the currently
223 running game if they open a new one. Returns TRUE if no game was running.
224 Returns FALSE if the user cancelled. Returns TRUE and shuts down the running
225 game if the user wishes to continue. */
226 static gboolean
227 confirm_open_new_game(ChimaraGlk *glk)
228 {
229         g_return_val_if_fail(glk && CHIMARA_IS_GLK(glk), FALSE);
230         
231         GtkWindow *window = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(glk)));
232         
233         if(chimara_glk_get_running(glk)) {
234                 GtkWidget *dialog = gtk_message_dialog_new(window,
235                     GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
236                     GTK_MESSAGE_WARNING,
237                     GTK_BUTTONS_CANCEL,
238                     _("Are you sure you want to open a new game?"));
239                 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
240                     _("If you open a new game, you will quit the one you are currently playing."));
241                 gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_OPEN, GTK_RESPONSE_OK);
242                 gint response = gtk_dialog_run(GTK_DIALOG(dialog));
243                 gtk_widget_destroy(dialog);
244                 
245                 if(response != GTK_RESPONSE_OK)
246                         return FALSE;
247
248                 chimara_glk_stop(glk);
249                 chimara_glk_wait(glk);
250         }
251         return TRUE;
252 }
253 #endif
254
255 void
256 on_stop_activate(GtkAction *action, ChimaraPlayer *player)
257 {
258         chimara_glk_stop(CHIMARA_GLK(player->glk));
259 }
260
261 void
262 on_copy_activate(GtkAction *action, ChimaraPlayer *player)
263 {
264         GtkWidget *focus = gtk_window_get_focus(GTK_WINDOW(player));
265         /* Call "copy clipboard" on any widget that defines it */
266         if(GTK_IS_LABEL(focus) || GTK_IS_ENTRY(focus) || GTK_IS_TEXT_VIEW(focus))
267                 g_signal_emit_by_name(focus, "copy-clipboard");
268 }
269
270 void
271 on_paste_activate(GtkAction *action, ChimaraPlayer *player)
272 {
273         GtkWidget *focus = gtk_window_get_focus(GTK_WINDOW(player));
274         /* Call "paste clipboard" on any widget that defines it */
275         if(GTK_IS_ENTRY(focus) || GTK_IS_TEXT_VIEW(focus))
276                 g_signal_emit_by_name(focus, "paste-clipboard");
277 }
278
279 void
280 on_toolbar_toggled(GtkToggleAction *action, ChimaraPlayer *player)
281 {
282         if(gtk_toggle_action_get_active(action))
283                 gtk_widget_show(player->toolbar);
284         else
285                 gtk_widget_hide(player->toolbar);
286 }
287
288 void
289 on_undo_activate(GtkAction *action, ChimaraPlayer *player)
290 {
291         chimara_glk_feed_line_input(CHIMARA_GLK(player->glk), "undo");
292 }
293
294 void 
295 on_save_activate(GtkAction *action, ChimaraPlayer *player)
296 {
297         chimara_glk_feed_line_input(CHIMARA_GLK(player->glk), "save");
298 }
299
300 void 
301 on_restore_activate(GtkAction *action, ChimaraPlayer *player)
302 {
303         chimara_glk_feed_line_input(CHIMARA_GLK(player->glk), "restore");
304 }
305
306 void 
307 on_restart_activate(GtkAction *action, ChimaraPlayer *player)
308 {
309         chimara_glk_feed_line_input(CHIMARA_GLK(player->glk), "restart");
310 }
311
312 void 
313 on_quit_activate(GtkAction *action, ChimaraPlayer *player)
314 {
315         chimara_glk_feed_line_input(CHIMARA_GLK(player->glk), "quit");
316 }
317