1 /*
2 * Nautilus
3 *
4 * Copyright (C) 2000 Eazel, Inc.
5 *
6 * Nautilus is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
10 *
11 * Nautilus 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 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public
17 * License along with this program; see the file COPYING. If not,
18 * see <http://www.gnu.org/licenses/>.
19 *
20 * Author: Maciej Stachowiak <mjs@eazel.com>
21 * Ettore Perazzoli <ettore@gnu.org>
22 * Michael Meeks <michael@nuclecu.unam.mx>
23 * Andy Hertzfeld <andy@eazel.com>
24 *
25 */
26
27 /* nautilus-location-bar.c - Location bar for Nautilus
28 */
29
30 #include <config.h>
31 #include "nautilus-location-entry.h"
32
33 #include "nautilus-application.h"
34 #include "nautilus-window.h"
35 #include <gtk/gtk.h>
36 #include <gdk/gdkkeysyms.h>
37 #include <glib/gi18n.h>
38 #include <gio/gio.h>
39 #include "nautilus-file-utilities.h"
40 #include "nautilus-clipboard.h"
41 #include <eel/eel-stock-dialogs.h>
42 #include <eel/eel-string.h>
43 #include <eel/eel-vfs-extensions.h>
44 #include <stdio.h>
45 #include <string.h>
46
47 #define NAUTILUS_DND_URI_LIST_TYPE "text/uri-list"
48 #define NAUTILUS_DND_TEXT_PLAIN_TYPE "text/plain"
49
50 enum
51 {
52 NAUTILUS_DND_URI_LIST,
53 NAUTILUS_DND_TEXT_PLAIN,
54 NAUTILUS_DND_NTARGETS
55 };
56
57 static const GtkTargetEntry drop_types [] =
58 {
59 { NAUTILUS_DND_URI_LIST_TYPE, 0, NAUTILUS_DND_URI_LIST },
60 { NAUTILUS_DND_TEXT_PLAIN_TYPE, 0, NAUTILUS_DND_TEXT_PLAIN },
61 };
62
63 typedef struct _NautilusLocationEntryPrivate
64 {
65 char *current_directory;
66 GFilenameCompleter *completer;
67
68 guint idle_id;
69 gboolean idle_insert_completion;
70
71 GFile *last_location;
72
73 gboolean has_special_text;
74 gboolean setting_special_text;
75 gchar *special_text;
76 NautilusLocationEntryAction secondary_action;
77
78 GtkEntryCompletion *completion;
79 GtkListStore *completions_store;
80 GtkCellRenderer *completion_cell;
81 } NautilusLocationEntryPrivate;
82
83 enum
84 {
85 CANCEL,
86 LOCATION_CHANGED,
87 LAST_SIGNAL
88 };
89
90 static guint signals[LAST_SIGNAL];
91
92 G_DEFINE_TYPE_WITH_PRIVATE (NautilusLocationEntry, nautilus_location_entry, GTK_TYPE_ENTRY);
93
94 static GFile *
nautilus_location_entry_get_location(NautilusLocationEntry * entry)95 nautilus_location_entry_get_location (NautilusLocationEntry *entry)
96 {
97 char *user_location;
98 GFile *location;
99
100 user_location = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1);
101 location = g_file_parse_name (user_location);
102 g_free (user_location);
103
104 return location;
105 }
106
107 static void
emit_location_changed(NautilusLocationEntry * entry)108 emit_location_changed (NautilusLocationEntry *entry)
109 {
110 GFile *location;
111
112 location = nautilus_location_entry_get_location (entry);
113 g_signal_emit (entry, signals[LOCATION_CHANGED], 0, location);
114 g_object_unref (location);
115 }
116
117 static void
nautilus_location_entry_update_action(NautilusLocationEntry * entry)118 nautilus_location_entry_update_action (NautilusLocationEntry *entry)
119 {
120 NautilusLocationEntryPrivate *priv;
121 const char *current_text;
122 GFile *location;
123
124 priv = nautilus_location_entry_get_instance_private (entry);
125
126 if (priv->last_location == NULL)
127 {
128 nautilus_location_entry_set_secondary_action (entry,
129 NAUTILUS_LOCATION_ENTRY_ACTION_GOTO);
130 return;
131 }
132
133 current_text = gtk_entry_get_text (GTK_ENTRY (entry));
134 location = g_file_parse_name (current_text);
135
136 if (g_file_equal (priv->last_location, location))
137 {
138 nautilus_location_entry_set_secondary_action (entry,
139 NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR);
140 }
141 else
142 {
143 nautilus_location_entry_set_secondary_action (entry,
144 NAUTILUS_LOCATION_ENTRY_ACTION_GOTO);
145 }
146
147 g_object_unref (location);
148 }
149
150 static int
get_editable_number_of_chars(GtkEditable * editable)151 get_editable_number_of_chars (GtkEditable *editable)
152 {
153 char *text;
154 int length;
155
156 text = gtk_editable_get_chars (editable, 0, -1);
157 length = g_utf8_strlen (text, -1);
158 g_free (text);
159 return length;
160 }
161
162 static void
set_position_and_selection_to_end(GtkEditable * editable)163 set_position_and_selection_to_end (GtkEditable *editable)
164 {
165 int end;
166
167 end = get_editable_number_of_chars (editable);
168 gtk_editable_select_region (editable, end, end);
169 gtk_editable_set_position (editable, end);
170 }
171
172 static void
nautilus_location_entry_update_current_uri(NautilusLocationEntry * entry,const char * uri)173 nautilus_location_entry_update_current_uri (NautilusLocationEntry *entry,
174 const char *uri)
175 {
176 NautilusLocationEntryPrivate *priv;
177
178 priv = nautilus_location_entry_get_instance_private (entry);
179
180 g_free (priv->current_directory);
181 priv->current_directory = g_strdup (uri);
182
183 gtk_entry_set_text (GTK_ENTRY (entry), uri);
184 set_position_and_selection_to_end (GTK_EDITABLE (entry));
185 }
186
187 void
nautilus_location_entry_set_location(NautilusLocationEntry * entry,GFile * location)188 nautilus_location_entry_set_location (NautilusLocationEntry *entry,
189 GFile *location)
190 {
191 NautilusLocationEntryPrivate *priv;
192 gchar *uri, *formatted_uri;
193
194 g_assert (location != NULL);
195
196 priv = nautilus_location_entry_get_instance_private (entry);
197
198 /* Note: This is called in reaction to external changes, and
199 * thus should not emit the LOCATION_CHANGED signal. */
200 uri = g_file_get_uri (location);
201 formatted_uri = g_file_get_parse_name (location);
202
203 if (eel_uri_is_search (uri))
204 {
205 nautilus_location_entry_set_special_text (entry, "");
206 }
207 else
208 {
209 nautilus_location_entry_update_current_uri (entry, formatted_uri);
210 }
211
212 /* remember the original location for later comparison */
213 if (!priv->last_location ||
214 !g_file_equal (priv->last_location, location))
215 {
216 g_clear_object (&priv->last_location);
217 priv->last_location = g_object_ref (location);
218 }
219
220 nautilus_location_entry_update_action (entry);
221
222 /* invalidate the completions list */
223 gtk_list_store_clear (priv->completions_store);
224
225 g_free (uri);
226 g_free (formatted_uri);
227 }
228
229 static void
drag_data_received_callback(GtkWidget * widget,GdkDragContext * context,int x,int y,GtkSelectionData * data,guint info,guint32 time,gpointer callback_data)230 drag_data_received_callback (GtkWidget *widget,
231 GdkDragContext *context,
232 int x,
233 int y,
234 GtkSelectionData *data,
235 guint info,
236 guint32 time,
237 gpointer callback_data)
238 {
239 char **names;
240 int name_count;
241 GtkWidget *window;
242 gboolean new_windows_for_extras;
243 char *prompt;
244 char *detail;
245 GFile *location;
246 NautilusLocationEntry *self = NAUTILUS_LOCATION_ENTRY (widget);
247
248 g_assert (data != NULL);
249 g_assert (callback_data == NULL);
250
251 names = g_uri_list_extract_uris ((const gchar *) gtk_selection_data_get_data (data));
252
253 if (names == NULL || *names == NULL)
254 {
255 g_warning ("No D&D URI's");
256 gtk_drag_finish (context, FALSE, FALSE, time);
257 return;
258 }
259
260 window = gtk_widget_get_toplevel (widget);
261 new_windows_for_extras = FALSE;
262 /* Ask user if they really want to open multiple windows
263 * for multiple dropped URIs. This is likely to have been
264 * a mistake.
265 */
266 name_count = g_strv_length (names);
267 if (name_count > 1)
268 {
269 prompt = g_strdup_printf (ngettext ("Do you want to view %d location?",
270 "Do you want to view %d locations?",
271 name_count),
272 name_count);
273 detail = g_strdup_printf (ngettext ("This will open %d separate window.",
274 "This will open %d separate windows.",
275 name_count),
276 name_count);
277 /* eel_run_simple_dialog should really take in pairs
278 * like gtk_dialog_new_with_buttons() does. */
279 new_windows_for_extras = eel_run_simple_dialog (GTK_WIDGET (window),
280 TRUE,
281 GTK_MESSAGE_QUESTION,
282 prompt,
283 detail,
284 _("_Cancel"), _("_OK"),
285 NULL) != 0 /* GNOME_OK */;
286
287 g_free (prompt);
288 g_free (detail);
289
290 if (!new_windows_for_extras)
291 {
292 gtk_drag_finish (context, FALSE, FALSE, time);
293 return;
294 }
295 }
296
297 location = g_file_new_for_uri (names[0]);
298 nautilus_location_entry_set_location (self, location);
299 emit_location_changed (self);
300 g_object_unref (location);
301
302 if (new_windows_for_extras)
303 {
304 int i;
305
306 for (i = 1; names[i] != NULL; ++i)
307 {
308 location = g_file_new_for_uri (names[i]);
309 nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()),
310 location, NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW, NULL, NULL, NULL);
311 g_object_unref (location);
312 }
313 }
314
315 g_strfreev (names);
316
317 gtk_drag_finish (context, TRUE, FALSE, time);
318 }
319
320 static void
drag_data_get_callback(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint32 time,gpointer callback_data)321 drag_data_get_callback (GtkWidget *widget,
322 GdkDragContext *context,
323 GtkSelectionData *selection_data,
324 guint info,
325 guint32 time,
326 gpointer callback_data)
327 {
328 NautilusLocationEntry *self;
329 GFile *location;
330 gchar *uri;
331
332 g_assert (selection_data != NULL);
333 self = callback_data;
334
335 location = nautilus_location_entry_get_location (self);
336 uri = g_file_get_uri (location);
337
338 switch (info)
339 {
340 case NAUTILUS_DND_URI_LIST:
341 case NAUTILUS_DND_TEXT_PLAIN:
342 {
343 gtk_selection_data_set (selection_data,
344 gtk_selection_data_get_target (selection_data),
345 8, (guchar *) uri,
346 strlen (uri));
347 }
348 break;
349
350 default:
351 {
352 g_assert_not_reached ();
353 }
354 }
355 g_free (uri);
356 g_object_unref (location);
357 }
358
359
360 static void
set_prefix_dimming(GtkCellRenderer * completion_cell,char * user_location)361 set_prefix_dimming (GtkCellRenderer *completion_cell,
362 char *user_location)
363 {
364 g_autofree char *location_basename = NULL;
365 PangoAttrList *attrs;
366 PangoAttribute *attr;
367
368 /* Dim the prefixes of the completion rows, leaving the basenames
369 * highlighted. This makes it easier to find what you're looking for.
370 *
371 * Perhaps a better solution would be to *only* show the basenames, but
372 * it would take a reimplementation of GtkEntryCompletion to align the
373 * popover. */
374
375 location_basename = g_path_get_basename (user_location);
376
377 attrs = pango_attr_list_new ();
378
379 /* 55% opacity. This is the same as the dim-label style class in Adwaita. */
380 attr = pango_attr_foreground_alpha_new (36045);
381 attr->end_index = strlen (user_location) - strlen (location_basename);
382 pango_attr_list_insert (attrs, attr);
383
384 g_object_set (completion_cell, "attributes", attrs, NULL);
385 pango_attr_list_unref (attrs);
386 }
387
388
389 /* Update the path completions list based on the current text of the entry. */
390 static gboolean
update_completions_store(gpointer callback_data)391 update_completions_store (gpointer callback_data)
392 {
393 NautilusLocationEntry *entry;
394 NautilusLocationEntryPrivate *priv;
395 GtkEditable *editable;
396 g_autofree char *absolute_location = NULL;
397 g_autofree char *user_location = NULL;
398 gboolean is_relative = FALSE;
399 int start_sel;
400 g_autofree char *uri_scheme = NULL;
401 g_auto (GStrv) completions = NULL;
402 char *completion;
403 int i;
404 GtkTreeIter iter;
405 int current_dir_strlen;
406
407 entry = NAUTILUS_LOCATION_ENTRY (callback_data);
408 priv = nautilus_location_entry_get_instance_private (entry);
409 editable = GTK_EDITABLE (entry);
410
411 if (gtk_editable_get_selection_bounds (editable, &start_sel, NULL))
412 {
413 user_location = gtk_editable_get_chars (editable, 0, start_sel);
414 }
415 else
416 {
417 user_location = gtk_editable_get_chars (editable, 0, -1);
418 }
419
420 g_strstrip (user_location);
421 set_prefix_dimming (priv->completion_cell, user_location);
422
423 priv->idle_id = 0;
424
425 uri_scheme = g_uri_parse_scheme (user_location);
426
427 if (!g_path_is_absolute (user_location) && uri_scheme == NULL && user_location[0] != '~')
428 {
429 is_relative = TRUE;
430 absolute_location = g_build_filename (priv->current_directory, user_location, NULL);
431 }
432 else
433 {
434 absolute_location = g_steal_pointer (&user_location);
435 }
436
437 completions = g_filename_completer_get_completions (priv->completer, absolute_location);
438
439 /* populate the completions model */
440 gtk_list_store_clear (priv->completions_store);
441
442 current_dir_strlen = strlen (priv->current_directory);
443 for (i = 0; completions[i] != NULL; i++)
444 {
445 completion = completions[i];
446
447 if (is_relative && strlen (completion) >= current_dir_strlen)
448 {
449 /* For relative paths, we need to strip the current directory
450 * (and the trailing slash) so the completions will match what's
451 * in the text entry */
452 completion += current_dir_strlen;
453 if (G_IS_DIR_SEPARATOR (completion[0]))
454 {
455 completion++;
456 }
457 }
458
459 gtk_list_store_append (priv->completions_store, &iter);
460 gtk_list_store_set (priv->completions_store, &iter, 0, completion, -1);
461 }
462
463 /* refilter the completions dropdown */
464 gtk_entry_completion_complete (priv->completion);
465
466 if (priv->idle_insert_completion)
467 {
468 /* insert the completion */
469 gtk_entry_completion_insert_prefix (priv->completion);
470 }
471
472 return FALSE;
473 }
474
475 /* Until we have a more elegant solution, this is how we figure out if
476 * the GtkEntry inserted characters, assuming that the return value is
477 * TRUE indicating that the GtkEntry consumed the key event for some
478 * reason. This is a clone of code from GtkEntry.
479 */
480 static gboolean
entry_would_have_inserted_characters(const GdkEvent * event)481 entry_would_have_inserted_characters (const GdkEvent *event)
482 {
483 guint keyval;
484 GdkModifierType state;
485
486 if (G_UNLIKELY (!gdk_event_get_keyval (event, &keyval)))
487 {
488 g_return_val_if_reached (GDK_EVENT_PROPAGATE);
489 }
490 if (G_UNLIKELY (!gdk_event_get_state (event, &state)))
491 {
492 g_return_val_if_reached (GDK_EVENT_PROPAGATE);
493 }
494
495 switch (keyval)
496 {
497 case GDK_KEY_BackSpace:
498 case GDK_KEY_Clear:
499 case GDK_KEY_Insert:
500 case GDK_KEY_Delete:
501 case GDK_KEY_Home:
502 case GDK_KEY_End:
503 case GDK_KEY_KP_Home:
504 case GDK_KEY_KP_End:
505 case GDK_KEY_Left:
506 case GDK_KEY_Right:
507 case GDK_KEY_KP_Left:
508 case GDK_KEY_KP_Right:
509 case GDK_KEY_Return:
510 /* For when the entry is set to be always visible.
511 */
512 case GDK_KEY_Escape:
513 {
514 return FALSE;
515 }
516
517 default:
518 {
519 if (keyval >= 0x20 && keyval <= 0xFF)
520 {
521 if ((state & GDK_CONTROL_MASK) != 0)
522 {
523 return FALSE;
524 }
525 if ((state & GDK_MOD1_MASK) != 0)
526 {
527 return FALSE;
528 }
529 }
530 }
531 }
532
533 /* GTK+ 4 TODO: gdk_event_get_string () and check if length > 0. */
534 return ((const GdkEventKey *) event)->length;
535 }
536
537 static gboolean
position_and_selection_are_at_end(GtkEditable * editable)538 position_and_selection_are_at_end (GtkEditable *editable)
539 {
540 int end;
541 int start_sel, end_sel;
542
543 end = get_editable_number_of_chars (editable);
544 if (gtk_editable_get_selection_bounds (editable, &start_sel, &end_sel))
545 {
546 if (start_sel != end || end_sel != end)
547 {
548 return FALSE;
549 }
550 }
551 return gtk_editable_get_position (editable) == end;
552 }
553
554 static void
got_completion_data_callback(GFilenameCompleter * completer,NautilusLocationEntry * entry)555 got_completion_data_callback (GFilenameCompleter *completer,
556 NautilusLocationEntry *entry)
557 {
558 NautilusLocationEntryPrivate *priv;
559
560 priv = nautilus_location_entry_get_instance_private (entry);
561
562 if (priv->idle_id)
563 {
564 g_source_remove (priv->idle_id);
565 priv->idle_id = 0;
566 }
567 update_completions_store (entry);
568 }
569
570 static void
finalize(GObject * object)571 finalize (GObject *object)
572 {
573 NautilusLocationEntry *entry;
574 NautilusLocationEntryPrivate *priv;
575
576 entry = NAUTILUS_LOCATION_ENTRY (object);
577 priv = nautilus_location_entry_get_instance_private (entry);
578
579 g_object_unref (priv->completer);
580 g_free (priv->special_text);
581
582 g_clear_object (&priv->last_location);
583 g_clear_object (&priv->completion);
584 g_clear_object (&priv->completions_store);
585
586 G_OBJECT_CLASS (nautilus_location_entry_parent_class)->finalize (object);
587 }
588
589 static void
destroy(GtkWidget * object)590 destroy (GtkWidget *object)
591 {
592 NautilusLocationEntry *entry;
593 NautilusLocationEntryPrivate *priv;
594
595 entry = NAUTILUS_LOCATION_ENTRY (object);
596 priv = nautilus_location_entry_get_instance_private (entry);
597
598 /* cancel the pending idle call, if any */
599 if (priv->idle_id != 0)
600 {
601 g_source_remove (priv->idle_id);
602 priv->idle_id = 0;
603 }
604
605 g_free (priv->current_directory);
606 priv->current_directory = NULL;
607
608 GTK_WIDGET_CLASS (nautilus_location_entry_parent_class)->destroy (object);
609 }
610
611 static void
on_has_focus_changed(GObject * object,GParamSpec * pspec,gpointer user_data)612 on_has_focus_changed (GObject *object,
613 GParamSpec *pspec,
614 gpointer user_data)
615 {
616 NautilusLocationEntry *entry;
617 NautilusLocationEntryPrivate *priv;
618
619 if (!gtk_widget_has_focus (GTK_WIDGET (object)))
620 {
621 return;
622 }
623
624 entry = NAUTILUS_LOCATION_ENTRY (object);
625 priv = nautilus_location_entry_get_instance_private (entry);
626
627 if (priv->has_special_text)
628 {
629 priv->setting_special_text = TRUE;
630 gtk_entry_set_text (GTK_ENTRY (entry), "");
631 priv->setting_special_text = FALSE;
632 }
633 }
634
635 static void
nautilus_location_entry_text_changed(NautilusLocationEntry * entry,GParamSpec * pspec)636 nautilus_location_entry_text_changed (NautilusLocationEntry *entry,
637 GParamSpec *pspec)
638 {
639 NautilusLocationEntryPrivate *priv;
640
641 priv = nautilus_location_entry_get_instance_private (entry);
642
643 if (priv->setting_special_text)
644 {
645 return;
646 }
647
648 priv->has_special_text = FALSE;
649 }
650
651 static void
nautilus_location_entry_icon_release(GtkEntry * gentry,GtkEntryIconPosition position,GdkEvent * event,gpointer unused)652 nautilus_location_entry_icon_release (GtkEntry *gentry,
653 GtkEntryIconPosition position,
654 GdkEvent *event,
655 gpointer unused)
656 {
657 NautilusLocationEntry *entry;
658 NautilusLocationEntryPrivate *priv;
659
660 entry = NAUTILUS_LOCATION_ENTRY (gentry);
661 priv = nautilus_location_entry_get_instance_private (entry);
662
663 switch (priv->secondary_action)
664 {
665 case NAUTILUS_LOCATION_ENTRY_ACTION_GOTO:
666 {
667 g_signal_emit_by_name (gentry, "activate", gentry);
668 }
669 break;
670
671 case NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR:
672 {
673 gtk_entry_set_text (gentry, "");
674 }
675 break;
676
677 default:
678 {
679 g_assert_not_reached ();
680 }
681 }
682 }
683
684 static gboolean
nautilus_location_entry_on_event(GtkWidget * widget,GdkEvent * event)685 nautilus_location_entry_on_event (GtkWidget *widget,
686 GdkEvent *event)
687 {
688 GtkWidgetClass *parent_widget_class;
689 NautilusLocationEntry *entry;
690 NautilusLocationEntryPrivate *priv;
691 GtkEditable *editable;
692 gboolean selected;
693 guint keyval;
694 GdkModifierType state;
695 gboolean handled;
696
697 parent_widget_class = GTK_WIDGET_CLASS (nautilus_location_entry_parent_class);
698
699 if (gdk_event_get_event_type (event) != GDK_KEY_PRESS)
700 {
701 return parent_widget_class->event (widget, event);
702 }
703
704 entry = NAUTILUS_LOCATION_ENTRY (widget);
705 priv = nautilus_location_entry_get_instance_private (entry);
706 editable = GTK_EDITABLE (widget);
707 selected = gtk_editable_get_selection_bounds (editable, NULL, NULL);
708
709 if (!gtk_editable_get_editable (editable))
710 {
711 return GDK_EVENT_PROPAGATE;
712 }
713
714 if (G_UNLIKELY (!gdk_event_get_keyval (event, &keyval)))
715 {
716 g_return_val_if_reached (GDK_EVENT_PROPAGATE);
717 }
718 if (G_UNLIKELY (!gdk_event_get_state (event, &state)))
719 {
720 g_return_val_if_reached (GDK_EVENT_PROPAGATE);
721 }
722
723 /* The location bar entry wants TAB to work kind of
724 * like it does in the shell for command completion,
725 * so if we get a tab and there's a selection, we
726 * should position the insertion point at the end of
727 * the selection.
728 */
729 if (keyval == GDK_KEY_Tab && !(state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
730 {
731 if (selected)
732 {
733 int position;
734
735 position = strlen (gtk_entry_get_text (GTK_ENTRY (editable)));
736 gtk_editable_select_region (editable, position, position);
737 }
738 else
739 {
740 gtk_widget_error_bell (GTK_WIDGET (entry));
741 }
742
743 return GDK_EVENT_STOP;
744 }
745
746 if ((keyval == GDK_KEY_Right || keyval == GDK_KEY_End) &&
747 !(state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) && selected)
748 {
749 set_position_and_selection_to_end (editable);
750 }
751
752 /* GTK+ 4 TODO: Calling the event vfunc is not enough, we need the entry
753 * to handle the key press and insert the text first.
754 *
755 * Chaining up here is required either way, since the code below
756 * used to be in the handler for ::event-after, which is no longer a thing.
757 */
758 handled = parent_widget_class->key_press_event (widget, (GdkEventKey *) event);
759
760
761 if (keyval == GDK_KEY_Down || keyval == GDK_KEY_Up)
762 {
763 /* Ignore up/down arrow keys. These are used by the entry completion,
764 * and if we modify the completion store, navigation through the list
765 * will be interrupted. */
766 return GDK_EVENT_PROPAGATE;
767 }
768
769 /* Only do completions when we are typing at the end of the
770 * text. Do the expand at idle time to avoid slowing down
771 * typing when the directory is large. Only insert an expansion
772 * when we type a key that would have inserted characters.
773 */
774 if (position_and_selection_are_at_end (editable))
775 {
776 /* Only insert a completion if a character was typed. Otherwise,
777 * update the completions store (i.e. in case backspace was pressed)
778 * but don't insert the completion into the entry. */
779 priv->idle_insert_completion = entry_would_have_inserted_characters (event);
780
781 if (priv->idle_id == 0)
782 {
783 priv->idle_id = g_idle_add (update_completions_store, widget);
784 }
785 }
786 else
787 {
788 /* FIXME: Also might be good to do this when you click
789 * to change the position or selection.
790 */
791 if (priv->idle_id != 0)
792 {
793 g_source_remove (priv->idle_id);
794 priv->idle_id = 0;
795 }
796 }
797
798 return handled;
799 }
800
801 static void
nautilus_location_entry_activate(GtkEntry * entry)802 nautilus_location_entry_activate (GtkEntry *entry)
803 {
804 NautilusLocationEntry *loc_entry;
805 NautilusLocationEntryPrivate *priv;
806 const gchar *entry_text;
807 gchar *full_path, *uri_scheme = NULL;
808 g_autofree char *path = NULL;
809
810 loc_entry = NAUTILUS_LOCATION_ENTRY (entry);
811 priv = nautilus_location_entry_get_instance_private (loc_entry);
812 entry_text = gtk_entry_get_text (entry);
813 path = g_strdup (entry_text);
814 path = g_strchug (path);
815 path = g_strchomp (path);
816
817 if (path != NULL && *path != '\0')
818 {
819 uri_scheme = g_uri_parse_scheme (path);
820
821 if (!g_path_is_absolute (path) && uri_scheme == NULL && path[0] != '~')
822 {
823 /* Fix non absolute paths */
824 full_path = g_build_filename (priv->current_directory, path, NULL);
825 gtk_entry_set_text (entry, full_path);
826 g_free (full_path);
827 }
828
829 g_free (uri_scheme);
830 }
831
832 GTK_ENTRY_CLASS (nautilus_location_entry_parent_class)->activate (entry);
833 }
834
835 static void
nautilus_location_entry_cancel(NautilusLocationEntry * entry)836 nautilus_location_entry_cancel (NautilusLocationEntry *entry)
837 {
838 NautilusLocationEntryPrivate *priv;
839
840 priv = nautilus_location_entry_get_instance_private (entry);
841
842 nautilus_location_entry_set_location (entry, priv->last_location);
843 }
844
845 static void
nautilus_location_entry_class_init(NautilusLocationEntryClass * class)846 nautilus_location_entry_class_init (NautilusLocationEntryClass *class)
847 {
848 GtkWidgetClass *widget_class;
849 GObjectClass *gobject_class;
850 GtkEntryClass *entry_class;
851 GtkBindingSet *binding_set;
852
853 widget_class = GTK_WIDGET_CLASS (class);
854 widget_class->destroy = destroy;
855 widget_class->event = nautilus_location_entry_on_event;
856
857 gobject_class = G_OBJECT_CLASS (class);
858 gobject_class->finalize = finalize;
859
860 entry_class = GTK_ENTRY_CLASS (class);
861 entry_class->activate = nautilus_location_entry_activate;
862
863 class->cancel = nautilus_location_entry_cancel;
864
865 signals[CANCEL] = g_signal_new
866 ("cancel",
867 G_TYPE_FROM_CLASS (class),
868 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
869 G_STRUCT_OFFSET (NautilusLocationEntryClass,
870 cancel),
871 NULL, NULL,
872 g_cclosure_marshal_VOID__VOID,
873 G_TYPE_NONE, 0);
874
875 signals[LOCATION_CHANGED] = g_signal_new
876 ("location-changed",
877 G_TYPE_FROM_CLASS (class),
878 G_SIGNAL_RUN_LAST, 0,
879 NULL, NULL,
880 g_cclosure_marshal_generic,
881 G_TYPE_NONE, 1, G_TYPE_OBJECT);
882
883 binding_set = gtk_binding_set_by_class (class);
884 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "cancel", 0);
885 }
886
887 void
nautilus_location_entry_set_secondary_action(NautilusLocationEntry * entry,NautilusLocationEntryAction secondary_action)888 nautilus_location_entry_set_secondary_action (NautilusLocationEntry *entry,
889 NautilusLocationEntryAction secondary_action)
890 {
891 NautilusLocationEntryPrivate *priv;
892
893 priv = nautilus_location_entry_get_instance_private (entry);
894
895 if (priv->secondary_action == secondary_action)
896 {
897 return;
898 }
899
900 switch (secondary_action)
901 {
902 case NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR:
903 {
904 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
905 GTK_ENTRY_ICON_SECONDARY,
906 "edit-clear-symbolic");
907 }
908 break;
909
910 case NAUTILUS_LOCATION_ENTRY_ACTION_GOTO:
911 {
912 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
913 GTK_ENTRY_ICON_SECONDARY,
914 "go-next-symbolic");
915 }
916 break;
917
918 default:
919 {
920 g_assert_not_reached ();
921 }
922 }
923 priv->secondary_action = secondary_action;
924 }
925
926 static void
editable_activate_callback(GtkEntry * entry,gpointer user_data)927 editable_activate_callback (GtkEntry *entry,
928 gpointer user_data)
929 {
930 NautilusLocationEntry *self = user_data;
931 const char *entry_text;
932 g_autofree gchar *path = NULL;
933
934 entry_text = gtk_entry_get_text (entry);
935 path = g_strdup (entry_text);
936 path = g_strchug (path);
937 path = g_strchomp (path);
938
939 if (path != NULL && *path != '\0')
940 {
941 gtk_entry_set_text (entry, path);
942 emit_location_changed (self);
943 }
944 }
945
946 static void
editable_changed_callback(GtkEntry * entry,gpointer user_data)947 editable_changed_callback (GtkEntry *entry,
948 gpointer user_data)
949 {
950 nautilus_location_entry_update_action (NAUTILUS_LOCATION_ENTRY (entry));
951 }
952
953 static void
nautilus_location_entry_init(NautilusLocationEntry * entry)954 nautilus_location_entry_init (NautilusLocationEntry *entry)
955 {
956 NautilusLocationEntryPrivate *priv;
957
958 priv = nautilus_location_entry_get_instance_private (entry);
959
960 priv->completer = g_filename_completer_new ();
961 g_filename_completer_set_dirs_only (priv->completer, TRUE);
962
963 nautilus_location_entry_set_secondary_action (entry,
964 NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR);
965
966 g_signal_connect (entry, "notify::has-focus",
967 G_CALLBACK (on_has_focus_changed), NULL);
968
969 g_signal_connect (entry, "notify::text",
970 G_CALLBACK (nautilus_location_entry_text_changed), NULL);
971
972 g_signal_connect (entry, "icon-release",
973 G_CALLBACK (nautilus_location_entry_icon_release), NULL);
974
975 g_signal_connect (priv->completer, "got-completion-data",
976 G_CALLBACK (got_completion_data_callback), entry);
977
978 /* Drag source */
979 g_signal_connect_object (entry, "drag-data-get",
980 G_CALLBACK (drag_data_get_callback), entry, 0);
981
982 /* Drag dest. */
983 gtk_drag_dest_set (GTK_WIDGET (entry),
984 GTK_DEST_DEFAULT_ALL,
985 drop_types, G_N_ELEMENTS (drop_types),
986 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
987 g_signal_connect (entry, "drag-data-received",
988 G_CALLBACK (drag_data_received_callback), NULL);
989
990 g_signal_connect_object (entry, "activate",
991 G_CALLBACK (editable_activate_callback), entry, G_CONNECT_AFTER);
992 g_signal_connect_object (entry, "changed",
993 G_CALLBACK (editable_changed_callback), entry, 0);
994
995 priv->completion = gtk_entry_completion_new ();
996 priv->completions_store = gtk_list_store_new (1, G_TYPE_STRING);
997 gtk_entry_completion_set_model (priv->completion, GTK_TREE_MODEL (priv->completions_store));
998
999 g_object_set (priv->completion,
1000 "text-column", 0,
1001 "inline-completion", TRUE,
1002 "inline-selection", TRUE,
1003 "popup-single-match", TRUE,
1004 NULL);
1005
1006 priv->completion_cell = gtk_cell_renderer_text_new ();
1007 g_object_set (priv->completion_cell, "xpad", 6, NULL);
1008
1009 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->completion), priv->completion_cell, FALSE);
1010 gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->completion), priv->completion_cell, "text", 0);
1011
1012 gtk_entry_set_completion (GTK_ENTRY (entry), priv->completion);
1013 }
1014
1015 GtkWidget *
nautilus_location_entry_new(void)1016 nautilus_location_entry_new (void)
1017 {
1018 GtkWidget *entry;
1019
1020 entry = gtk_widget_new (NAUTILUS_TYPE_LOCATION_ENTRY, "max-width-chars", 350, NULL);
1021
1022 return entry;
1023 }
1024
1025 void
nautilus_location_entry_set_special_text(NautilusLocationEntry * entry,const char * special_text)1026 nautilus_location_entry_set_special_text (NautilusLocationEntry *entry,
1027 const char *special_text)
1028 {
1029 NautilusLocationEntryPrivate *priv;
1030
1031 priv = nautilus_location_entry_get_instance_private (entry);
1032
1033 priv->has_special_text = TRUE;
1034
1035 g_free (priv->special_text);
1036 priv->special_text = g_strdup (special_text);
1037
1038 priv->setting_special_text = TRUE;
1039 gtk_entry_set_text (GTK_ENTRY (entry), special_text);
1040 priv->setting_special_text = FALSE;
1041 }
1042