dbe9caf7c208a23b47665091990515c0f60b97c8
[colloquium.git] / src / colloquium.c
1 /*
2  * colloquium.c
3  *
4  * Copyright © 2013-2018 Thomas White <taw@bitwiz.org.uk>
5  *
6  * This file is part of Colloquium.
7  *
8  * Colloquium is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  *
21  */
22
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <gtk/gtk.h>
29 #include <getopt.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <sys/stat.h>
33 #include <glib.h>
34 #include <glib/gstdio.h>
35
36 #include "colloquium.h"
37 #include "presentation.h"
38 #include "narrative_window.h"
39 #include "utils.h"
40
41
42 struct _colloquium
43 {
44         GtkApplication parent_instance;
45         char *mydir;
46         int first_run;
47         char *imagestore;
48         int hidepointer;
49 };
50
51
52 typedef GtkApplicationClass ColloquiumClass;
53
54
55 G_DEFINE_TYPE(Colloquium, colloquium, GTK_TYPE_APPLICATION)
56
57
58 static void colloquium_activate(GApplication *papp)
59 {
60         Colloquium *app = COLLOQUIUM(papp);
61         if ( !app->first_run ) {
62                 struct presentation *p;
63                 p = new_presentation(app->imagestore);
64                 narrative_window_new(p, papp);
65         }
66 }
67
68
69 static void new_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
70 {
71         GApplication *app = vp;
72         g_application_activate(app);
73 }
74
75
76 static void open_intro_doc(Colloquium *app)
77 {
78         GFile *file = g_file_new_for_uri("resource:///uk/me/bitwiz/Colloquium/demo.sc");
79         g_application_open(G_APPLICATION(app), &file, 1, "");
80         g_object_unref(file);
81 }
82
83
84 static void intro_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
85 {
86         GApplication *app = vp;
87         open_intro_doc(COLLOQUIUM(app));
88 }
89
90
91 void open_about_dialog(GtkWidget *parent)
92 {
93         GtkWidget *window;
94
95         const gchar *authors[] = {
96                 "Thomas White <taw@bitwiz.org.uk>",
97                 NULL
98         };
99
100         window = gtk_about_dialog_new();
101         gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(parent));
102
103         gtk_about_dialog_set_program_name(GTK_ABOUT_DIALOG(window),
104             "Colloquium");
105         gtk_about_dialog_set_logo_icon_name(GTK_ABOUT_DIALOG(window),
106             "colloquium");
107         gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(window),
108             PACKAGE_VERSION);
109         gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(window),
110             "© 2017-2018 Thomas White <taw@bitwiz.me.uk>");
111         gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(window),
112             /* Description of the program */
113             _("Narrative-based presentation system"));
114         gtk_about_dialog_set_license_type(GTK_ABOUT_DIALOG(window), GTK_LICENSE_GPL_3_0);
115         gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(window),
116             "https://www.bitwiz.me.uk/");
117         gtk_about_dialog_set_website_label(GTK_ABOUT_DIALOG(window),
118             "https://www.bitwiz.me.uk/");
119         gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(window), authors);
120         gtk_about_dialog_set_translator_credits(GTK_ABOUT_DIALOG(window),
121                                                 _("translator-credits"));
122
123         g_signal_connect(window, "response", G_CALLBACK(gtk_widget_destroy),
124             NULL);
125
126         gtk_widget_show_all(window);
127 }
128
129
130 static void quit_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
131 {
132         GApplication *app = vp;
133         g_application_quit(app);
134 }
135
136
137 static GFile **gslist_to_array(GSList *item, int *n)
138 {
139         int i = 0;
140         int len = g_slist_length(item);
141         GFile **files = malloc(len * sizeof(GFile *));
142
143         if ( files == NULL ) return NULL;
144
145         while ( item != NULL ) {
146                 if ( i == len ) {
147                         fprintf(stderr, _("WTF? Too many files\n"));
148                         break;
149                 }
150                 files[i++] = item->data;
151                 item = item->next;
152         }
153
154         *n = len;
155         return files;
156 }
157
158
159 static gint open_response_sig(GtkWidget *d, gint response, GApplication *papp)
160 {
161         if ( response == GTK_RESPONSE_ACCEPT ) {
162
163                 GSList *files;
164                 int n_files = 0;
165                 GFile **files_array;
166                 int i;
167
168                 files = gtk_file_chooser_get_files(GTK_FILE_CHOOSER(d));
169                 files_array = gslist_to_array(files, &n_files);
170                 if ( files_array == NULL ) {
171                         fprintf(stderr, _("Failed to convert file list\n"));
172                         return 0;
173                 }
174                 g_slist_free(files);
175                 g_application_open(papp, files_array, n_files, "");
176
177                 for ( i=0; i<n_files; i++ ) {
178                         g_object_unref(files_array[i]);
179                 }
180
181         }
182
183         gtk_widget_destroy(d);
184
185         return 0;
186 }
187
188
189 static void open_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
190 {
191         GtkWidget *d;
192         GApplication *app = vp;
193
194         d = gtk_file_chooser_dialog_new(_("Open Presentation"),
195                                         gtk_application_get_active_window(GTK_APPLICATION(app)),
196                                         GTK_FILE_CHOOSER_ACTION_OPEN,
197                                         _("_Cancel"), GTK_RESPONSE_CANCEL,
198                                         _("_Open"), GTK_RESPONSE_ACCEPT,
199                                         NULL);
200
201         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(d), TRUE);
202
203         g_signal_connect(G_OBJECT(d), "response",
204                          G_CALLBACK(open_response_sig), app);
205
206         gtk_widget_show_all(d);
207 }
208
209
210 GActionEntry app_entries[] = {
211
212         { "new", new_sig, NULL, NULL, NULL  },
213         { "open", open_sig, NULL, NULL, NULL  },
214         { "intro", intro_sig, NULL, NULL, NULL  },
215         { "quit", quit_sig, NULL, NULL, NULL  },
216 };
217
218
219 static void colloquium_open(GApplication  *papp, GFile **files, gint n_files,
220                             const gchar *hint)
221 {
222         int i;
223         Colloquium *app = COLLOQUIUM(papp);
224
225         for ( i = 0; i<n_files; i++ ) {
226                 struct presentation *p;
227                 p = new_presentation(app->imagestore);
228                 if ( load_presentation(p, files[i]) == 0 ) {
229                         narrative_window_new(p, papp);
230                 } else {
231                         char *uri = g_file_get_uri(files[i]);
232                         fprintf(stderr, _("Failed to load presentation '%s'\n"),
233                                 uri);
234                         g_free(uri);
235                 }
236         }
237 }
238
239
240 static void colloquium_finalize(GObject *object)
241 {
242         G_OBJECT_CLASS(colloquium_parent_class)->finalize(object);
243 }
244
245
246 static void create_config(const char *filename)
247 {
248
249         FILE *fh;
250         fh = fopen(filename, "w");
251         if ( fh == NULL ) {
252                 fprintf(stderr, _("Failed to create config\n"));
253                 return;
254         }
255
256         fprintf(fh, "imagestore: %s\n",
257                 g_get_user_special_dir(G_USER_DIRECTORY_PICTURES));
258         fprintf(fh, "hidepointer: no\n");
259
260         fclose(fh);
261 }
262
263
264 static int yesno(const char *a)
265 {
266         if ( a == NULL ) return 0;
267
268         if ( strcmp(a, "1") == 0 ) return 1;
269         if ( strcasecmp(a, "yes") == 0 ) return 1;
270         if ( strcasecmp(a, "true") == 0 ) return 1;
271
272         if ( strcasecmp(a, "0") == 0 ) return 0;
273         if ( strcasecmp(a, "no") == 0 ) return 0;
274         if ( strcasecmp(a, "false") == 0 ) return 0;
275
276         fprintf(stderr, _("Don't understand '%s', assuming false\n"), a);
277         return 0;
278 }
279
280
281 static void read_config(const char *filename, Colloquium *app)
282 {
283         FILE *fh;
284         char line[1024];
285
286         fh = fopen(filename, "r");
287         if ( fh == NULL ) {
288                 fprintf(stderr, _("Failed to open config %s\n"), filename);
289                 return;
290         }
291
292         do {
293
294                 if ( fgets(line, 1024, fh) == NULL ) break;
295                 chomp(line);
296
297                 if ( strncmp(line, "imagestore: ", 11) == 0 ) {
298                         app->imagestore = strdup(line+12);
299                 }
300
301                 if ( strncmp(line, "hidepointer: ", 12) == 0 ) {
302                         app->hidepointer = yesno(line+13);
303                 }
304         } while ( !feof(fh) );
305
306         fclose(fh);
307 }
308
309
310 const char *colloquium_get_imagestore(Colloquium *app)
311 {
312         return app->imagestore;
313 }
314
315
316 int colloquium_get_hidepointer(Colloquium *app)
317 {
318         return app->hidepointer;
319 }
320
321
322 static void colloquium_startup(GApplication *papp)
323 {
324         Colloquium *app = COLLOQUIUM(papp);
325         GtkBuilder *builder;
326         const char *configdir;
327         char *tmp;
328
329         G_APPLICATION_CLASS(colloquium_parent_class)->startup(papp);
330
331         g_action_map_add_action_entries(G_ACTION_MAP(app), app_entries,
332                                          G_N_ELEMENTS(app_entries), app);
333
334         builder = gtk_builder_new_from_resource("/uk/me/bitwiz/Colloquium/menu-bar.ui");
335         gtk_application_set_menubar(GTK_APPLICATION(app),
336             G_MENU_MODEL(gtk_builder_get_object(builder, "menubar")));
337         g_object_unref(builder);
338
339         if ( gtk_application_prefers_app_menu(GTK_APPLICATION(app)) ) {
340                 /* Set the application menu only if it will be shown by the
341                  * desktop environment.  All the entries are already in the
342                  * normal menus, so don't let GTK create a fallback menu in the
343                  * menu bar. */
344                 printf(_("Using app menu\n"));
345                 builder = gtk_builder_new_from_resource("/uk/me/bitwiz/Colloquium/app-menu.ui");
346                 GMenuModel *mmodel = G_MENU_MODEL(gtk_builder_get_object(builder, "app-menu"));
347                 gtk_application_set_app_menu(GTK_APPLICATION(app), mmodel);
348                 g_object_unref(builder);
349         }
350
351         configdir = g_get_user_config_dir();
352         app->mydir = malloc(strlen(configdir)+14);
353         strcpy(app->mydir, configdir);
354         strcat(app->mydir, "/colloquium");
355
356         if ( !g_file_test(app->mydir, G_FILE_TEST_IS_DIR) ) {
357
358                 /* Folder not created yet */
359                 open_intro_doc(app);
360                 app->first_run = 1;
361
362                 if ( g_mkdir(app->mydir, S_IRUSR | S_IWUSR | S_IXUSR) ) {
363                         fprintf(stderr, _("Failed to create config folder\n"));
364                 }
365         }
366
367         /* Read config file */
368         tmp = malloc(strlen(app->mydir)+32);
369         if ( tmp != NULL ) {
370
371                 tmp[0] = '\0';
372                 strcat(tmp, app->mydir);
373                 strcat(tmp, "/config");
374
375                 /* Create default config file if it doesn't exist */
376                 if ( !g_file_test(tmp, G_FILE_TEST_EXISTS) ) {
377                         create_config(tmp);
378                 }
379
380                 read_config(tmp, app);
381                 free(tmp);
382         }
383 }
384
385
386 static void colloquium_shutdown(GApplication *app)
387 {
388         G_APPLICATION_CLASS(colloquium_parent_class)->shutdown(app);
389 }
390
391
392 static void colloquium_class_init(ColloquiumClass *class)
393 {
394         GApplicationClass *app_class = G_APPLICATION_CLASS(class);
395         GObjectClass *object_class = G_OBJECT_CLASS(class);
396
397         app_class->startup = colloquium_startup;
398         app_class->shutdown = colloquium_shutdown;
399         app_class->activate = colloquium_activate;
400         app_class->open = colloquium_open;
401
402         object_class->finalize = colloquium_finalize;
403 }
404
405
406 static void colloquium_init(Colloquium *app)
407 {
408         app->imagestore = NULL;
409         app->hidepointer = 0;
410 }
411
412
413 static Colloquium *colloquium_new()
414 {
415         Colloquium *app;
416
417         g_set_application_name("Colloquium");
418         app = g_object_new(colloquium_get_type(),
419                            "application-id", "uk.org.bitwiz.Colloquium",
420                            "flags", G_APPLICATION_HANDLES_OPEN,
421                            "register-session", TRUE,
422                            NULL);
423
424         app->first_run = 0;  /* Will be updated at "startup" if appropriate */
425
426         return app;
427 }
428
429
430 static void show_help(const char *s)
431 {
432         printf(_("Syntax: %s [options] [<file.sc>]\n\n"), s);
433         printf(_("A tiny presentation program.\n\n"
434                  "  -h, --help    Display this help message.\n"));
435 }
436
437
438 int main(int argc, char *argv[])
439 {
440         int c;
441         int status;
442         Colloquium *app;
443
444         /* Long options */
445         const struct option longopts[] = {
446                 {"help",               0, NULL,               'h'},
447                 {0, 0, NULL, 0}
448         };
449
450         /* Short options */
451         while ((c = getopt_long(argc, argv, "h", longopts, NULL)) != -1) {
452
453                 switch (c)
454                 {
455                         case 'h' :
456                         show_help(argv[0]);
457                         return 0;
458
459                         case 0 :
460                         break;
461
462                         default :
463                         return 1;
464                 }
465
466         }
467
468 #if !GLIB_CHECK_VERSION(2,36,0)
469         g_type_init();
470 #endif
471
472         bindtextdomain("colloquium", LOCALEDIR);
473         textdomain("colloquium");
474
475         app = colloquium_new();
476         status = g_application_run(G_APPLICATION(app), argc, argv);
477         g_object_unref(app);
478         return status;
479 }