1 /* GTK - The GIMP Toolkit
2 * gtkfilechooserentry.c: Entry with filename completion
3 * Copyright (C) 2003, Red Hat, Inc.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
19 */
20
21 #include "config.h"
22
23 #include "gtkfilechooserentry.h"
24
25 #include <string.h>
26
27 #include "gtkalignment.h"
28 #include "gtkcelllayout.h"
29 #include "gtkcellrenderertext.h"
30 #include "gtkentryprivate.h"
31 #include "gtkfilesystemmodel.h"
32 #include "gtklabel.h"
33 #include "gtkmain.h"
34 #include "gtkwindow.h"
35 #include "gtkintl.h"
36 #include "gtkalias.h"
37
38 #include "gdkkeysyms.h"
39
40 typedef struct _GtkFileChooserEntryClass GtkFileChooserEntryClass;
41
42 #define GTK_FILE_CHOOSER_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_CHOOSER_ENTRY, GtkFileChooserEntryClass))
43 #define GTK_IS_FILE_CHOOSER_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_CHOOSER_ENTRY))
44 #define GTK_FILE_CHOOSER_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_CHOOSER_ENTRY, GtkFileChooserEntryClass))
45
46 struct _GtkFileChooserEntryClass
47 {
48 GtkEntryClass parent_class;
49 };
50
51 struct _GtkFileChooserEntry
52 {
53 GtkEntry parent_instance;
54
55 GtkFileChooserAction action;
56
57 GFile *base_folder;
58 GFile *current_folder_file;
59 gchar *dir_part;
60 gchar *file_part;
61
62 GtkTreeModel *completion_store;
63
64 guint current_folder_loaded : 1;
65 guint complete_on_load : 1;
66 guint eat_tabs : 1;
67 guint local_only : 1;
68 };
69
70 enum
71 {
72 DISPLAY_NAME_COLUMN,
73 FULL_PATH_COLUMN,
74 N_COLUMNS
75 };
76
77 static void gtk_file_chooser_entry_finalize (GObject *object);
78 static void gtk_file_chooser_entry_dispose (GObject *object);
79 static void gtk_file_chooser_entry_grab_focus (GtkWidget *widget);
80 static gboolean gtk_file_chooser_entry_tab_handler (GtkWidget *widget,
81 GdkEventKey *event);
82 static gboolean gtk_file_chooser_entry_focus_out_event (GtkWidget *widget,
83 GdkEventFocus *event);
84
85 #ifdef G_OS_WIN32
86 static gint insert_text_callback (GtkFileChooserEntry *widget,
87 const gchar *new_text,
88 gint new_text_length,
89 gint *position,
90 gpointer user_data);
91 static void delete_text_callback (GtkFileChooserEntry *widget,
92 gint start_pos,
93 gint end_pos,
94 gpointer user_data);
95 #endif
96
97 static gboolean match_selected_callback (GtkEntryCompletion *completion,
98 GtkTreeModel *model,
99 GtkTreeIter *iter,
100 GtkFileChooserEntry *chooser_entry);
101
102 static void set_complete_on_load (GtkFileChooserEntry *chooser_entry,
103 gboolean complete_on_load);
104 static void refresh_current_folder_and_file_part (GtkFileChooserEntry *chooser_entry);
105 static void set_completion_folder (GtkFileChooserEntry *chooser_entry,
106 GFile *folder,
107 char *dir_part);
108 static void finished_loading_cb (GtkFileSystemModel *model,
109 GError *error,
110 GtkFileChooserEntry *chooser_entry);
111
G_DEFINE_TYPE(GtkFileChooserEntry,_gtk_file_chooser_entry,GTK_TYPE_ENTRY)112 G_DEFINE_TYPE (GtkFileChooserEntry, _gtk_file_chooser_entry, GTK_TYPE_ENTRY)
113
114 static char *
115 gtk_file_chooser_entry_get_completion_text (GtkFileChooserEntry *chooser_entry)
116 {
117 GtkEditable *editable = GTK_EDITABLE (chooser_entry);
118 int start, end;
119
120 gtk_editable_get_selection_bounds (editable, &start, &end);
121 return gtk_editable_get_chars (editable, 0, MIN (start, end));
122 }
123
124 static void
gtk_file_chooser_entry_dispatch_properties_changed(GObject * object,guint n_pspecs,GParamSpec ** pspecs)125 gtk_file_chooser_entry_dispatch_properties_changed (GObject *object,
126 guint n_pspecs,
127 GParamSpec **pspecs)
128 {
129 GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object);
130 guint i;
131
132 G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->dispatch_properties_changed (object, n_pspecs, pspecs);
133
134 /* Don't do this during or after disposal */
135 if (gtk_widget_get_parent (GTK_WIDGET (object)) != NULL)
136 {
137 /* What we are after: The text in front of the cursor was modified.
138 * Unfortunately, there's no other way to catch this.
139 */
140 for (i = 0; i < n_pspecs; i++)
141 {
142 if (pspecs[i]->name == I_("cursor-position") ||
143 pspecs[i]->name == I_("selection-bound") ||
144 pspecs[i]->name == I_("text"))
145 {
146 set_complete_on_load (chooser_entry, FALSE);
147 refresh_current_folder_and_file_part (chooser_entry);
148 break;
149 }
150 }
151 }
152 }
153
154 static void
_gtk_file_chooser_entry_class_init(GtkFileChooserEntryClass * class)155 _gtk_file_chooser_entry_class_init (GtkFileChooserEntryClass *class)
156 {
157 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
158 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
159
160 gobject_class->finalize = gtk_file_chooser_entry_finalize;
161 gobject_class->dispose = gtk_file_chooser_entry_dispose;
162 gobject_class->dispatch_properties_changed = gtk_file_chooser_entry_dispatch_properties_changed;
163
164 widget_class->grab_focus = gtk_file_chooser_entry_grab_focus;
165 widget_class->focus_out_event = gtk_file_chooser_entry_focus_out_event;
166 }
167
168 static void
_gtk_file_chooser_entry_init(GtkFileChooserEntry * chooser_entry)169 _gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry)
170 {
171 GtkEntryCompletion *comp;
172 GtkCellRenderer *cell;
173
174 chooser_entry->local_only = TRUE;
175
176 g_object_set (chooser_entry, "truncate-multiline", TRUE, NULL);
177
178 comp = gtk_entry_completion_new ();
179 gtk_entry_completion_set_popup_single_match (comp, FALSE);
180 gtk_entry_completion_set_minimum_key_length (comp, 0);
181 /* see docs for gtk_entry_completion_set_text_column() */
182 g_object_set (comp, "text-column", FULL_PATH_COLUMN, NULL);
183
184 /* Need a match func here or entry completion uses a wrong one.
185 * We do our own filtering after all. */
186 gtk_entry_completion_set_match_func (comp,
187 (GtkEntryCompletionMatchFunc) gtk_true,
188 chooser_entry,
189 NULL);
190
191 cell = gtk_cell_renderer_text_new ();
192 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (comp),
193 cell, TRUE);
194 gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (comp),
195 cell,
196 "text", DISPLAY_NAME_COLUMN);
197
198 g_signal_connect (comp, "match-selected",
199 G_CALLBACK (match_selected_callback), chooser_entry);
200
201 gtk_entry_set_completion (GTK_ENTRY (chooser_entry), comp);
202 g_object_unref (comp);
203 /* NB: This needs to happen after the completion is set, so this handler
204 * runs before the handler installed by entrycompletion */
205 g_signal_connect (chooser_entry, "key-press-event",
206 G_CALLBACK (gtk_file_chooser_entry_tab_handler), NULL);
207
208 #ifdef G_OS_WIN32
209 g_signal_connect (chooser_entry, "insert-text",
210 G_CALLBACK (insert_text_callback), NULL);
211 g_signal_connect (chooser_entry, "delete-text",
212 G_CALLBACK (delete_text_callback), NULL);
213 #endif
214 }
215
216 static void
gtk_file_chooser_entry_finalize(GObject * object)217 gtk_file_chooser_entry_finalize (GObject *object)
218 {
219 GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object);
220
221 if (chooser_entry->base_folder)
222 g_object_unref (chooser_entry->base_folder);
223
224 if (chooser_entry->current_folder_file)
225 g_object_unref (chooser_entry->current_folder_file);
226
227 g_free (chooser_entry->dir_part);
228 g_free (chooser_entry->file_part);
229
230 G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->finalize (object);
231 }
232
233 static void
gtk_file_chooser_entry_dispose(GObject * object)234 gtk_file_chooser_entry_dispose (GObject *object)
235 {
236 GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object);
237
238 set_completion_folder (chooser_entry, NULL, NULL);
239
240 G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->dispose (object);
241 }
242
243 /* Match functions for the GtkEntryCompletion */
244 static gboolean
match_selected_callback(GtkEntryCompletion * completion,GtkTreeModel * model,GtkTreeIter * iter,GtkFileChooserEntry * chooser_entry)245 match_selected_callback (GtkEntryCompletion *completion,
246 GtkTreeModel *model,
247 GtkTreeIter *iter,
248 GtkFileChooserEntry *chooser_entry)
249 {
250 char *path;
251 gint pos;
252
253 gtk_tree_model_get (model, iter,
254 FULL_PATH_COLUMN, &path,
255 -1);
256
257 gtk_editable_delete_text (GTK_EDITABLE (chooser_entry),
258 0,
259 gtk_editable_get_position (GTK_EDITABLE (chooser_entry)));
260 pos = 0;
261 gtk_editable_insert_text (GTK_EDITABLE (chooser_entry),
262 path,
263 -1,
264 &pos);
265
266 gtk_editable_set_position (GTK_EDITABLE (chooser_entry), pos);
267
268 g_free (path);
269
270 return TRUE;
271 }
272
273 static void
set_complete_on_load(GtkFileChooserEntry * chooser_entry,gboolean complete_on_load)274 set_complete_on_load (GtkFileChooserEntry *chooser_entry,
275 gboolean complete_on_load)
276 {
277 /* a completion was triggered, but we couldn't do it.
278 * So no text was inserted when pressing tab, so we beep
279 */
280 if (chooser_entry->complete_on_load && !complete_on_load)
281 gtk_widget_error_bell (GTK_WIDGET (chooser_entry));
282
283 chooser_entry->complete_on_load = complete_on_load;
284 }
285
286 static gboolean
is_valid_scheme_character(char c)287 is_valid_scheme_character (char c)
288 {
289 return g_ascii_isalnum (c) || c == '+' || c == '-' || c == '.';
290 }
291
292 static gboolean
has_uri_scheme(const char * str)293 has_uri_scheme (const char *str)
294 {
295 const char *p;
296
297 p = str;
298
299 if (!is_valid_scheme_character (*p))
300 return FALSE;
301
302 do
303 p++;
304 while (is_valid_scheme_character (*p));
305
306 return (strncmp (p, "://", 3) == 0);
307 }
308
309 static GFile *
gtk_file_chooser_get_file_for_text(GtkFileChooserEntry * chooser_entry,const gchar * str)310 gtk_file_chooser_get_file_for_text (GtkFileChooserEntry *chooser_entry,
311 const gchar *str)
312 {
313 GFile *file;
314
315 if (str[0] == '~' || g_path_is_absolute (str) || has_uri_scheme (str))
316 file = g_file_parse_name (str);
317 else if (chooser_entry->base_folder != NULL)
318 file = g_file_resolve_relative_path (chooser_entry->base_folder, str);
319 else
320 file = NULL;
321
322 return file;
323 }
324
325 static gboolean
is_directory_shortcut(const char * text)326 is_directory_shortcut (const char *text)
327 {
328 return strcmp (text, ".") == 0 ||
329 strcmp (text, "..") == 0 ||
330 strcmp (text, "~" ) == 0;
331 }
332
333 static GFile *
gtk_file_chooser_get_directory_for_text(GtkFileChooserEntry * chooser_entry,const char * text)334 gtk_file_chooser_get_directory_for_text (GtkFileChooserEntry *chooser_entry,
335 const char * text)
336 {
337 GFile *file, *parent;
338
339 file = gtk_file_chooser_get_file_for_text (chooser_entry, text);
340
341 if (file == NULL)
342 return NULL;
343
344 if (text[0] == 0 || text[strlen (text) - 1] == G_DIR_SEPARATOR ||
345 is_directory_shortcut (text))
346 return file;
347
348 parent = g_file_get_parent (file);
349 g_object_unref (file);
350
351 return parent;
352 }
353
354 /* Finds a common prefix based on the contents of the entry
355 * and mandatorily appends it
356 */
357 static void
explicitly_complete(GtkFileChooserEntry * chooser_entry)358 explicitly_complete (GtkFileChooserEntry *chooser_entry)
359 {
360 chooser_entry->complete_on_load = FALSE;
361
362 if (chooser_entry->completion_store)
363 {
364 char *completion, *text;
365 gsize completion_len, text_len;
366
367 text = gtk_file_chooser_entry_get_completion_text (chooser_entry);
368 text_len = strlen (text);
369 completion = _gtk_entry_completion_compute_prefix (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), text);
370 completion_len = completion ? strlen (completion) : 0;
371
372 if (completion_len > text_len)
373 {
374 GtkEditable *editable = GTK_EDITABLE (chooser_entry);
375 int pos = gtk_editable_get_position (editable);
376
377 gtk_editable_insert_text (editable,
378 completion + text_len,
379 completion_len - text_len,
380 &pos);
381 gtk_editable_set_position (editable, pos);
382 return;
383 }
384 }
385
386 gtk_widget_error_bell (GTK_WIDGET (chooser_entry));
387 }
388
389 static void
gtk_file_chooser_entry_grab_focus(GtkWidget * widget)390 gtk_file_chooser_entry_grab_focus (GtkWidget *widget)
391 {
392 GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->grab_focus (widget);
393 _gtk_file_chooser_entry_select_filename (GTK_FILE_CHOOSER_ENTRY (widget));
394 }
395
396 static void
start_explicit_completion(GtkFileChooserEntry * chooser_entry)397 start_explicit_completion (GtkFileChooserEntry *chooser_entry)
398 {
399 if (chooser_entry->current_folder_loaded)
400 explicitly_complete (chooser_entry);
401 else
402 set_complete_on_load (chooser_entry, TRUE);
403 }
404
405 static gboolean
gtk_file_chooser_entry_tab_handler(GtkWidget * widget,GdkEventKey * event)406 gtk_file_chooser_entry_tab_handler (GtkWidget *widget,
407 GdkEventKey *event)
408 {
409 GtkFileChooserEntry *chooser_entry;
410 GtkEditable *editable;
411 GdkModifierType state;
412 gint start, end;
413
414 chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget);
415 editable = GTK_EDITABLE (widget);
416
417 if (!chooser_entry->eat_tabs)
418 return FALSE;
419
420 if (event->keyval != GDK_KEY_Tab)
421 return FALSE;
422
423 if (gtk_get_current_event_state (&state) &&
424 (state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
425 return FALSE;
426
427 /* This is a bit evil -- it makes Tab never leave the entry. It basically
428 * makes it 'safe' for people to hit. */
429 gtk_editable_get_selection_bounds (editable, &start, &end);
430
431 if (start != end)
432 gtk_editable_set_position (editable, MAX (start, end));
433 else
434 start_explicit_completion (chooser_entry);
435
436 return TRUE;
437 }
438
439 static gboolean
gtk_file_chooser_entry_focus_out_event(GtkWidget * widget,GdkEventFocus * event)440 gtk_file_chooser_entry_focus_out_event (GtkWidget *widget,
441 GdkEventFocus *event)
442 {
443 GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget);
444
445 set_complete_on_load (chooser_entry, FALSE);
446
447 return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->focus_out_event (widget, event);
448 }
449
450 static void
update_inline_completion(GtkFileChooserEntry * chooser_entry)451 update_inline_completion (GtkFileChooserEntry *chooser_entry)
452 {
453 GtkEntryCompletion *completion = gtk_entry_get_completion (GTK_ENTRY (chooser_entry));
454
455 if (!chooser_entry->current_folder_loaded)
456 {
457 gtk_entry_completion_set_inline_completion (completion, FALSE);
458 return;
459 }
460
461 switch (chooser_entry->action)
462 {
463 case GTK_FILE_CHOOSER_ACTION_OPEN:
464 case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
465 gtk_entry_completion_set_inline_completion (completion, TRUE);
466 break;
467 case GTK_FILE_CHOOSER_ACTION_SAVE:
468 case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
469 gtk_entry_completion_set_inline_completion (completion, FALSE);
470 break;
471 }
472 }
473
474 static void
discard_completion_store(GtkFileChooserEntry * chooser_entry)475 discard_completion_store (GtkFileChooserEntry *chooser_entry)
476 {
477 if (!chooser_entry->completion_store)
478 return;
479
480 gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), NULL);
481 update_inline_completion (chooser_entry);
482 g_object_unref (chooser_entry->completion_store);
483 chooser_entry->completion_store = NULL;
484 }
485
486 static gboolean
completion_store_set(GtkFileSystemModel * model,GFile * file,GFileInfo * info,int column,GValue * value,gpointer data)487 completion_store_set (GtkFileSystemModel *model,
488 GFile *file,
489 GFileInfo *info,
490 int column,
491 GValue *value,
492 gpointer data)
493 {
494 GtkFileChooserEntry *chooser_entry = data;
495
496 const char *prefix = "";
497 const char *suffix = "";
498
499 switch (column)
500 {
501 case FULL_PATH_COLUMN:
502 prefix = chooser_entry->dir_part;
503 /* fall through */
504 case DISPLAY_NAME_COLUMN:
505 if (_gtk_file_info_consider_as_directory (info))
506 suffix = G_DIR_SEPARATOR_S;
507
508 g_value_take_string (value,
509 g_strconcat (prefix,
510 g_file_info_get_display_name (info),
511 suffix,
512 NULL));
513 break;
514 default:
515 g_assert_not_reached ();
516 break;
517 }
518
519 return TRUE;
520 }
521
522 /* Fills the completion store from the contents of the current folder */
523 static void
populate_completion_store(GtkFileChooserEntry * chooser_entry)524 populate_completion_store (GtkFileChooserEntry *chooser_entry)
525 {
526 chooser_entry->completion_store = GTK_TREE_MODEL (
527 _gtk_file_system_model_new_for_directory (chooser_entry->current_folder_file,
528 "standard::name,standard::display-name,standard::type",
529 completion_store_set,
530 chooser_entry,
531 N_COLUMNS,
532 G_TYPE_STRING,
533 G_TYPE_STRING));
534 g_signal_connect (chooser_entry->completion_store, "finished-loading",
535 G_CALLBACK (finished_loading_cb), chooser_entry);
536
537 _gtk_file_system_model_set_filter_folders (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store),
538 TRUE);
539 _gtk_file_system_model_set_show_files (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store),
540 chooser_entry->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
541 chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SAVE);
542 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (chooser_entry->completion_store),
543 DISPLAY_NAME_COLUMN, GTK_SORT_ASCENDING);
544
545 gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)),
546 chooser_entry->completion_store);
547 }
548
549 /* Callback when the current folder finishes loading */
550 static void
finished_loading_cb(GtkFileSystemModel * model,GError * error,GtkFileChooserEntry * chooser_entry)551 finished_loading_cb (GtkFileSystemModel *model,
552 GError *error,
553 GtkFileChooserEntry *chooser_entry)
554 {
555 GtkEntryCompletion *completion;
556
557 chooser_entry->current_folder_loaded = TRUE;
558
559 if (error)
560 {
561 discard_completion_store (chooser_entry);
562 set_complete_on_load (chooser_entry, FALSE);
563 return;
564 }
565
566 if (chooser_entry->complete_on_load)
567 explicitly_complete (chooser_entry);
568
569 gtk_widget_set_tooltip_text (GTK_WIDGET (chooser_entry), NULL);
570
571 completion = gtk_entry_get_completion (GTK_ENTRY (chooser_entry));
572 update_inline_completion (chooser_entry);
573
574 if (gtk_widget_has_focus (GTK_WIDGET (chooser_entry)))
575 {
576 gtk_entry_completion_complete (completion);
577 gtk_entry_completion_insert_prefix (completion);
578 }
579 }
580
581 static void
set_completion_folder(GtkFileChooserEntry * chooser_entry,GFile * folder_file,char * dir_part)582 set_completion_folder (GtkFileChooserEntry *chooser_entry,
583 GFile *folder_file,
584 char *dir_part)
585 {
586 if (folder_file &&
587 chooser_entry->local_only
588 && !_gtk_file_has_native_path (folder_file))
589 folder_file = NULL;
590
591 if (((chooser_entry->current_folder_file
592 && folder_file
593 && g_file_equal (folder_file, chooser_entry->current_folder_file))
594 || chooser_entry->current_folder_file == folder_file)
595 && g_strcmp0 (dir_part, chooser_entry->dir_part) == 0)
596 {
597 return;
598 }
599
600 if (chooser_entry->current_folder_file)
601 {
602 g_object_unref (chooser_entry->current_folder_file);
603 chooser_entry->current_folder_file = NULL;
604 }
605
606 g_free (chooser_entry->dir_part);
607 chooser_entry->dir_part = g_strdup (dir_part);
608
609 chooser_entry->current_folder_loaded = FALSE;
610
611 discard_completion_store (chooser_entry);
612
613 if (folder_file)
614 {
615 chooser_entry->current_folder_file = g_object_ref (folder_file);
616 populate_completion_store (chooser_entry);
617 }
618 }
619
620 static void
refresh_current_folder_and_file_part(GtkFileChooserEntry * chooser_entry)621 refresh_current_folder_and_file_part (GtkFileChooserEntry *chooser_entry)
622 {
623 GFile *folder_file;
624 char *text, *last_slash, *old_file_part;
625 char *dir_part;
626
627 old_file_part = chooser_entry->file_part;
628
629 text = gtk_file_chooser_entry_get_completion_text (chooser_entry);
630
631 last_slash = strrchr (text, G_DIR_SEPARATOR);
632 if (last_slash)
633 {
634 dir_part = g_strndup (text, last_slash - text + 1);
635 chooser_entry->file_part = g_strdup (last_slash + 1);
636 }
637 else
638 {
639 dir_part = g_strdup ("");
640 chooser_entry->file_part = g_strdup (text);
641 }
642
643 folder_file = gtk_file_chooser_get_directory_for_text (chooser_entry, text);
644
645 set_completion_folder (chooser_entry, folder_file, dir_part);
646
647 if (folder_file)
648 g_object_unref (folder_file);
649
650 g_free (dir_part);
651
652 if (chooser_entry->completion_store &&
653 (g_strcmp0 (old_file_part, chooser_entry->file_part) != 0))
654 {
655 GtkFileFilter *filter;
656 char *pattern;
657
658 filter = gtk_file_filter_new ();
659 pattern = g_strconcat (chooser_entry->file_part, "*", NULL);
660 gtk_file_filter_add_pattern (filter, pattern);
661
662 g_object_ref_sink (filter);
663
664 _gtk_file_system_model_set_filter (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store),
665 filter);
666
667 g_free (pattern);
668 g_object_unref (filter);
669 }
670
671 g_free (text);
672 g_free (old_file_part);
673 }
674
675 #ifdef G_OS_WIN32
676 static gint
insert_text_callback(GtkFileChooserEntry * chooser_entry,const gchar * new_text,gint new_text_length,gint * position,gpointer user_data)677 insert_text_callback (GtkFileChooserEntry *chooser_entry,
678 const gchar *new_text,
679 gint new_text_length,
680 gint *position,
681 gpointer user_data)
682 {
683 const gchar *colon = memchr (new_text, ':', new_text_length);
684 gint i;
685
686 /* Disallow these characters altogether */
687 for (i = 0; i < new_text_length; i++)
688 {
689 if (new_text[i] == '<' ||
690 new_text[i] == '>' ||
691 new_text[i] == '"' ||
692 new_text[i] == '|' ||
693 new_text[i] == '*' ||
694 new_text[i] == '?')
695 break;
696 }
697
698 if (i < new_text_length ||
699 /* Disallow entering text that would cause a colon to be anywhere except
700 * after a drive letter.
701 */
702 (colon != NULL &&
703 *position + (colon - new_text) != 1) ||
704 (new_text_length > 0 &&
705 *position <= 1 &&
706 gtk_entry_get_text_length (GTK_ENTRY (chooser_entry)) >= 2 &&
707 gtk_entry_get_text (GTK_ENTRY (chooser_entry))[1] == ':'))
708 {
709 gtk_widget_error_bell (GTK_WIDGET (chooser_entry));
710 g_signal_stop_emission_by_name (chooser_entry, "insert_text");
711 return FALSE;
712 }
713
714 return TRUE;
715 }
716
717 static void
delete_text_callback(GtkFileChooserEntry * chooser_entry,gint start_pos,gint end_pos,gpointer user_data)718 delete_text_callback (GtkFileChooserEntry *chooser_entry,
719 gint start_pos,
720 gint end_pos,
721 gpointer user_data)
722 {
723 /* If deleting a drive letter, delete the colon, too */
724 if (start_pos == 0 && end_pos == 1 &&
725 gtk_entry_get_text_length (GTK_ENTRY (chooser_entry)) >= 2 &&
726 gtk_entry_get_text (GTK_ENTRY (chooser_entry))[1] == ':')
727 {
728 g_signal_handlers_block_by_func (chooser_entry,
729 G_CALLBACK (delete_text_callback),
730 user_data);
731 gtk_editable_delete_text (GTK_EDITABLE (chooser_entry), 0, 1);
732 g_signal_handlers_unblock_by_func (chooser_entry,
733 G_CALLBACK (delete_text_callback),
734 user_data);
735 }
736 }
737 #endif
738
739 /**
740 * _gtk_file_chooser_entry_new:
741 * @eat_tabs: If %FALSE, allow focus navigation with the tab key.
742 *
743 * Creates a new #GtkFileChooserEntry object. #GtkFileChooserEntry
744 * is an internal implementation widget for the GTK+ file chooser
745 * which is an entry with completion with respect to a
746 * #GtkFileSystem object.
747 *
748 * Return value: the newly created #GtkFileChooserEntry
749 **/
750 GtkWidget *
_gtk_file_chooser_entry_new(gboolean eat_tabs)751 _gtk_file_chooser_entry_new (gboolean eat_tabs)
752 {
753 GtkFileChooserEntry *chooser_entry;
754
755 chooser_entry = g_object_new (GTK_TYPE_FILE_CHOOSER_ENTRY, NULL);
756 chooser_entry->eat_tabs = (eat_tabs != FALSE);
757
758 return GTK_WIDGET (chooser_entry);
759 }
760
761 /**
762 * _gtk_file_chooser_entry_set_base_folder:
763 * @chooser_entry: a #GtkFileChooserEntry
764 * @file: file for a folder in the chooser entries current file system.
765 *
766 * Sets the folder with respect to which completions occur.
767 **/
768 void
_gtk_file_chooser_entry_set_base_folder(GtkFileChooserEntry * chooser_entry,GFile * file)769 _gtk_file_chooser_entry_set_base_folder (GtkFileChooserEntry *chooser_entry,
770 GFile *file)
771 {
772 g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
773 g_return_if_fail (file == NULL || G_IS_FILE (file));
774
775 if (chooser_entry->base_folder == file ||
776 (file != NULL && chooser_entry->base_folder != NULL
777 && g_file_equal (chooser_entry->base_folder, file)))
778 return;
779
780 if (file)
781 g_object_ref (file);
782
783 if (chooser_entry->base_folder)
784 g_object_unref (chooser_entry->base_folder);
785
786 chooser_entry->base_folder = file;
787
788 refresh_current_folder_and_file_part (chooser_entry);
789 }
790
791 /**
792 * _gtk_file_chooser_entry_get_current_folder:
793 * @chooser_entry: a #GtkFileChooserEntry
794 *
795 * Gets the current folder for the #GtkFileChooserEntry. If the
796 * user has only entered a filename, this will be in the base folder
797 * (see _gtk_file_chooser_entry_set_base_folder()), but if the
798 * user has entered a relative or absolute path, then it will
799 * be different. If the user has entered unparsable text, or text which
800 * the entry cannot handle, this will return %NULL.
801 *
802 * Return value: the file for the current folder - you must g_object_unref()
803 * the value after use.
804 **/
805 GFile *
_gtk_file_chooser_entry_get_current_folder(GtkFileChooserEntry * chooser_entry)806 _gtk_file_chooser_entry_get_current_folder (GtkFileChooserEntry *chooser_entry)
807 {
808 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry), NULL);
809
810 return gtk_file_chooser_get_directory_for_text (chooser_entry,
811 gtk_entry_get_text (GTK_ENTRY (chooser_entry)));
812 }
813
814 /**
815 * _gtk_file_chooser_entry_get_file_part:
816 * @chooser_entry: a #GtkFileChooserEntry
817 *
818 * Gets the non-folder portion of whatever the user has entered
819 * into the file selector. What is returned is a UTF-8 string,
820 * and if a filename path is needed, g_file_get_child_for_display_name()
821 * must be used
822 *
823 * Return value: the entered filename - this value is owned by the
824 * chooser entry and must not be modified or freed.
825 **/
826 const gchar *
_gtk_file_chooser_entry_get_file_part(GtkFileChooserEntry * chooser_entry)827 _gtk_file_chooser_entry_get_file_part (GtkFileChooserEntry *chooser_entry)
828 {
829 const char *last_slash, *text;
830
831 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry), NULL);
832
833 text = gtk_entry_get_text (GTK_ENTRY (chooser_entry));
834 last_slash = strrchr (text, G_DIR_SEPARATOR);
835 if (last_slash)
836 return last_slash + 1;
837 else if (is_directory_shortcut (text))
838 return "";
839 else
840 return text;
841 }
842
843 /**
844 * _gtk_file_chooser_entry_set_action:
845 * @chooser_entry: a #GtkFileChooserEntry
846 * @action: the action which is performed by the file selector using this entry
847 *
848 * Sets action which is performed by the file selector using this entry.
849 * The #GtkFileChooserEntry will use different completion strategies for
850 * different actions.
851 **/
852 void
_gtk_file_chooser_entry_set_action(GtkFileChooserEntry * chooser_entry,GtkFileChooserAction action)853 _gtk_file_chooser_entry_set_action (GtkFileChooserEntry *chooser_entry,
854 GtkFileChooserAction action)
855 {
856 g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
857
858 if (chooser_entry->action != action)
859 {
860 GtkEntryCompletion *comp;
861
862 chooser_entry->action = action;
863
864 comp = gtk_entry_get_completion (GTK_ENTRY (chooser_entry));
865
866 /* FIXME: do we need to actually set the following? */
867
868 switch (action)
869 {
870 case GTK_FILE_CHOOSER_ACTION_OPEN:
871 case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
872 gtk_entry_completion_set_popup_single_match (comp, FALSE);
873 break;
874 case GTK_FILE_CHOOSER_ACTION_SAVE:
875 case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
876 gtk_entry_completion_set_popup_single_match (comp, TRUE);
877 break;
878 }
879
880 if (chooser_entry->completion_store)
881 _gtk_file_system_model_set_show_files (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store),
882 action == GTK_FILE_CHOOSER_ACTION_OPEN ||
883 action == GTK_FILE_CHOOSER_ACTION_SAVE);
884
885 update_inline_completion (chooser_entry);
886 }
887 }
888
889
890 /**
891 * _gtk_file_chooser_entry_get_action:
892 * @chooser_entry: a #GtkFileChooserEntry
893 *
894 * Gets the action for this entry.
895 *
896 * Returns: the action
897 **/
898 GtkFileChooserAction
_gtk_file_chooser_entry_get_action(GtkFileChooserEntry * chooser_entry)899 _gtk_file_chooser_entry_get_action (GtkFileChooserEntry *chooser_entry)
900 {
901 g_return_val_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry),
902 GTK_FILE_CHOOSER_ACTION_OPEN);
903
904 return chooser_entry->action;
905 }
906
907 gboolean
_gtk_file_chooser_entry_get_is_folder(GtkFileChooserEntry * chooser_entry,GFile * file)908 _gtk_file_chooser_entry_get_is_folder (GtkFileChooserEntry *chooser_entry,
909 GFile *file)
910 {
911 GtkTreeIter iter;
912 GFileInfo *info;
913
914 if (chooser_entry->completion_store == NULL ||
915 !_gtk_file_system_model_get_iter_for_file (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store),
916 &iter,
917 file))
918 return FALSE;
919
920 info = _gtk_file_system_model_get_info (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store),
921 &iter);
922
923 return _gtk_file_info_consider_as_directory (info);
924 }
925
926
927 /*
928 * _gtk_file_chooser_entry_select_filename:
929 * @chooser_entry: a #GtkFileChooserEntry
930 *
931 * Selects the filename (without the extension) for user edition.
932 */
933 void
_gtk_file_chooser_entry_select_filename(GtkFileChooserEntry * chooser_entry)934 _gtk_file_chooser_entry_select_filename (GtkFileChooserEntry *chooser_entry)
935 {
936 const gchar *str, *ext;
937 glong len = -1;
938
939 if (chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SAVE)
940 {
941 str = gtk_entry_get_text (GTK_ENTRY (chooser_entry));
942 ext = g_strrstr (str, ".");
943
944 if (ext)
945 len = g_utf8_pointer_to_offset (str, ext);
946 }
947
948 gtk_editable_select_region (GTK_EDITABLE (chooser_entry), 0, (gint) len);
949 }
950
951 void
_gtk_file_chooser_entry_set_local_only(GtkFileChooserEntry * chooser_entry,gboolean local_only)952 _gtk_file_chooser_entry_set_local_only (GtkFileChooserEntry *chooser_entry,
953 gboolean local_only)
954 {
955 chooser_entry->local_only = local_only;
956 refresh_current_folder_and_file_part (chooser_entry);
957 }
958
959 gboolean
_gtk_file_chooser_entry_get_local_only(GtkFileChooserEntry * chooser_entry)960 _gtk_file_chooser_entry_get_local_only (GtkFileChooserEntry *chooser_entry)
961 {
962 return chooser_entry->local_only;
963 }
964