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