3
* Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
4
* Copyright (c) 2009 Jannis Pohlmann <jannis@xfce.org>
6
* This program is free software; you can redistribute it and/or modify it
7
* under the terms of the GNU General Public License as published by the Free
8
* Software Foundation; either version 2 of the License, or (at your option)
11
* This program is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
16
* You should have received a copy of the GNU General Public License along with
17
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
18
* Place, Suite 330, Boston, MA 02111-1307 USA
27
#include <thunar/thunar-icon-factory.h>
28
#include <thunar/thunar-job.h>
29
#include <thunar/thunar-misc-jobs.h>
30
#include <thunar/thunar-private.h>
31
#include <thunar/thunar-templates-action.h>
35
/* Signal identifiers */
45
static void thunar_templates_action_finalize (GObject *object);
46
static GtkWidget *thunar_templates_action_create_menu_item (GtkAction *action);
47
static void thunar_templates_action_menu_shown (GtkWidget *menu,
48
ThunarTemplatesAction *templates_action);
52
struct _ThunarTemplatesActionClass
54
GtkActionClass __parent__;
56
void (*create_empty_file) (ThunarTemplatesAction *templates_action);
57
void (*create_template) (ThunarTemplatesAction *templates_action,
58
const ThunarFile *file);
61
struct _ThunarTemplatesAction
70
static guint templates_action_signals[LAST_SIGNAL];
74
G_DEFINE_TYPE (ThunarTemplatesAction, thunar_templates_action, GTK_TYPE_ACTION)
79
thunar_templates_action_class_init (ThunarTemplatesActionClass *klass)
81
GtkActionClass *gtkaction_class;
82
GObjectClass *gobject_class;
84
gobject_class = G_OBJECT_CLASS (klass);
85
gobject_class->finalize = thunar_templates_action_finalize;
87
gtkaction_class = GTK_ACTION_CLASS (klass);
88
gtkaction_class->create_menu_item = thunar_templates_action_create_menu_item;
91
* ThunarTemplatesAction::create-empty-file:
92
* @templates_action : a #ThunarTemplatesAction.
94
* Emitted by @templates_action whenever the user requests to
95
* create a new empty file.
97
templates_action_signals[CREATE_EMPTY_FILE] =
98
g_signal_new (I_("create-empty-file"),
99
G_TYPE_FROM_CLASS (klass),
101
G_STRUCT_OFFSET (ThunarTemplatesActionClass, create_empty_file),
102
NULL, NULL, g_cclosure_marshal_VOID__VOID,
106
* ThunarTemplatesAction::create-template:
107
* @templates_action : a #ThunarTemplatesAction.
108
* @file : the #ThunarFile of the template file.
110
* Emitted by @templates_action whenever the user requests to
111
* create a new file based on the template referred to by
114
templates_action_signals[CREATE_TEMPLATE] =
115
g_signal_new (I_("create-template"),
116
G_TYPE_FROM_CLASS (klass),
118
G_STRUCT_OFFSET (ThunarTemplatesActionClass, create_template),
119
NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
120
G_TYPE_NONE, 1, THUNAR_TYPE_FILE);
126
thunar_templates_action_init (ThunarTemplatesAction *templates_action)
128
templates_action->job = NULL;
134
thunar_templates_action_finalize (GObject *object)
136
ThunarTemplatesAction *templates_action = THUNAR_TEMPLATES_ACTION (object);
138
if (templates_action->job != NULL)
140
g_signal_handlers_disconnect_matched (templates_action->job,
141
G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
143
g_object_unref (templates_action->job);
146
(*G_OBJECT_CLASS (thunar_templates_action_parent_class)->finalize) (object);
152
thunar_templates_action_create_menu_item (GtkAction *action)
157
_thunar_return_val_if_fail (THUNAR_IS_TEMPLATES_ACTION (action), NULL);
159
/* let GtkAction allocate the menu item */
160
item = (*GTK_ACTION_CLASS (thunar_templates_action_parent_class)->create_menu_item) (action);
162
/* associate an empty submenu with the item (will be filled when shown) */
163
menu = gtk_menu_new ();
164
g_signal_connect (G_OBJECT (menu), "show", G_CALLBACK (thunar_templates_action_menu_shown), action);
165
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
173
item_activated (GtkWidget *item,
174
ThunarTemplatesAction *templates_action)
176
const ThunarFile *file;
178
_thunar_return_if_fail (THUNAR_IS_TEMPLATES_ACTION (templates_action));
179
_thunar_return_if_fail (GTK_IS_WIDGET (item));
181
/* check if a file is set for the item (else it's the "Empty File" item) */
182
file = g_object_get_data (G_OBJECT (item), I_("thunar-file"));
183
if (G_UNLIKELY (file != NULL))
185
g_signal_emit (G_OBJECT (templates_action),
186
templates_action_signals[CREATE_TEMPLATE], 0, file);
190
g_signal_emit (G_OBJECT (templates_action),
191
templates_action_signals[CREATE_EMPTY_FILE], 0);
198
find_parent_menu (ThunarFile *file,
202
GtkWidget *parent_menu = NULL;
207
/* determine the parent of the file */
208
parent = g_file_get_parent (thunar_file_get_file (file));
210
/* check if the file has a parent at all */
214
/* iterate over all dirs and menu items */
215
for (lp = g_list_first (dirs), ip = g_list_first (items);
216
parent_menu == NULL && lp != NULL && ip != NULL;
217
lp = lp->next, ip = ip->next)
219
/* check if the current dir/item is the parent of our file */
220
if (g_file_equal (parent, thunar_file_get_file (lp->data)))
222
/* we want to insert an item for the file in this menu */
223
parent_menu = gtk_menu_item_get_submenu (ip->data);
227
/* destroy the parent GFile */
228
g_object_unref (parent);
236
compare_files (ThunarFile *a,
244
file_a = thunar_file_get_file (a);
245
file_b = thunar_file_get_file (b);
247
/* check whether the files are equal */
248
if (g_file_equal (file_a, file_b))
251
/* directories always come first */
252
if (thunar_file_get_kind (a) == G_FILE_TYPE_DIRECTORY
253
&& thunar_file_get_kind (b) != G_FILE_TYPE_DIRECTORY)
257
else if (thunar_file_get_kind (a) != G_FILE_TYPE_DIRECTORY
258
&& thunar_file_get_kind (b) == G_FILE_TYPE_DIRECTORY)
263
/* ancestors come first */
264
if (g_file_has_prefix (file_b, file_a))
266
else if (g_file_has_prefix (file_a, file_b))
269
parent_a = g_file_get_parent (file_a);
270
parent_b = g_file_get_parent (file_b);
272
if (g_file_equal (parent_a, parent_b))
274
g_object_unref (parent_a);
275
g_object_unref (parent_b);
277
/* compare siblings by their display name */
278
return g_utf8_collate (thunar_file_get_display_name (a),
279
thunar_file_get_display_name (b));
282
/* again, ancestors come first */
283
if (g_file_has_prefix (file_b, parent_a))
285
g_object_unref (parent_a);
286
g_object_unref (parent_b);
290
else if (g_file_has_prefix (file_a, parent_b))
292
g_object_unref (parent_a);
293
g_object_unref (parent_b);
298
g_object_unref (parent_a);
299
g_object_unref (parent_b);
307
thunar_templates_action_files_ready (ThunarJob *job,
309
ThunarTemplatesAction *templates_action)
311
ThunarIconFactory *icon_factory;
315
GtkWidget *parent_menu;
322
GList *parent_menus = NULL;
325
/* determine the menu to add the items and submenus to */
326
menu = g_object_get_data (G_OBJECT (job), "menu");
328
/* do nothing if there is no menu */
332
/* get the icon factory */
333
icon_factory = thunar_icon_factory_get_default ();
335
/* sort items so that directories come before files and ancestors come
336
* before descendants */
337
files = g_list_sort (files, (GCompareFunc) compare_files);
339
for (lp = g_list_first (files); lp != NULL; lp = lp->next)
343
/* determine the parent menu for this file/directory */
344
parent_menu = find_parent_menu (file, dirs, items);
345
parent_menu = parent_menu == NULL ? menu : parent_menu;
347
if (thunar_file_get_kind (file) == G_FILE_TYPE_DIRECTORY)
349
/* allocate a new submenu for the directory */
350
submenu = gtk_menu_new ();
351
g_object_ref_sink (G_OBJECT (submenu));
352
gtk_menu_set_screen (GTK_MENU (submenu), gtk_widget_get_screen (menu));
354
/* allocate a new menu item for the directory */
355
item = gtk_image_menu_item_new_with_label (thunar_file_get_display_name (file));
356
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
358
/* prepend the directory, its item and the parent menu it should
359
* later be added to to the respective lists */
360
dirs = g_list_prepend (dirs, file);
361
items = g_list_prepend (items, item);
362
parent_menus = g_list_prepend (parent_menus, parent_menu);
366
/* allocate a new menu item */
367
item = gtk_image_menu_item_new_with_label (thunar_file_get_display_name (file));
368
g_object_set_data_full (G_OBJECT (item), I_("thunar-file"),
369
g_object_ref (file), g_object_unref);
370
g_signal_connect (item, "activate", G_CALLBACK (item_activated),
372
gtk_menu_shell_append (GTK_MENU_SHELL (parent_menu), item);
373
gtk_widget_show (item);
376
/* determine the icon for this file/directory */
377
icon = thunar_icon_factory_load_file_icon (icon_factory, file,
378
THUNAR_FILE_ICON_STATE_DEFAULT,
381
/* allocate an image based on the icon */
382
image = gtk_image_new_from_pixbuf (icon);
383
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
385
/* release the icon reference */
386
g_object_unref (icon);
389
/* add all non-empty directory items to their parent menu */
390
for (lp = items, pp = parent_menus;
391
lp != NULL && pp != NULL;
392
lp = lp->next, pp = pp->next)
394
/* determine the submenu for this directory item */
395
submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (lp->data));
397
if (GTK_MENU_SHELL (submenu)->children == NULL)
399
/* the directory submenu is empty, destroy it */
400
gtk_widget_destroy (lp->data);
404
/* the directory has template files, so add it to its parent menu */
405
gtk_menu_shell_prepend (GTK_MENU_SHELL (pp->data), lp->data);
406
gtk_widget_show (lp->data);
413
g_list_free (parent_menus);
415
/* release the icon factory */
416
g_object_unref (icon_factory);
418
/* let the job destroy the file list */
425
thunar_templates_action_load_error (ThunarJob *job,
427
ThunarTemplatesAction *templates_action)
432
_thunar_return_if_fail (THUNAR_IS_JOB (job));
433
_thunar_return_if_fail (error != NULL);
434
_thunar_return_if_fail (THUNAR_IS_TEMPLATES_ACTION (templates_action));
435
_thunar_return_if_fail (templates_action->job == job);
437
menu = g_object_get_data (G_OBJECT (job), "menu");
439
/* check if any items were added to the menu */
440
if (G_LIKELY (menu != NULL && GTK_MENU_SHELL (menu)->children == NULL))
442
/* tell the user that no templates were found */
443
item = gtk_menu_item_new_with_label (error->message);
444
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
445
gtk_widget_set_sensitive (item, FALSE);
446
gtk_widget_show (item);
453
thunar_templates_action_load_finished (ThunarJob *job,
454
ThunarTemplatesAction *templates_action)
460
_thunar_return_if_fail (THUNAR_IS_JOB (job));
461
_thunar_return_if_fail (THUNAR_IS_TEMPLATES_ACTION (templates_action));
462
_thunar_return_if_fail (templates_action->job == job);
464
menu = g_object_get_data (G_OBJECT (job), "menu");
465
if (G_LIKELY (menu != NULL))
467
/* append a menu separator */
468
item = gtk_separator_menu_item_new ();
469
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
470
gtk_widget_show (item);
472
/* add the "Empty File" item */
473
item = gtk_image_menu_item_new_with_mnemonic (_("_Empty File"));
474
g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (item_activated),
476
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
477
gtk_widget_show (item);
479
/* add the icon for the emtpy file item */
480
image = gtk_image_new_from_stock (GTK_STOCK_NEW, GTK_ICON_SIZE_MENU);
481
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
484
g_signal_handlers_disconnect_matched (job, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
486
g_object_unref (job);
492
thunar_templates_action_menu_shown (GtkWidget *menu,
493
ThunarTemplatesAction *templates_action)
497
_thunar_return_if_fail (THUNAR_IS_TEMPLATES_ACTION (templates_action));
498
_thunar_return_if_fail (GTK_IS_MENU_SHELL (menu));
500
/* drop all existing children of the menu first */
501
children = gtk_container_get_children (GTK_CONTAINER (menu));
502
g_list_free_full (children, (GDestroyNotify) gtk_widget_destroy);
504
if (G_LIKELY (templates_action->job == NULL))
506
templates_action->job = thunar_misc_jobs_load_template_files (menu);
507
g_object_add_weak_pointer (G_OBJECT (templates_action->job),
508
(gpointer) &templates_action->job);
510
g_signal_connect (templates_action->job, "files-ready",
511
G_CALLBACK (thunar_templates_action_files_ready),
513
g_signal_connect (templates_action->job, "error",
514
G_CALLBACK (thunar_templates_action_load_error),
516
g_signal_connect (templates_action->job, "finished",
517
G_CALLBACK (thunar_templates_action_load_finished),
525
* thunar_templates_action_new:
526
* @name : the internal name of the action.
527
* @label : the label for the action.
529
* Allocates a new #ThunarTemplatesAction with the given
532
* Return value: the newly allocated #ThunarTemplatesAction.
535
thunar_templates_action_new (const gchar *name,
538
_thunar_return_val_if_fail (name != NULL, NULL);
539
_thunar_return_val_if_fail (label != NULL, NULL);
541
return g_object_new (THUNAR_TYPE_TEMPLATES_ACTION,
542
"hide-if-empty", FALSE,