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