1 | /* GTK - The GIMP Toolkit |
2 | * gtkfilechooserutils.c: Private utility functions useful for |
3 | * implementing a GtkFileChooser interface |
4 | * Copyright (C) 2003, Red Hat, Inc. |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Lesser 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 | * Lesser General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Lesser General Public |
17 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | #include "config.h" |
21 | #include "gtkfilechooserutils.h" |
22 | #include "gtkfilechooser.h" |
23 | #include "gtktypebuiltins.h" |
24 | #include "gtkintl.h" |
25 | |
26 | |
27 | static gboolean delegate_set_current_folder (GtkFileChooser *chooser, |
28 | GFile *file, |
29 | GError **error); |
30 | static GFile * delegate_get_current_folder (GtkFileChooser *chooser); |
31 | static void delegate_set_current_name (GtkFileChooser *chooser, |
32 | const char *name); |
33 | static char * delegate_get_current_name (GtkFileChooser *chooser); |
34 | static gboolean delegate_select_file (GtkFileChooser *chooser, |
35 | GFile *file, |
36 | GError **error); |
37 | static void delegate_unselect_file (GtkFileChooser *chooser, |
38 | GFile *file); |
39 | static void delegate_select_all (GtkFileChooser *chooser); |
40 | static void delegate_unselect_all (GtkFileChooser *chooser); |
41 | static GListModel * delegate_get_files (GtkFileChooser *chooser); |
42 | static void delegate_add_filter (GtkFileChooser *chooser, |
43 | GtkFileFilter *filter); |
44 | static void delegate_remove_filter (GtkFileChooser *chooser, |
45 | GtkFileFilter *filter); |
46 | static GListModel * delegate_get_filters (GtkFileChooser *chooser); |
47 | static gboolean delegate_add_shortcut_folder (GtkFileChooser *chooser, |
48 | GFile *file, |
49 | GError **error); |
50 | static gboolean delegate_remove_shortcut_folder (GtkFileChooser *chooser, |
51 | GFile *file, |
52 | GError **error); |
53 | static GListModel * delegate_get_shortcut_folders (GtkFileChooser *chooser); |
54 | static void delegate_notify (GObject *object, |
55 | GParamSpec *pspec, |
56 | gpointer data); |
57 | |
58 | static void delegate_add_choice (GtkFileChooser *chooser, |
59 | const char *id, |
60 | const char *label, |
61 | const char **options, |
62 | const char **option_labels); |
63 | static void delegate_remove_choice (GtkFileChooser *chooser, |
64 | const char *id); |
65 | static void delegate_set_choice (GtkFileChooser *chooser, |
66 | const char *id, |
67 | const char *option); |
68 | static const char * delegate_get_choice (GtkFileChooser *chooser, |
69 | const char *id); |
70 | |
71 | |
72 | /** |
73 | * _gtk_file_chooser_install_properties: |
74 | * @klass: the class structure for a type deriving from `GObject` |
75 | * |
76 | * Installs the necessary properties for a class implementing |
77 | * `GtkFileChooser`. |
78 | * |
79 | * A `GtkParamSpecOverride` property is installed for each property, |
80 | * using the values from the `GtkFileChooserProp` enumeration. The |
81 | * caller must make sure itself that the enumeration values don’t |
82 | * collide with some other property values they are using. |
83 | */ |
84 | void |
85 | _gtk_file_chooser_install_properties (GObjectClass *klass) |
86 | { |
87 | g_object_class_override_property (oclass: klass, |
88 | property_id: GTK_FILE_CHOOSER_PROP_ACTION, |
89 | name: "action" ); |
90 | g_object_class_override_property (oclass: klass, |
91 | property_id: GTK_FILE_CHOOSER_PROP_FILTER, |
92 | name: "filter" ); |
93 | g_object_class_override_property (oclass: klass, |
94 | property_id: GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE, |
95 | name: "select-multiple" ); |
96 | g_object_class_override_property (oclass: klass, |
97 | property_id: GTK_FILE_CHOOSER_PROP_CREATE_FOLDERS, |
98 | name: "create-folders" ); |
99 | g_object_class_override_property (oclass: klass, |
100 | property_id: GTK_FILE_CHOOSER_PROP_FILTERS, |
101 | name: "filters" ); |
102 | g_object_class_override_property (oclass: klass, |
103 | property_id: GTK_FILE_CHOOSER_PROP_SHORTCUT_FOLDERS, |
104 | name: "shortcut-folders" ); |
105 | } |
106 | |
107 | /** |
108 | * _gtk_file_chooser_delegate_iface_init: |
109 | * @iface: a `GtkFileChoserIface` structure |
110 | * |
111 | * An interface-initialization function for use in cases where |
112 | * an object is simply delegating the methods, signals of |
113 | * the `GtkFileChooser` interface to another object. |
114 | * |
115 | * _gtk_file_chooser_set_delegate() must be called on each |
116 | * instance of the object so that the delegate object can |
117 | * be found. |
118 | **/ |
119 | void |
120 | _gtk_file_chooser_delegate_iface_init (GtkFileChooserIface *iface) |
121 | { |
122 | iface->set_current_folder = delegate_set_current_folder; |
123 | iface->get_current_folder = delegate_get_current_folder; |
124 | iface->set_current_name = delegate_set_current_name; |
125 | iface->get_current_name = delegate_get_current_name; |
126 | iface->select_file = delegate_select_file; |
127 | iface->unselect_file = delegate_unselect_file; |
128 | iface->select_all = delegate_select_all; |
129 | iface->unselect_all = delegate_unselect_all; |
130 | iface->get_files = delegate_get_files; |
131 | iface->add_filter = delegate_add_filter; |
132 | iface->remove_filter = delegate_remove_filter; |
133 | iface->get_filters = delegate_get_filters; |
134 | iface->add_shortcut_folder = delegate_add_shortcut_folder; |
135 | iface->remove_shortcut_folder = delegate_remove_shortcut_folder; |
136 | iface->get_shortcut_folders = delegate_get_shortcut_folders; |
137 | iface->add_choice = delegate_add_choice; |
138 | iface->remove_choice = delegate_remove_choice; |
139 | iface->set_choice = delegate_set_choice; |
140 | iface->get_choice = delegate_get_choice; |
141 | } |
142 | |
143 | /** |
144 | * _gtk_file_chooser_set_delegate: |
145 | * @receiver: a `GObject` implementing `GtkFileChooser` |
146 | * @delegate: another `GObject` implementing `GtkFileChooser` |
147 | * |
148 | * Establishes that calls on @receiver for `GtkFileChooser` |
149 | * methods should be delegated to @delegate, and that |
150 | * `GtkFileChooser` signals emitted on @delegate should be |
151 | * forwarded to @receiver. Must be used in conjunction with |
152 | * _gtk_file_chooser_delegate_iface_init(). |
153 | **/ |
154 | void |
155 | _gtk_file_chooser_set_delegate (GtkFileChooser *receiver, |
156 | GtkFileChooser *delegate) |
157 | { |
158 | g_return_if_fail (GTK_IS_FILE_CHOOSER (receiver)); |
159 | g_return_if_fail (GTK_IS_FILE_CHOOSER (delegate)); |
160 | |
161 | g_object_set_data (G_OBJECT (receiver), I_("gtk-file-chooser-delegate" ), data: delegate); |
162 | g_signal_connect (delegate, "notify" , |
163 | G_CALLBACK (delegate_notify), receiver); |
164 | } |
165 | |
166 | GQuark |
167 | _gtk_file_chooser_delegate_get_quark (void) |
168 | { |
169 | static GQuark quark = 0; |
170 | |
171 | if (G_UNLIKELY (quark == 0)) |
172 | quark = g_quark_from_static_string (string: "gtk-file-chooser-delegate" ); |
173 | |
174 | return quark; |
175 | } |
176 | |
177 | static GtkFileChooser * |
178 | get_delegate (GtkFileChooser *receiver) |
179 | { |
180 | return g_object_get_qdata (G_OBJECT (receiver), |
181 | GTK_FILE_CHOOSER_DELEGATE_QUARK); |
182 | } |
183 | |
184 | static gboolean |
185 | delegate_select_file (GtkFileChooser *chooser, |
186 | GFile *file, |
187 | GError **error) |
188 | { |
189 | return gtk_file_chooser_select_file (chooser: get_delegate (receiver: chooser), file, error); |
190 | } |
191 | |
192 | static void |
193 | delegate_unselect_file (GtkFileChooser *chooser, |
194 | GFile *file) |
195 | { |
196 | gtk_file_chooser_unselect_file (chooser: get_delegate (receiver: chooser), file); |
197 | } |
198 | |
199 | static void |
200 | delegate_select_all (GtkFileChooser *chooser) |
201 | { |
202 | gtk_file_chooser_select_all (chooser: get_delegate (receiver: chooser)); |
203 | } |
204 | |
205 | static void |
206 | delegate_unselect_all (GtkFileChooser *chooser) |
207 | { |
208 | gtk_file_chooser_unselect_all (chooser: get_delegate (receiver: chooser)); |
209 | } |
210 | |
211 | static GListModel * |
212 | delegate_get_files (GtkFileChooser *chooser) |
213 | { |
214 | return gtk_file_chooser_get_files (chooser: get_delegate (receiver: chooser)); |
215 | } |
216 | |
217 | static void |
218 | delegate_add_filter (GtkFileChooser *chooser, |
219 | GtkFileFilter *filter) |
220 | { |
221 | gtk_file_chooser_add_filter (chooser: get_delegate (receiver: chooser), filter); |
222 | } |
223 | |
224 | static void |
225 | delegate_remove_filter (GtkFileChooser *chooser, |
226 | GtkFileFilter *filter) |
227 | { |
228 | gtk_file_chooser_remove_filter (chooser: get_delegate (receiver: chooser), filter); |
229 | } |
230 | |
231 | static GListModel * |
232 | delegate_get_filters (GtkFileChooser *chooser) |
233 | { |
234 | return gtk_file_chooser_get_filters (chooser: get_delegate (receiver: chooser)); |
235 | } |
236 | |
237 | static gboolean |
238 | delegate_add_shortcut_folder (GtkFileChooser *chooser, |
239 | GFile *file, |
240 | GError **error) |
241 | { |
242 | return gtk_file_chooser_add_shortcut_folder (chooser: get_delegate (receiver: chooser), folder: file, error); |
243 | } |
244 | |
245 | static gboolean |
246 | delegate_remove_shortcut_folder (GtkFileChooser *chooser, |
247 | GFile *file, |
248 | GError **error) |
249 | { |
250 | return gtk_file_chooser_remove_shortcut_folder (chooser: get_delegate (receiver: chooser), folder: file, error); |
251 | } |
252 | |
253 | static GListModel * |
254 | delegate_get_shortcut_folders (GtkFileChooser *chooser) |
255 | { |
256 | return gtk_file_chooser_get_shortcut_folders (chooser: get_delegate (receiver: chooser)); |
257 | } |
258 | |
259 | static gboolean |
260 | delegate_set_current_folder (GtkFileChooser *chooser, |
261 | GFile *file, |
262 | GError **error) |
263 | { |
264 | return gtk_file_chooser_set_current_folder (chooser: get_delegate (receiver: chooser), file, error); |
265 | } |
266 | |
267 | static GFile * |
268 | delegate_get_current_folder (GtkFileChooser *chooser) |
269 | { |
270 | return gtk_file_chooser_get_current_folder (chooser: get_delegate (receiver: chooser)); |
271 | } |
272 | |
273 | static void |
274 | delegate_set_current_name (GtkFileChooser *chooser, |
275 | const char *name) |
276 | { |
277 | gtk_file_chooser_set_current_name (chooser: get_delegate (receiver: chooser), name); |
278 | } |
279 | |
280 | static char * |
281 | delegate_get_current_name (GtkFileChooser *chooser) |
282 | { |
283 | return gtk_file_chooser_get_current_name (chooser: get_delegate (receiver: chooser)); |
284 | } |
285 | |
286 | static void |
287 | delegate_notify (GObject *object, |
288 | GParamSpec *pspec, |
289 | gpointer data) |
290 | { |
291 | gpointer iface; |
292 | |
293 | iface = g_type_interface_peek (instance_class: g_type_class_peek (G_OBJECT_TYPE (object)), |
294 | iface_type: gtk_file_chooser_get_type ()); |
295 | if (g_object_interface_find_property (g_iface: iface, property_name: pspec->name)) |
296 | g_object_notify (object: data, property_name: pspec->name); |
297 | } |
298 | |
299 | GSettings * |
300 | _gtk_file_chooser_get_settings_for_widget (GtkWidget *widget) |
301 | { |
302 | static GQuark file_chooser_settings_quark = 0; |
303 | GtkSettings *gtksettings; |
304 | GSettings *settings; |
305 | |
306 | if (G_UNLIKELY (file_chooser_settings_quark == 0)) |
307 | file_chooser_settings_quark = g_quark_from_static_string (string: "-gtk-file-chooser-settings" ); |
308 | |
309 | gtksettings = gtk_widget_get_settings (widget); |
310 | settings = g_object_get_qdata (G_OBJECT (gtksettings), quark: file_chooser_settings_quark); |
311 | |
312 | if (G_UNLIKELY (settings == NULL)) |
313 | { |
314 | settings = g_settings_new (schema_id: "org.gtk.gtk4.Settings.FileChooser" ); |
315 | g_settings_delay (settings); |
316 | |
317 | g_object_set_qdata_full (G_OBJECT (gtksettings), |
318 | quark: file_chooser_settings_quark, |
319 | data: settings, |
320 | destroy: g_object_unref); |
321 | } |
322 | |
323 | return settings; |
324 | } |
325 | |
326 | char * |
327 | _gtk_file_chooser_label_for_file (GFile *file) |
328 | { |
329 | const char *path, *start, *end, *p; |
330 | char *uri, *host, *label; |
331 | |
332 | uri = g_file_get_uri (file); |
333 | |
334 | start = strstr (haystack: uri, needle: "://" ); |
335 | if (start) |
336 | { |
337 | start += 3; |
338 | path = strchr (s: start, c: '/'); |
339 | if (path) |
340 | end = path; |
341 | else |
342 | { |
343 | end = uri + strlen (s: uri); |
344 | path = "/" ; |
345 | } |
346 | |
347 | /* strip username */ |
348 | p = strchr (s: start, c: '@'); |
349 | if (p && p < end) |
350 | start = p + 1; |
351 | |
352 | p = strchr (s: start, c: ':'); |
353 | if (p && p < end) |
354 | end = p; |
355 | |
356 | host = g_strndup (str: start, n: end - start); |
357 | /* Translators: the first string is a path and the second string |
358 | * is a hostname. Nautilus and the panel contain the same string |
359 | * to translate. |
360 | */ |
361 | label = g_strdup_printf (_("%1$s on %2$s" ), path, host); |
362 | |
363 | g_free (mem: host); |
364 | } |
365 | else |
366 | { |
367 | label = g_strdup (str: uri); |
368 | } |
369 | |
370 | g_free (mem: uri); |
371 | |
372 | return label; |
373 | } |
374 | |
375 | static void |
376 | delegate_add_choice (GtkFileChooser *chooser, |
377 | const char *id, |
378 | const char *label, |
379 | const char **options, |
380 | const char **option_labels) |
381 | { |
382 | gtk_file_chooser_add_choice (chooser: get_delegate (receiver: chooser), |
383 | id, label, options, option_labels); |
384 | } |
385 | static void |
386 | delegate_remove_choice (GtkFileChooser *chooser, |
387 | const char *id) |
388 | { |
389 | gtk_file_chooser_remove_choice (chooser: get_delegate (receiver: chooser), id); |
390 | } |
391 | |
392 | static void |
393 | delegate_set_choice (GtkFileChooser *chooser, |
394 | const char *id, |
395 | const char *option) |
396 | { |
397 | gtk_file_chooser_set_choice (chooser: get_delegate (receiver: chooser), id, option); |
398 | } |
399 | |
400 | |
401 | static const char * |
402 | delegate_get_choice (GtkFileChooser *chooser, |
403 | const char *id) |
404 | { |
405 | return gtk_file_chooser_get_choice (chooser: get_delegate (receiver: chooser), id); |
406 | } |
407 | |
408 | gboolean |
409 | _gtk_file_info_consider_as_directory (GFileInfo *info) |
410 | { |
411 | GFileType type = g_file_info_get_file_type (info); |
412 | |
413 | return (type == G_FILE_TYPE_DIRECTORY || |
414 | type == G_FILE_TYPE_MOUNTABLE || |
415 | type == G_FILE_TYPE_SHORTCUT); |
416 | } |
417 | |
418 | gboolean |
419 | _gtk_file_has_native_path (GFile *file) |
420 | { |
421 | char *local_file_path; |
422 | gboolean has_native_path; |
423 | |
424 | /* Don't use g_file_is_native(), as we want to support FUSE paths if available */ |
425 | local_file_path = g_file_get_path (file); |
426 | has_native_path = (local_file_path != NULL); |
427 | g_free (mem: local_file_path); |
428 | |
429 | return has_native_path; |
430 | } |
431 | |
432 | gboolean |
433 | _gtk_file_consider_as_remote (GFile *file) |
434 | { |
435 | GFileInfo *info; |
436 | gboolean is_remote; |
437 | |
438 | info = g_file_query_filesystem_info (file, G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, NULL, NULL); |
439 | if (info) |
440 | { |
441 | is_remote = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE); |
442 | |
443 | g_object_unref (object: info); |
444 | } |
445 | else |
446 | is_remote = FALSE; |
447 | |
448 | return is_remote; |
449 | } |
450 | |
451 | GIcon * |
452 | _gtk_file_info_get_icon (GFileInfo *info, |
453 | int icon_size, |
454 | int scale, |
455 | GtkIconTheme *icon_theme) |
456 | { |
457 | GIcon *icon; |
458 | GdkPixbuf *pixbuf; |
459 | const char *thumbnail_path; |
460 | |
461 | thumbnail_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); |
462 | |
463 | if (thumbnail_path) |
464 | { |
465 | pixbuf = gdk_pixbuf_new_from_file_at_size (filename: thumbnail_path, |
466 | width: icon_size*scale, height: icon_size*scale, |
467 | NULL); |
468 | |
469 | if (pixbuf != NULL) |
470 | return G_ICON (pixbuf); |
471 | } |
472 | |
473 | icon = g_file_info_get_icon (info); |
474 | if (icon && gtk_icon_theme_has_gicon (self: icon_theme, gicon: icon)) |
475 | return g_object_ref (icon); |
476 | |
477 | /* Use general fallback for all files without icon */ |
478 | icon = g_themed_icon_new (iconname: "text-x-generic" ); |
479 | return icon; |
480 | } |
481 | |