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