Avoid duplicating code
[projects/chimara/chimara.git] / player / app.c
1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include <glib-object.h>
4 #include <glib/gi18n.h>
5 #include <glib/gstdio.h>
6
7 /* Use a custom GSettings backend for our preferences file */
8 #define G_SETTINGS_ENABLE_BACKEND
9 #include <gio/gsettingsbackend.h>
10
11 #include <config.h>
12 #include <libchimara/chimara-if.h>
13 #include "app.h"
14 #include "browser.h"
15 #include "error.h"
16 #include "preferences.h"
17 #include "player.h"
18 #include "util.h"
19
20 typedef struct _ChimaraAppPrivate {
21         GtkActionGroup *action_group;
22 } ChimaraAppPrivate;
23
24 #define CHIMARA_APP_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), CHIMARA_TYPE_APP, ChimaraAppPrivate))
25 #define CHIMARA_APP_USE_PRIVATE ChimaraAppPrivate *priv = CHIMARA_APP_PRIVATE(self)
26
27 G_DEFINE_TYPE(ChimaraApp, chimara_app, G_TYPE_OBJECT);
28
29 static void
30 chimara_app_finalize(GObject *self)
31 {
32         CHIMARA_APP_USE_PRIVATE;
33         g_object_unref(priv->action_group);
34         
35         /* Chain up */
36         G_OBJECT_CLASS(chimara_app_parent_class)->finalize(self);
37 }
38
39 static void
40 chimara_app_class_init(ChimaraAppClass *klass)
41 {
42         /* Override methods of parent classes */
43         GObjectClass *object_class = G_OBJECT_CLASS(klass);
44         //object_class->set_property = chimara_if_set_property;
45         //object_class->get_property = chimara_if_get_property;
46         object_class->finalize = chimara_app_finalize;
47         
48         /* Signals */
49
50         /* Properties */
51
52         /* Private data */
53         g_type_class_add_private(klass, sizeof(ChimaraAppPrivate));
54 }
55
56 static void
57 chimara_app_init(ChimaraApp *self)
58 {
59         CHIMARA_APP_USE_PRIVATE;
60         GError *error = NULL;
61
62         /* Create configuration dir ~/.chimara */
63         gchar *configdir = g_build_filename(g_get_home_dir(), ".chimara", NULL);
64         if(!g_file_test(configdir, G_FILE_TEST_IS_DIR)
65                 && g_mkdir(configdir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0)
66                 g_error(_("Cannot create configuration directory ~/.chimara"));
67         g_free(configdir);
68
69         /* Initialize settings file; it can be overridden by a "chimara-config" file
70          in the current directory */
71         gchar *keyfile;
72         if(g_file_test("chimara-config", G_FILE_TEST_IS_REGULAR))
73                 keyfile = g_strdup("chimara-config");
74         else
75                 keyfile = g_build_filename(g_get_home_dir(), ".chimara", "config", NULL);
76         GSettingsBackend *backend = g_keyfile_settings_backend_new(keyfile, "/org/chimara-if/player/", NULL);
77         self->prefs_settings = g_settings_new_with_backend("org.chimara-if.player.preferences", backend);
78         self->state_settings = g_settings_new_with_backend("org.chimara-if.player.state", backend);
79         g_free(keyfile);
80
81         /* Build user interface */
82         char *object_ids[] = {
83                 "app_group",
84                 "aboutwindow",
85                 NULL
86         };
87         GtkBuilder *builder = new_builder_with_objects(object_ids);
88
89         self->aboutwindow = GTK_WIDGET(load_object(builder, "aboutwindow"));
90         priv->action_group = GTK_ACTION_GROUP(load_object(builder, "app_group"));
91         g_object_ref(priv->action_group);
92
93         const gchar **ptr;
94         GtkRecentFilter *filter = gtk_recent_filter_new();
95         /* TODO: Use mimetypes and construct the filter dynamically depending on 
96         what plugins are installed */
97         const gchar *patterns[] = {
98                 "*.z[1-8]", "*.[zg]lb", "*.[zg]blorb", "*.ulx", "*.blb", "*.blorb", NULL
99         };
100
101         for(ptr = patterns; *ptr; ptr++)
102                 gtk_recent_filter_add_pattern(filter, *ptr);
103         GtkRecentChooser *recent = GTK_RECENT_CHOOSER(load_object(builder, "recent"));
104         gtk_recent_chooser_add_filter(recent, filter);
105
106         gtk_builder_connect_signals(builder, self);
107
108         g_object_unref(builder);
109 }
110
111 /* PUBLIC FUNCTIONS */
112
113 ChimaraApp *
114 chimara_app_get(void)
115 {
116     static ChimaraApp *theapp = NULL;
117
118     if(G_UNLIKELY(theapp == NULL)) {
119                 theapp = CHIMARA_APP(g_object_new(CHIMARA_TYPE_APP, NULL));
120
121                 /* Create one-per-application windows */
122                 theapp->prefswindow = chimara_prefs_new();
123                 theapp->browser_window = chimara_browser_new();
124         }
125
126         return theapp;
127 }
128
129 GtkActionGroup *
130 chimara_app_get_action_group(ChimaraApp *self)
131 {
132         CHIMARA_APP_USE_PRIVATE;
133         return priv->action_group;
134 }
135
136 /* GLADE CALLBACKS */
137
138 /* Internal function: See if there is a corresponding graphics file. If so,
139 return its path. If not, return NULL. */
140 static char *
141 search_for_graphics_file(const char *filename)
142 {
143         ChimaraApp *theapp = chimara_app_get();
144
145         /* First get the name of the story file */
146         char *scratch = g_path_get_basename(filename);
147         *(strrchr(scratch, '.')) = '\0';
148
149         /* Check in the stored resource path, if set */
150         char *resource_path;
151         g_settings_get(theapp->prefs_settings, "resource-path", "ms", &resource_path);
152
153         /* Otherwise check in the current directory */
154         if(!resource_path)
155                 resource_path = g_path_get_dirname(filename);
156
157         char *blorbfile = g_strconcat(resource_path, "/", scratch, ".blb", NULL);
158         g_free(scratch);
159         g_free(resource_path);
160
161         if(g_file_test(blorbfile, G_FILE_TEST_EXISTS))
162                 return blorbfile;
163
164         g_free(blorbfile);
165         return NULL;
166 }
167
168 void
169 on_open_activate(GtkAction *action, ChimaraApp *theapp)
170 {
171         //if(!confirm_open_new_game(CHIMARA_GLK(player->glk)))
172         //      return;
173
174         GtkWidget *dialog = gtk_file_chooser_dialog_new(_("Open Game"),
175             NULL, // FIXME
176             GTK_FILE_CHOOSER_ACTION_OPEN,
177             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
178             GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
179             NULL);
180
181         /* Get last opened path */
182         gchar *path;
183         g_settings_get(theapp->state_settings, "last-open-path", "ms", &path);
184         if(path) {
185                 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path);
186                 g_free(path);
187         }
188
189         if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
190                 GError *error = NULL;
191                 char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
192
193                 /* Open a new player window */
194                 ChimaraPlayer *player = CHIMARA_PLAYER(chimara_player_new());
195                 gtk_widget_show_all(GTK_WIDGET(player));
196                 gtk_window_present(GTK_WINDOW(player));
197
198                 gchar *blorbfile = search_for_graphics_file(filename);
199                 if(blorbfile) {
200                         g_object_set(player->glk, "graphics-file", blorbfile, NULL);
201                         g_free(blorbfile);
202                 }
203                 if(!chimara_if_run_game(CHIMARA_IF(player->glk), filename, &error)) {
204                         error_dialog(GTK_WINDOW(player), error, _("Could not open game file '%s': "), filename);
205                         g_free(filename);
206                         gtk_widget_destroy(dialog);
207                         return;
208                 }
209                 
210                 path = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog));
211                 if(path) {
212                         g_settings_set(theapp->state_settings, "last-open-path", "ms", path);
213                         g_free(path);
214                 }
215
216                 /* Add file to recent files list */
217                 GtkRecentManager *manager = gtk_recent_manager_get_default();
218                 gchar *uri;
219                 
220                 if(!(uri = g_filename_to_uri(filename, NULL, &error)))
221                         g_warning(_("Could not convert filename '%s' to URI: %s"), filename, error->message);
222                 else {
223                         if(!gtk_recent_manager_add_item(manager, uri))
224                                 g_warning(_("Could not add URI '%s' to recent files list."), uri);
225                         g_free(uri);
226                 }
227                 g_free(filename);
228         }
229         gtk_widget_destroy(dialog);
230 }
231
232 void
233 on_recent_item_activated(GtkRecentChooser *chooser, ChimaraApp *theapp)
234 {
235         GError *error = NULL;
236         gchar *uri = gtk_recent_chooser_get_current_uri(chooser);
237         gchar *filename;
238         if(!(filename = g_filename_from_uri(uri, NULL, &error))) {
239                 error_dialog(NULL /* FIXME */, error, _("Could not open game file '%s': "), uri);
240                 goto finally;
241         }
242         
243         //if(!confirm_open_new_game(CHIMARA_GLK(player->glk)))
244         //      goto finally2;
245
246         /* Open a new player window */
247         ChimaraPlayer *player = CHIMARA_PLAYER(chimara_player_new());
248         gtk_widget_show_all(GTK_WIDGET(player));
249         gtk_window_present(GTK_WINDOW(player));
250         
251         char *blorbfile = search_for_graphics_file(filename);
252         if(blorbfile) {
253                 g_object_set(player->glk, "graphics-file", blorbfile, NULL);
254                 g_free(blorbfile);
255         }
256         if(!chimara_if_run_game(CHIMARA_IF(player->glk), filename, &error)) {
257                 error_dialog(GTK_WINDOW(player), error, _("Could not open game file '%s': "), filename);
258                 goto finally2;
259         }
260         
261         /* Add file to recent files list again, this updates it to most recently used */
262         GtkRecentManager *manager = gtk_recent_manager_get_default();
263         if(!gtk_recent_manager_add_item(manager, uri))
264                 g_warning(_("Could not add URI '%s' to recent files list."), uri);
265
266 finally2:
267         g_free(filename);
268 finally:
269         g_free(uri);
270 }
271
272 void 
273 on_quit_chimara_activate(GtkAction *action, ChimaraApp *theapp)
274 {
275         gtk_main_quit();
276 }
277
278 void
279 on_preferences_activate(GtkAction *action, ChimaraApp *theapp)
280 {
281         gtk_window_present(GTK_WINDOW(theapp->prefswindow));
282 }
283
284 void
285 on_about_activate(GtkAction *action, ChimaraApp *theapp)
286 {
287         gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(theapp->aboutwindow), PACKAGE_VERSION);
288         gtk_window_present(GTK_WINDOW(theapp->aboutwindow));
289 }
290