1 /*
2  *  fm-ditem-page.c: Desktop item editing support
3  *
4  *  Copyright (C) 2004 James Willcox
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Library General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public
17  *  License along with this library; if not, write to the Free
18  *  Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  *  Authors: James Willcox <jwillcox@gnome.org>
21  *
22  */
23 
24 #include <config.h>
25 #include <string.h>
26 
27 #include <gtk/gtk.h>
28 #include <glib/gi18n.h>
29 
30 #include <eel/eel-glib-extensions.h>
31 
32 #include <libcaja-extension/caja-extension-types.h>
33 #include <libcaja-extension/caja-file-info.h>
34 #include <libcaja-private/caja-file.h>
35 #include <libcaja-private/caja-file-attributes.h>
36 
37 #include "fm-ditem-page.h"
38 
39 #define MAIN_GROUP "Desktop Entry"
40 
41 typedef struct ItemEntry
42 {
43     const char *field;
44     const char *description;
45     char *current_value;
46     gboolean localized;
47     gboolean filename;
48 } ItemEntry;
49 
50 enum
51 {
52     TARGET_URI_LIST
53 };
54 
55 static const GtkTargetEntry target_table[] =
56 {
57     { "text/uri-list",  0, TARGET_URI_LIST }
58 };
59 
60 static gboolean
_g_key_file_load_from_gfile(GKeyFile * key_file,GFile * file,GKeyFileFlags flags,GError ** error)61 _g_key_file_load_from_gfile (GKeyFile *key_file,
62                              GFile *file,
63                              GKeyFileFlags flags,
64                              GError **error)
65 {
66     char *data;
67     gsize len;
68     gboolean res;
69 
70     if (!g_file_load_contents (file, NULL, &data, &len, NULL, error))
71     {
72         return FALSE;
73     }
74 
75     res = g_key_file_load_from_data (key_file, data, len, flags, error);
76 
77     g_free (data);
78 
79     return res;
80 }
81 
82 static gboolean
_g_key_file_save_to_uri(GKeyFile * key_file,const char * uri,GError ** error)83 _g_key_file_save_to_uri (GKeyFile *key_file,
84                          const char *uri,
85                          GError  **error)
86 {
87     GFile *file;
88     char *data;
89     gsize len;
90 
91     data = g_key_file_to_data (key_file, &len, error);
92     if (data == NULL)
93     {
94         return FALSE;
95     }
96     file = g_file_new_for_uri (uri);
97     if (!g_file_replace_contents (file,
98                                   data, len,
99                                   NULL, FALSE,
100                                   G_FILE_CREATE_NONE,
101                                   NULL, NULL, error))
102     {
103         g_object_unref (file);
104         g_free (data);
105         return FALSE;
106     }
107     g_object_unref (file);
108     g_free (data);
109     return TRUE;
110 }
111 
112 static GKeyFile *
_g_key_file_new_from_file(GFile * file,GKeyFileFlags flags,GError ** error)113 _g_key_file_new_from_file (GFile *file,
114                            GKeyFileFlags flags,
115                            GError **error)
116 {
117     GKeyFile *key_file;
118 
119     key_file = g_key_file_new ();
120     if (!_g_key_file_load_from_gfile (key_file, file, flags, error))
121     {
122         g_key_file_free (key_file);
123         key_file = NULL;
124     }
125     return key_file;
126 }
127 
128 static GKeyFile *
_g_key_file_new_from_uri(const char * uri,GKeyFileFlags flags,GError ** error)129 _g_key_file_new_from_uri (const char *uri,
130                           GKeyFileFlags flags,
131                           GError **error)
132 {
133     GKeyFile *key_file;
134     GFile *file;
135 
136     file = g_file_new_for_uri (uri);
137     key_file = _g_key_file_new_from_file (file, flags, error);
138     g_object_unref (file);
139     return key_file;
140 }
141 
142 static ItemEntry *
item_entry_new(const char * field,const char * description,gboolean localized,gboolean filename)143 item_entry_new (const char *field,
144                 const char *description,
145                 gboolean localized,
146                 gboolean filename)
147 {
148     ItemEntry *entry;
149 
150     entry = g_new0 (ItemEntry, 1);
151     entry->field = field;
152     entry->description = description;
153     entry->localized = localized;
154     entry->filename = filename;
155 
156     return entry;
157 }
158 
159 static void
item_entry_free(ItemEntry * entry)160 item_entry_free (ItemEntry *entry)
161 {
162     g_free (entry->current_value);
163     g_free (entry);
164 }
165 
166 static void
fm_ditem_page_url_drag_data_received(GtkWidget * widget,GdkDragContext * context,int x,int y,GtkSelectionData * selection_data,guint info,guint time,GtkEntry * entry)167 fm_ditem_page_url_drag_data_received (GtkWidget *widget, GdkDragContext *context,
168                                       int x, int y,
169                                       GtkSelectionData *selection_data,
170                                       guint info, guint time,
171                                       GtkEntry *entry)
172 {
173     char **uris;
174     gboolean exactly_one;
175     char *path;
176 
177     uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0);
178     exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0');
179 
180     if (!exactly_one)
181     {
182         g_strfreev (uris);
183         return;
184     }
185 
186     path = g_filename_from_uri (uris[0], NULL, NULL);
187     if (path != NULL)
188     {
189         gtk_entry_set_text (entry, path);
190         g_free (path);
191     }
192     else
193     {
194         gtk_entry_set_text (entry, uris[0]);
195     }
196 
197     g_strfreev (uris);
198 }
199 
200 static void
fm_ditem_page_exec_drag_data_received(GtkWidget * widget,GdkDragContext * context,int x,int y,GtkSelectionData * selection_data,guint info,guint time,GtkEntry * entry)201 fm_ditem_page_exec_drag_data_received (GtkWidget *widget, GdkDragContext *context,
202                                        int x, int y,
203                                        GtkSelectionData *selection_data,
204                                        guint info, guint time,
205                                        GtkEntry *entry)
206 {
207     char **uris;
208     gboolean exactly_one;
209     CajaFile *file;
210     char *uri, *exec;
211 
212     uris = g_strsplit (gtk_selection_data_get_data (selection_data), "\r\n", 0);
213     exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0');
214 
215     if (!exactly_one)
216     {
217         g_strfreev (uris);
218         return;
219     }
220 
221     file = caja_file_get_by_uri (uris[0]);
222     g_strfreev (uris);
223 
224     g_return_if_fail (file != NULL);
225 
226     uri = caja_file_get_uri (file);
227     if (caja_file_is_mime_type (file, "application/x-desktop"))
228     {
229         GKeyFile *key_file;
230 
231         key_file = _g_key_file_new_from_uri (uri, G_KEY_FILE_NONE, NULL);
232 
233         if (key_file != NULL)
234         {
235             char *type;
236 
237             type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL);
238             if (type != NULL && strcmp (type, "Application") == 0)
239             {
240                 exec = g_key_file_get_string (key_file, MAIN_GROUP, "Exec", NULL);
241                 if (exec != NULL)
242                 {
243                     g_free (uri);
244                     uri = exec;
245                 }
246             }
247             g_free (type);
248             g_key_file_free (key_file);
249         }
250     }
251     gtk_entry_set_text (entry,
252                         uri?uri:"");
253     gtk_widget_grab_focus (GTK_WIDGET (entry));
254 
255     g_free (uri);
256 
257     caja_file_unref (file);
258 }
259 
260 static void
save_entry(GtkEntry * entry,GKeyFile * key_file,const char * uri)261 save_entry (GtkEntry *entry, GKeyFile *key_file, const char *uri)
262 {
263     GError *error;
264     ItemEntry *item_entry;
265     const char *val;
266 
267     item_entry = g_object_get_data (G_OBJECT (entry), "item_entry");
268     val = gtk_entry_get_text (entry);
269 
270     if (strcmp (val, item_entry->current_value) == 0)
271     {
272         return; /* No actual change, don't update file */
273     }
274 
275     g_free (item_entry->current_value);
276     item_entry->current_value = g_strdup (val);
277 
278     if (item_entry->localized)
279     {
280         gchar **languages;
281 
282         languages = (gchar **) g_get_language_names ();
283         g_key_file_set_locale_string (key_file, MAIN_GROUP, item_entry->field, languages[0], val);
284     }
285     else
286     {
287         g_key_file_set_string (key_file, MAIN_GROUP, item_entry->field, val);
288     }
289 
290     error = NULL;
291 
292     if (!_g_key_file_save_to_uri (key_file, uri, &error))
293     {
294         g_warning ("%s", error->message);
295         g_error_free (error);
296     }
297 }
298 
299 static void
entry_activate_cb(GtkWidget * entry,GtkWidget * container)300 entry_activate_cb (GtkWidget *entry,
301                    GtkWidget *container)
302 {
303     const char *uri;
304     GKeyFile *key_file;
305 
306     uri = g_object_get_data (G_OBJECT (container), "uri");
307     key_file = g_object_get_data (G_OBJECT (container), "keyfile");
308     save_entry (GTK_ENTRY (entry), key_file, uri);
309 }
310 
311 static gboolean
entry_focus_out_cb(GtkWidget * entry,GdkEventFocus * event,GtkWidget * container)312 entry_focus_out_cb (GtkWidget *entry,
313                     GdkEventFocus *event,
314                     GtkWidget *container)
315 {
316     const char *uri;
317     GKeyFile *key_file;
318 
319     uri = g_object_get_data (G_OBJECT (container), "uri");
320     key_file = g_object_get_data (G_OBJECT (container), "keyfile");
321     save_entry (GTK_ENTRY (entry), key_file, uri);
322     return FALSE;
323 }
324 
325 static GtkWidget *
build_grid(GtkWidget * container,GKeyFile * key_file,GtkSizeGroup * label_size_group,GList * entries)326 build_grid (GtkWidget *container,
327              GKeyFile *key_file,
328              GtkSizeGroup *label_size_group,
329              GList *entries)
330 {
331     GList *l;
332     char *val;
333     GtkWidget *grid;
334     GtkWidget *label;
335     GtkWidget *entry = NULL;
336 
337     grid = gtk_grid_new ();
338     gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL);
339     gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
340     gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
341 
342     for (l = entries; l; l = l->next)
343     {
344         ItemEntry *item_entry = (ItemEntry *)l->data;
345         char *label_text;
346 
347         label_text = g_strdup_printf ("%s:", item_entry->description);
348         label = gtk_label_new (label_text);
349         gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
350         g_free (label_text);
351         gtk_label_set_xalign (GTK_LABEL (label), 0.0);
352         gtk_size_group_add_widget (label_size_group, label);
353 
354         entry = gtk_entry_new ();
355         gtk_widget_set_hexpand (entry, TRUE);
356 
357 
358         if (item_entry->localized)
359         {
360             val = g_key_file_get_locale_string (key_file,
361                                                 MAIN_GROUP,
362                                                 item_entry->field,
363                                                 NULL, NULL);
364         }
365         else
366         {
367             val = g_key_file_get_string (key_file,
368                                          MAIN_GROUP,
369                                          item_entry->field,
370                                          NULL);
371         }
372 
373         item_entry->current_value = g_strdup (val?val:"");
374         gtk_entry_set_text (GTK_ENTRY (entry), item_entry->current_value);
375         g_free (val);
376 
377         gtk_container_add (GTK_CONTAINER (grid), label);
378         gtk_grid_attach_next_to (GTK_GRID (grid), entry, label,
379                                   GTK_POS_RIGHT, 1, 1);
380 
381         g_signal_connect (entry, "activate",
382                           G_CALLBACK (entry_activate_cb),
383                           container);
384         g_signal_connect (entry, "focus_out_event",
385                           G_CALLBACK (entry_focus_out_cb),
386                           container);
387 
388         g_object_set_data_full (G_OBJECT (entry), "item_entry", item_entry,
389                                 (GDestroyNotify)item_entry_free);
390 
391         if (item_entry->filename)
392         {
393             gtk_drag_dest_set (GTK_WIDGET (entry),
394                                GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
395                                target_table, G_N_ELEMENTS (target_table),
396                                GDK_ACTION_COPY | GDK_ACTION_MOVE);
397 
398             g_signal_connect (entry, "drag_data_received",
399                               G_CALLBACK (fm_ditem_page_url_drag_data_received),
400                               entry);
401         }
402         else if (strcmp (item_entry->field, "Exec") == 0)
403         {
404             gtk_drag_dest_set (GTK_WIDGET (entry),
405                                GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
406                                target_table, G_N_ELEMENTS (target_table),
407                                GDK_ACTION_COPY | GDK_ACTION_MOVE);
408 
409             g_signal_connect (entry, "drag_data_received",
410                               G_CALLBACK (fm_ditem_page_exec_drag_data_received),
411                               entry);
412         }
413     }
414 
415     /* append dummy row */
416     label = gtk_label_new ("");
417     gtk_container_add (GTK_CONTAINER (grid), label);
418     gtk_size_group_add_widget (label_size_group, label);
419 
420     gtk_widget_show_all (grid);
421     return grid;
422 }
423 
424 static void
create_page(GKeyFile * key_file,GtkWidget * box)425 create_page (GKeyFile *key_file, GtkWidget *box)
426 {
427     GtkWidget *grid;
428     GList *entries;
429     GtkSizeGroup *label_size_group;
430     char *type;
431 
432     entries = NULL;
433 
434     type = g_key_file_get_string (key_file, MAIN_GROUP, "Type", NULL);
435 
436     if (g_strcmp0 (type, "Link") == 0)
437     {
438         entries = g_list_prepend (entries,
439                                   item_entry_new ("Comment",
440                                           _("Comment"), TRUE, FALSE));
441         entries = g_list_prepend (entries,
442                                   item_entry_new ("URL",
443                                           _("URL"), FALSE, TRUE));
444         entries = g_list_prepend (entries,
445                                   item_entry_new ("GenericName",
446                                           _("Description"), TRUE, FALSE));
447     }
448     else if (g_strcmp0 (type, "Application") == 0)
449     {
450         entries = g_list_prepend (entries,
451                                   item_entry_new ("Comment",
452                                           _("Comment"), TRUE, FALSE));
453         entries = g_list_prepend (entries,
454                                   item_entry_new ("Exec",
455                                           _("Command"), FALSE, FALSE));
456         entries = g_list_prepend (entries,
457                                   item_entry_new ("GenericName",
458                                           _("Description"), TRUE, FALSE));
459     }
460     else
461     {
462         /* we only handle launchers and links */
463 
464         /* ensure that we build an empty gid with a dummy row at the end */
465         goto build_grid;
466     }
467     g_free (type);
468 
469 build_grid:
470     label_size_group = g_object_get_data (G_OBJECT (box), "label-size-group");
471 
472     grid = build_grid (box, key_file, label_size_group, entries);
473     g_list_free (entries);
474 
475     gtk_box_pack_start (GTK_BOX (box), grid, FALSE, TRUE, 0);
476 
477     gtk_widget_show_all (GTK_WIDGET (box));
478 }
479 
480 
481 static void
ditem_read_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)482 ditem_read_cb (GObject *source_object,
483                GAsyncResult *res,
484                gpointer user_data)
485 {
486     GtkWidget *box;
487     gsize file_size;
488     char *file_contents;
489 
490     box = GTK_WIDGET (user_data);
491 
492     if (g_file_load_contents_finish (G_FILE (source_object),
493                                      res,
494                                      &file_contents, &file_size,
495                                      NULL, NULL))
496     {
497         GKeyFile *key_file;
498 
499         key_file = g_key_file_new ();
500         g_object_set_data_full (G_OBJECT (box), "keyfile", key_file, (GDestroyNotify)g_key_file_free);
501         if (g_key_file_load_from_data (key_file, file_contents, file_size, 0, NULL))
502         {
503             create_page (key_file, box);
504         }
505         g_free (file_contents);
506 
507     }
508     g_object_unref (box);
509 }
510 
511 static void
fm_ditem_page_create_begin(const char * uri,GtkWidget * box)512 fm_ditem_page_create_begin (const char *uri,
513                             GtkWidget *box)
514 {
515     GFile *location;
516 
517     location = g_file_new_for_uri (uri);
518     g_object_set_data_full (G_OBJECT (box), "uri", g_strdup (uri), g_free);
519     g_file_load_contents_async (location, NULL, ditem_read_cb, g_object_ref (box));
520     g_object_unref (location);
521 }
522 
523 GtkWidget *
fm_ditem_page_make_box(GtkSizeGroup * label_size_group,GList * files)524 fm_ditem_page_make_box (GtkSizeGroup *label_size_group,
525                         GList *files)
526 {
527     CajaFileInfo *info;
528     char *uri;
529     GtkWidget *box;
530 
531     g_assert (fm_ditem_page_should_show (files));
532 
533     box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
534     g_object_set_data_full (G_OBJECT (box), "label-size-group",
535                             label_size_group, (GDestroyNotify) g_object_unref);
536 
537     info = CAJA_FILE_INFO (files->data);
538 
539     uri = caja_file_info_get_uri (info);
540     fm_ditem_page_create_begin (uri, box);
541     g_free (uri);
542 
543     return box;
544 }
545 
546 gboolean
fm_ditem_page_should_show(GList * files)547 fm_ditem_page_should_show (GList *files)
548 {
549     CajaFileInfo *info;
550 
551     if (!files || files->next)
552     {
553         return FALSE;
554     }
555 
556     info = CAJA_FILE_INFO (files->data);
557 
558     if (!caja_file_info_is_mime_type (info, "application/x-desktop"))
559     {
560         return FALSE;
561     }
562 
563     return TRUE;
564 }
565 
566