1 /* EasyTAG - tag editor for audio files
2 * Copyright (C) 2014-2015 David King <amigadave@amigadave.com>
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the Free
6 * Software Foundation; either version 2 of the License, or (at your option)
7 * any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along with
15 * this program; if not, write to the Free Software Foundation, Inc., 51
16 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19 #include "config.h"
20
21 #include "application.h"
22
23 #include <glib/gi18n.h>
24 #include <stdlib.h>
25
26 #include "about.h"
27 #include "charset.h"
28 #include "easytag.h"
29 #include "log.h"
30 #include "misc.h"
31 #include "setting.h"
32
33 typedef struct
34 {
35 guint idle_handler;
36 GFile *init_directory;
37 } EtApplicationPrivate;
38
39 G_DEFINE_TYPE_WITH_PRIVATE (EtApplication, et_application, GTK_TYPE_APPLICATION)
40
41 static const GOptionEntry entries[] =
42 {
43 { "version", 'v', 0, G_OPTION_ARG_NONE, NULL,
44 N_("Print the version and exit"), NULL },
45 { NULL }
46 };
47
48 static void
on_help(GSimpleAction * action,GVariant * parameter,gpointer user_data)49 on_help (GSimpleAction *action,
50 GVariant *parameter,
51 gpointer user_data)
52 {
53 GError *error = NULL;
54
55 /* TODO: Link to locally-installed help on Windows. */
56 gtk_show_uri (gtk_window_get_screen (GTK_WINDOW (MainWindow)),
57 "help:easytag", GDK_CURRENT_TIME, &error);
58
59 if (error)
60 {
61 g_debug ("Error while opening help: %s", error->message);
62 g_clear_error (&error);
63 }
64 else
65 {
66 return;
67 }
68
69 gtk_show_uri (gtk_window_get_screen (GTK_WINDOW (MainWindow)),
70 "https://help.gnome.org/users/easytag/stable/",
71 GDK_CURRENT_TIME, &error);
72
73 if (error)
74 {
75 g_debug ("Error while opening online help: %s", error->message);
76 g_error_free (error);
77 }
78 }
79
80 static void
on_about(GSimpleAction * action,GVariant * parameter,gpointer user_data)81 on_about (GSimpleAction *action,
82 GVariant *parameter,
83 gpointer user_data)
84 {
85 et_show_about_dialog (gtk_application_get_active_window (GTK_APPLICATION (user_data)));
86 }
87
88 static void
on_quit(GSimpleAction * action,GVariant * parameter,gpointer user_data)89 on_quit (GSimpleAction *action,
90 GVariant *parameter,
91 gpointer user_data)
92 {
93 et_application_window_quit (ET_APPLICATION_WINDOW (gtk_application_get_active_window (GTK_APPLICATION (user_data))));
94 }
95
96 static const GActionEntry actions[] =
97 {
98 { "help", on_help },
99 { "about", on_about },
100 { "quit", on_quit }
101 };
102
103 /*
104 * Load the default directory when the user interface is completely displayed
105 * to avoid bad visualization effect at startup.
106 */
107 static gboolean
on_idle_init(EtApplication * self)108 on_idle_init (EtApplication *self)
109 {
110 EtApplicationPrivate *priv;
111
112 priv = et_application_get_instance_private (self);
113
114 ET_Core_Free ();
115 ET_Core_Create ();
116
117 if (g_settings_get_boolean (MainSettings, "scan-startup"))
118 {
119 g_action_group_activate_action (G_ACTION_GROUP (MainWindow), "scanner",
120 NULL);
121 }
122
123 if (priv->init_directory)
124 {
125 et_application_window_select_dir (ET_APPLICATION_WINDOW (MainWindow),
126 priv->init_directory);
127 }
128 else
129 {
130 et_application_window_status_bar_message (ET_APPLICATION_WINDOW (MainWindow),
131 _("Select a directory to browse"),
132 FALSE);
133 g_action_group_activate_action (G_ACTION_GROUP (MainWindow),
134 "go-default", NULL);
135 }
136
137 /* Set sensitivity of buttons if the default directory is invalid. */
138 et_application_window_update_actions (ET_APPLICATION_WINDOW (MainWindow));
139
140 priv->idle_handler = 0;
141
142 return G_SOURCE_REMOVE;
143 }
144
145 /*
146 * common_init:
147 * @application: the application
148 *
149 * Create and show the main window. Common to all actions which may occur after
150 * startup, such as "activate" and "open".
151 */
152 static void
common_init(EtApplication * self)153 common_init (EtApplication *self)
154 {
155 EtApplicationPrivate *priv;
156 gboolean settings_warning;
157 EtApplicationWindow *window;
158
159 priv = et_application_get_instance_private (self);
160
161 /* Create all config files. */
162 settings_warning = !Setting_Create_Files ();
163
164 /* Load Config */
165 Init_Config_Variables ();
166
167 /* Initialization */
168 ET_Core_Create ();
169 Main_Stop_Button_Pressed = FALSE;
170
171 /* The main window */
172 window = et_application_window_new (GTK_APPLICATION (self));
173 MainWindow = GTK_WIDGET (window);
174
175 gtk_widget_show (MainWindow);
176
177 /* Starting messages */
178 Log_Print (LOG_OK, _("Starting EasyTAG version %s…"), PACKAGE_VERSION);
179 #ifdef G_OS_WIN32
180 if (g_getenv ("LANG"))
181 {
182 Log_Print (LOG_OK, _("Setting locale: ‘%s’"), g_getenv ("LANG"));
183 }
184 #endif /* G_OS_WIN32 */
185
186 if (get_locale ())
187 Log_Print (LOG_OK,
188 _("System locale is ‘%s’, using ‘%s’"),
189 get_locale (), get_encoding_from_locale (get_locale ()));
190
191 if (settings_warning)
192 {
193 Log_Print (LOG_WARNING, _("Unable to create setting directories"));
194 }
195
196 /* Load the default dir when the UI is created and displayed
197 * to the screen and open also the scanner window */
198 priv->idle_handler = g_idle_add ((GSourceFunc)on_idle_init, self);
199 g_source_set_name_by_id (priv->idle_handler, "Init idle function");
200 }
201
202 /*
203 * check_for_hidden_path_in_tree:
204 * @arg: the path to check
205 *
206 * Recursively check for a hidden path in the directory tree given by @arg. If
207 * a hidden path is found, set browse-show-hidden to TRUE.
208 */
209 static void
check_for_hidden_path_in_tree(GFile * arg)210 check_for_hidden_path_in_tree (GFile *arg)
211 {
212 GFile *file = NULL;
213 GFile *parent;
214 GFileInfo *info;
215 GError *err = NULL;
216
217 /* Not really the parent until an iteration through the loop below. */
218 parent = g_file_dup (arg);
219
220 do
221 {
222 info = g_file_query_info (parent,
223 G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
224 G_FILE_QUERY_INFO_NONE, NULL, &err);
225
226 if (info == NULL)
227 {
228 g_message ("Error querying file information (%s)", err->message);
229 g_clear_error (&err);
230
231 if (file)
232 {
233 g_clear_object (&file);
234 }
235 g_object_unref (parent);
236 break;
237 }
238 else
239 {
240 if (g_file_info_get_is_hidden (info))
241 {
242 g_settings_set_boolean (MainSettings, "browse-show-hidden",
243 TRUE);
244 }
245 }
246
247 g_object_unref (info);
248
249 if (file)
250 {
251 g_clear_object (&file);
252 }
253
254 file = parent;
255 }
256 while ((parent = g_file_get_parent (file)) != NULL);
257
258 g_clear_object (&file);
259 }
260
261 /*
262 * et_application_activate:
263 * @application: the application
264 *
265 * Handle the application being activated, which occurs after startup and if
266 * no files are opened.
267 */
268 static void
et_application_activate(GApplication * application)269 et_application_activate (GApplication *application)
270 {
271 GtkWindow *window;
272
273 window = gtk_application_get_active_window (GTK_APPLICATION (application));
274
275 if (window != NULL)
276 {
277 gtk_window_present (window);
278 }
279 else
280 {
281 common_init (ET_APPLICATION (application));
282 }
283 }
284
285 /*
286 * et_application_local_command_line:
287 * @application: the application
288 * @arguments: pointer to the argument string array
289 * @exit_status: pointer to the returned exit status
290 *
291 * Parse the local instance command-line arguments.
292 *
293 * Returns: %TRUE to indicate that the command-line arguments were completely
294 * handled in the local instance
295 */
296 static gboolean
et_application_local_command_line(GApplication * application,gchar ** arguments[],gint * exit_status)297 et_application_local_command_line (GApplication *application,
298 gchar **arguments[],
299 gint *exit_status)
300 {
301 GError *error = NULL;
302 guint n_args;
303 gchar **argv;
304
305 /* Try to register. */
306 if (!g_application_register (application, NULL, &error))
307 {
308 g_critical ("Error registering EtApplication: %s", error->message);
309 g_error_free (error);
310 *exit_status = 1;
311 return TRUE;
312 }
313
314 argv = *arguments;
315 n_args = g_strv_length (argv);
316 *exit_status = 0;
317
318 g_debug ("Received %u commandline arguments", n_args);
319
320 if (n_args <= 1)
321 {
322 g_application_activate (application);
323 return TRUE;
324 }
325 else
326 {
327 const gsize i = 1;
328
329 /* Exit the local instance for --help and --version. */
330 if ((strcmp (argv[i], "--version") == 0)
331 || (strcmp (argv[i], "-v") == 0))
332 {
333 g_print (PACKAGE_TARNAME " " PACKAGE_VERSION "\n");
334 g_print (_("Website: %s"), PACKAGE_URL "\n");
335 exit (0);
336 }
337 else if ((strcmp (argv[i], "--help") == 0)
338 || (strcmp (argv[i], "-h") == 0))
339 {
340 GOptionContext *context;
341 gchar *help;
342
343 context = g_option_context_new (_("- Tag and rename audio files"));
344 g_option_context_add_main_entries (context, entries,
345 GETTEXT_PACKAGE);
346 help = g_option_context_get_help (context, TRUE, NULL);
347 g_print ("%s", help);
348 exit (0);
349 }
350 else
351 {
352 /* Assume a filename otherwise, and allow the primary instance to
353 * handle it. */
354 GFile **files;
355 gsize n_files;
356 gsize j;
357
358 n_files = n_args - 1;
359 files = g_new (GFile *, n_files);
360
361 for (j = 0; j < n_files; j++)
362 {
363 files[j] = g_file_new_for_commandline_arg (argv[j + 1]);
364 }
365
366 g_application_open (application, files, n_files, "");
367
368 for (j = 0; j < n_files; j++)
369 {
370 g_object_unref (files[j]);
371 }
372
373 g_free (files);
374 }
375 }
376
377 return TRUE;
378 }
379
380 /*
381 * et_application_open:
382 * @application: the application
383 * @files: array of files to open
384 * @n_files: the number of files
385 * @hint: hint of method to open files, currently empty
386 *
387 * Handle the files passed to the primary instance.
388 *
389 * Returns: the exit status to be passed to the calling process
390 */
391 static void
et_application_open(GApplication * self,GFile ** files,gint n_files,const gchar * hint)392 et_application_open (GApplication *self,
393 GFile **files,
394 gint n_files,
395 const gchar *hint)
396 {
397 EtApplicationPrivate *priv;
398 GtkWindow *window;
399 gboolean activated;
400 GFile *arg;
401 GFile *parent;
402 GFileInfo *info;
403 GError *err = NULL;
404 GFileType type;
405 gchar *path;
406 gchar *display_path;
407
408 priv = et_application_get_instance_private (ET_APPLICATION (self));
409
410 window = gtk_application_get_active_window (GTK_APPLICATION (self));
411 activated = window ? TRUE : FALSE;
412
413 /* Only take the first file; ignore the rest. */
414 arg = files[0];
415
416 check_for_hidden_path_in_tree (arg);
417
418 path = g_file_get_path (arg);
419 display_path = g_filename_display_name (path);
420 info = g_file_query_info (arg, G_FILE_ATTRIBUTE_STANDARD_TYPE,
421 G_FILE_QUERY_INFO_NONE, NULL, &err);
422
423 g_free (path);
424
425 if (info == NULL)
426 {
427 if (activated)
428 {
429 Log_Print (LOG_ERROR, _("Error while querying information for file ‘%s’: %s"),
430 display_path, err->message);
431
432 }
433 else
434 {
435 g_warning ("Error while querying information for file: '%s' (%s)",
436 display_path, err->message);
437 }
438
439 g_free (display_path);
440 g_error_free (err);
441 return;
442 }
443
444 type = g_file_info_get_file_type (info);
445 g_object_unref (info);
446
447 if (type == G_FILE_TYPE_DIRECTORY)
448 {
449 if (activated)
450 {
451 et_application_window_select_dir (ET_APPLICATION_WINDOW (window),
452 arg);
453 }
454 else
455 {
456 priv->init_directory = g_object_ref (arg);
457 }
458 }
459 else if (type == G_FILE_TYPE_REGULAR)
460 {
461 /* When given a file, load the parent directory. */
462 parent = g_file_get_parent (arg);
463
464 if (parent)
465 {
466 if (activated)
467 {
468 et_application_window_select_dir (ET_APPLICATION_WINDOW (window),
469 parent);
470 }
471 else
472 {
473 priv->init_directory = parent;
474 }
475 }
476 else
477 {
478 Log_Print (LOG_WARNING, _("Cannot open path ‘%s’"), display_path);
479 g_free (display_path);
480 return;
481 }
482 }
483 else
484 {
485 Log_Print (LOG_WARNING, _("Cannot open path ‘%s’"), display_path);
486 g_free (display_path);
487 return;
488 }
489
490 g_free (display_path);
491
492 if (!activated)
493 {
494 common_init (ET_APPLICATION (self));
495 }
496 }
497
498 /*
499 * et_application_shutdown:
500 * @application: the application
501 * @user_data: user data set when the signal handler was connected
502 *
503 * Handle the application being shutdown, which occurs after the main loop has
504 * exited and before returning from g_application_run().
505 */
506 static void
et_application_shutdown(GApplication * application)507 et_application_shutdown (GApplication *application)
508 {
509 Charset_Insert_Locales_Destroy ();
510
511 G_APPLICATION_CLASS (et_application_parent_class)->shutdown (application);
512 }
513
514 static void
et_application_startup(GApplication * application)515 et_application_startup (GApplication *application)
516 {
517 GtkBuilder *builder;
518 GMenuModel *appmenu;
519 GMenuModel *menubar;
520
521 g_action_map_add_action_entries (G_ACTION_MAP (application), actions,
522 G_N_ELEMENTS (actions), application);
523
524 G_APPLICATION_CLASS (et_application_parent_class)->startup (application);
525
526 /* gtk_init() calls setlocale(), so gettext must be called after that. */
527 g_set_application_name (_(PACKAGE_NAME));
528 builder = gtk_builder_new_from_resource ("/org/gnome/EasyTAG/menus.ui");
529
530 appmenu = G_MENU_MODEL (gtk_builder_get_object (builder, "app-menu"));
531 gtk_application_set_app_menu (GTK_APPLICATION (application), appmenu);
532
533 menubar = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
534 gtk_application_set_menubar (GTK_APPLICATION (application), menubar);
535
536 g_object_unref (builder);
537
538 Charset_Insert_Locales_Init ();
539 }
540
541 static void
et_application_dispose(GObject * object)542 et_application_dispose (GObject *object)
543 {
544 EtApplication *self;
545 EtApplicationPrivate *priv;
546
547 self = ET_APPLICATION (object);
548 priv = et_application_get_instance_private (self);
549
550 if (priv->idle_handler)
551 {
552 g_source_remove (priv->idle_handler);
553 priv->idle_handler = 0;
554 }
555 }
556
557 static void
et_application_finalize(GObject * object)558 et_application_finalize (GObject *object)
559 {
560 EtApplication *self;
561 EtApplicationPrivate *priv;
562
563 self = ET_APPLICATION (object);
564 priv = et_application_get_instance_private (self);
565
566 if (priv->init_directory)
567 {
568 g_object_unref (priv->init_directory);
569 priv->init_directory = NULL;
570 }
571
572 G_OBJECT_CLASS (et_application_parent_class)->finalize (object);
573 }
574
575 static void
et_application_init(EtApplication * self)576 et_application_init (EtApplication *self)
577 {
578 }
579
580 static void
et_application_class_init(EtApplicationClass * klass)581 et_application_class_init (EtApplicationClass *klass)
582 {
583 GObjectClass *gobject_class;
584 GApplicationClass *gapplication_class;
585
586 gobject_class = G_OBJECT_CLASS (klass);
587 gapplication_class = G_APPLICATION_CLASS (klass);
588
589 gobject_class->dispose = et_application_dispose;
590 gobject_class->finalize = et_application_finalize;
591 gapplication_class->activate = et_application_activate;
592 gapplication_class->local_command_line = et_application_local_command_line;
593 gapplication_class->open = et_application_open;
594 gapplication_class->shutdown = et_application_shutdown;
595 gapplication_class->startup = et_application_startup;
596 }
597
598 /*
599 * et_application_new:
600 *
601 * Create a new EtApplication instance and initialise internationalization.
602 *
603 * Returns: a new #EtApplication
604 */
605 EtApplication *
et_application_new(void)606 et_application_new (void)
607 {
608 return g_object_new (ET_TYPE_APPLICATION, "application-id",
609 "org.gnome.EasyTAG", "flags",
610 G_APPLICATION_HANDLES_OPEN, NULL);
611 }
612