1 /* nautilus-rename-file-popover-controller.c
2  *
3  * Copyright (C) 2016 the Nautilus developers
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program 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  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #include <glib/gi18n.h>
21 
22 #include <eel/eel-vfs-extensions.h>
23 
24 #include "nautilus-rename-file-popover-controller.h"
25 
26 #include "nautilus-directory.h"
27 #include "nautilus-file-private.h"
28 
29 
30 #define RENAME_ENTRY_MIN_CHARS 20
31 #define RENAME_ENTRY_MAX_CHARS 35
32 
33 struct _NautilusRenameFilePopoverController
34 {
35     NautilusFileNameWidgetController parent_instance;
36 
37     NautilusFile *target_file;
38     gboolean target_is_folder;
39 
40     GtkWidget *rename_file_popover;
41     GtkWidget *name_entry;
42     GtkWidget *name_label;
43 
44     gulong closed_handler_id;
45     gulong file_changed_handler_id;
46     gulong key_press_event_handler_id;
47 };
48 
G_DEFINE_TYPE(NautilusRenameFilePopoverController,nautilus_rename_file_popover_controller,NAUTILUS_TYPE_FILE_NAME_WIDGET_CONTROLLER)49 G_DEFINE_TYPE (NautilusRenameFilePopoverController, nautilus_rename_file_popover_controller, NAUTILUS_TYPE_FILE_NAME_WIDGET_CONTROLLER)
50 
51 static void
52 disconnect_signal_handlers (NautilusRenameFilePopoverController *self)
53 {
54     g_assert (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self));
55 
56     g_clear_signal_handler (&self->closed_handler_id, self->rename_file_popover);
57     g_clear_signal_handler (&self->file_changed_handler_id, self->target_file);
58     g_clear_signal_handler (&self->key_press_event_handler_id, self->name_entry);
59 }
60 
61 static void
reset_state(NautilusRenameFilePopoverController * self)62 reset_state (NautilusRenameFilePopoverController *self)
63 {
64     g_assert (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self));
65 
66     disconnect_signal_handlers (self);
67 
68     g_clear_object (&self->target_file);
69 
70     gtk_popover_popdown (GTK_POPOVER (self->rename_file_popover));
71 }
72 
73 static void
rename_file_popover_controller_on_closed(GtkPopover * popover,gpointer user_data)74 rename_file_popover_controller_on_closed (GtkPopover *popover,
75                                           gpointer    user_data)
76 {
77     NautilusRenameFilePopoverController *controller;
78 
79     controller = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (user_data);
80 
81     reset_state (controller);
82 
83     g_signal_emit_by_name (controller, "cancelled");
84 }
85 
86 static gboolean
nautilus_rename_file_popover_controller_name_is_valid(NautilusFileNameWidgetController * controller,gchar * name,gchar ** error_message)87 nautilus_rename_file_popover_controller_name_is_valid (NautilusFileNameWidgetController  *controller,
88                                                        gchar                             *name,
89                                                        gchar                            **error_message)
90 {
91     NautilusRenameFilePopoverController *self;
92     gboolean is_valid;
93 
94     self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (controller);
95 
96     is_valid = TRUE;
97     if (strlen (name) == 0)
98     {
99         is_valid = FALSE;
100     }
101     else if (strstr (name, "/") != NULL)
102     {
103         is_valid = FALSE;
104         if (self->target_is_folder)
105         {
106             *error_message = _("Folder names cannot contain “/”.");
107         }
108         else
109         {
110             *error_message = _("File names cannot contain “/”.");
111         }
112     }
113     else if (strcmp (name, ".") == 0)
114     {
115         is_valid = FALSE;
116         if (self->target_is_folder)
117         {
118             *error_message = _("A folder cannot be called “.”.");
119         }
120         else
121         {
122             *error_message = _("A file cannot be called “.”.");
123         }
124     }
125     else if (strcmp (name, "..") == 0)
126     {
127         is_valid = FALSE;
128         if (self->target_is_folder)
129         {
130             *error_message = _("A folder cannot be called “..”.");
131         }
132         else
133         {
134             *error_message = _("A file cannot be called “..”.");
135         }
136     }
137     else if (nautilus_file_name_widget_controller_is_name_too_long (controller, name))
138     {
139         is_valid = FALSE;
140         if (self->target_is_folder)
141         {
142             *error_message = _("Folder name is too long.");
143         }
144         else
145         {
146             *error_message = _("File name is too long.");
147         }
148     }
149 
150     if (is_valid && g_str_has_prefix (name, "."))
151     {
152         /* We must warn about the side effect */
153         if (self->target_is_folder)
154         {
155             *error_message = _("Folders with “.” at the beginning of their name are hidden.");
156         }
157         else
158         {
159             *error_message = _("Files with “.” at the beginning of their name are hidden.");
160         }
161     }
162 
163     return is_valid;
164 }
165 
166 static gboolean
nautilus_rename_file_popover_controller_ignore_existing_file(NautilusFileNameWidgetController * controller,NautilusFile * existing_file)167 nautilus_rename_file_popover_controller_ignore_existing_file (NautilusFileNameWidgetController *controller,
168                                                               NautilusFile                     *existing_file)
169 {
170     NautilusRenameFilePopoverController *self;
171     g_autofree gchar *display_name = NULL;
172 
173     self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (controller);
174 
175     display_name = nautilus_file_get_display_name (existing_file);
176 
177     return nautilus_file_compare_display_name (self->target_file, display_name) == 0;
178 }
179 
180 static gboolean
name_entry_on_f2_pressed(GtkWidget * widget,NautilusRenameFilePopoverController * self)181 name_entry_on_f2_pressed (GtkWidget                           *widget,
182                           NautilusRenameFilePopoverController *self)
183 {
184     guint text_length;
185     gint start_pos;
186     gint end_pos;
187     gboolean all_selected;
188 
189     text_length = (guint) gtk_entry_get_text_length (GTK_ENTRY (widget));
190     if (text_length == 0)
191     {
192         return GDK_EVENT_STOP;
193     }
194 
195     gtk_editable_get_selection_bounds (GTK_EDITABLE (widget),
196                                        &start_pos, &end_pos);
197 
198     all_selected = (start_pos == 0) && ((guint) end_pos == text_length);
199     if (!all_selected || !nautilus_file_is_regular_file (self->target_file))
200     {
201         gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1);
202     }
203     else
204     {
205         gint start_offset;
206         gint end_offset;
207 
208         /* Select the name part without the file extension */
209         eel_filename_get_rename_region (gtk_entry_get_text (GTK_ENTRY (widget)),
210                                         &start_offset, &end_offset);
211         gtk_editable_select_region (GTK_EDITABLE (widget),
212                                     start_offset, end_offset);
213     }
214 
215     return GDK_EVENT_STOP;
216 }
217 
218 static gboolean
name_entry_on_undo(GtkWidget * widget,NautilusRenameFilePopoverController * self)219 name_entry_on_undo (GtkWidget                           *widget,
220                     NautilusRenameFilePopoverController *self)
221 {
222     g_autofree gchar *edit_name = NULL;
223 
224     edit_name = nautilus_file_get_edit_name (self->target_file);
225 
226     gtk_entry_set_text (GTK_ENTRY (widget), edit_name);
227 
228     gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1);
229 
230     return GDK_EVENT_STOP;
231 }
232 
233 static gboolean
name_entry_on_event(GtkWidget * widget,GdkEvent * event,gpointer user_data)234 name_entry_on_event (GtkWidget *widget,
235                      GdkEvent  *event,
236                      gpointer   user_data)
237 {
238     NautilusRenameFilePopoverController *self;
239     guint keyval;
240     GdkModifierType state;
241 
242     if (gdk_event_get_event_type (event) != GDK_KEY_PRESS)
243     {
244         return GDK_EVENT_PROPAGATE;
245     }
246 
247     self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (user_data);
248 
249     if (G_UNLIKELY (!gdk_event_get_keyval (event, &keyval)))
250     {
251         g_return_val_if_reached (GDK_EVENT_PROPAGATE);
252     }
253     if (G_UNLIKELY (!gdk_event_get_state (event, &state)))
254     {
255         g_return_val_if_reached (GDK_EVENT_PROPAGATE);
256     }
257 
258     if (keyval == GDK_KEY_F2)
259     {
260         return name_entry_on_f2_pressed (widget, self);
261     }
262     else if (keyval == GDK_KEY_z && (state & GDK_CONTROL_MASK) != 0)
263     {
264         return name_entry_on_undo (widget, self);
265     }
266 
267     return GDK_EVENT_PROPAGATE;
268 }
269 
270 static void
target_file_on_changed(NautilusFile * file,gpointer user_data)271 target_file_on_changed (NautilusFile *file,
272                         gpointer      user_data)
273 {
274     NautilusRenameFilePopoverController *controller;
275 
276     controller = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (user_data);
277 
278     if (nautilus_file_is_gone (file))
279     {
280         reset_state (controller);
281 
282         g_signal_emit_by_name (controller, "cancelled");
283     }
284 }
285 
286 NautilusRenameFilePopoverController *
nautilus_rename_file_popover_controller_new(void)287 nautilus_rename_file_popover_controller_new (void)
288 {
289     NautilusRenameFilePopoverController *self;
290     g_autoptr (GtkBuilder) builder = NULL;
291     GtkWidget *rename_file_popover;
292     GtkWidget *error_revealer;
293     GtkWidget *error_label;
294     GtkWidget *name_entry;
295     GtkWidget *activate_button;
296     GtkWidget *name_label;
297 
298     builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-rename-file-popover.ui");
299     rename_file_popover = GTK_WIDGET (gtk_builder_get_object (builder, "rename_file_popover"));
300     error_revealer = GTK_WIDGET (gtk_builder_get_object (builder, "error_revealer"));
301     error_label = GTK_WIDGET (gtk_builder_get_object (builder, "error_label"));
302     name_entry = GTK_WIDGET (gtk_builder_get_object (builder, "name_entry"));
303     activate_button = GTK_WIDGET (gtk_builder_get_object (builder, "rename_button"));
304     name_label = GTK_WIDGET (gtk_builder_get_object (builder, "name_label"));
305 
306     self = g_object_new (NAUTILUS_TYPE_RENAME_FILE_POPOVER_CONTROLLER,
307                          "error-revealer", error_revealer,
308                          "error-label", error_label,
309                          "name-entry", name_entry,
310                          "activate-button", activate_button,
311                          NULL);
312 
313     self->rename_file_popover = g_object_ref_sink (rename_file_popover);
314     self->name_entry = name_entry;
315     self->name_label = name_label;
316 
317     gtk_popover_set_default_widget (GTK_POPOVER (rename_file_popover), name_entry);
318 
319     return self;
320 }
321 
322 NautilusFile *
nautilus_rename_file_popover_controller_get_target_file(NautilusRenameFilePopoverController * self)323 nautilus_rename_file_popover_controller_get_target_file (NautilusRenameFilePopoverController *self)
324 {
325     g_return_val_if_fail (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self), NULL);
326 
327     return self->target_file;
328 }
329 
330 void
nautilus_rename_file_popover_controller_show_for_file(NautilusRenameFilePopoverController * self,NautilusFile * target_file,GdkRectangle * pointing_to,GtkWidget * relative_to)331 nautilus_rename_file_popover_controller_show_for_file   (NautilusRenameFilePopoverController *self,
332                                                          NautilusFile                        *target_file,
333                                                          GdkRectangle                        *pointing_to,
334                                                          GtkWidget                           *relative_to)
335 {
336     g_autoptr (NautilusDirectory) containing_directory = NULL;
337     g_autofree gchar *edit_name = NULL;
338     gint n_chars;
339 
340     g_assert (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self));
341     g_assert (NAUTILUS_IS_FILE (target_file));
342 
343     reset_state (self);
344 
345     self->target_file = g_object_ref (target_file);
346 
347     if (!nautilus_file_is_self_owned (self->target_file))
348     {
349         g_autoptr (NautilusFile) parent = NULL;
350 
351         parent = nautilus_file_get_parent (self->target_file);
352         containing_directory = nautilus_directory_get_for_file (parent);
353     }
354     else
355     {
356         containing_directory = nautilus_directory_get_for_file (self->target_file);
357     }
358 
359     nautilus_file_name_widget_controller_set_containing_directory (NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (self),
360                                                                    containing_directory);
361 
362     self->target_is_folder = nautilus_file_is_directory (self->target_file);
363 
364     self->closed_handler_id = g_signal_connect (self->rename_file_popover,
365                                                 "closed",
366                                                 G_CALLBACK (rename_file_popover_controller_on_closed),
367                                                 self);
368 
369     self->file_changed_handler_id = g_signal_connect (self->target_file,
370                                                       "changed",
371                                                       G_CALLBACK (target_file_on_changed),
372                                                       self);
373 
374     self->key_press_event_handler_id = g_signal_connect (self->name_entry,
375                                                          "event",
376                                                          G_CALLBACK (name_entry_on_event),
377                                                          self);
378 
379     gtk_label_set_text (GTK_LABEL (self->name_label),
380                         self->target_is_folder ? _("Folder name") :
381                         _("File name"));
382 
383     edit_name = nautilus_file_get_edit_name (self->target_file);
384 
385     gtk_entry_set_text (GTK_ENTRY (self->name_entry), edit_name);
386 
387     gtk_popover_set_pointing_to (GTK_POPOVER (self->rename_file_popover), pointing_to);
388     gtk_popover_set_relative_to (GTK_POPOVER (self->rename_file_popover), relative_to);
389 
390     gtk_popover_popup (GTK_POPOVER (self->rename_file_popover));
391 
392     if (nautilus_file_is_regular_file (self->target_file))
393     {
394         gint start_offset;
395         gint end_offset;
396 
397         /* Select the name part without the file extension */
398         eel_filename_get_rename_region (edit_name,
399                                         &start_offset, &end_offset);
400         gtk_editable_select_region (GTK_EDITABLE (self->name_entry),
401                                     start_offset, end_offset);
402     }
403 
404     n_chars = g_utf8_strlen (edit_name, -1);
405     gtk_entry_set_width_chars (GTK_ENTRY (self->name_entry),
406                                MIN (MAX (n_chars, RENAME_ENTRY_MIN_CHARS),
407                                     RENAME_ENTRY_MAX_CHARS));
408 }
409 
410 static void
on_name_accepted(NautilusFileNameWidgetController * controller)411 on_name_accepted (NautilusFileNameWidgetController *controller)
412 {
413     NautilusRenameFilePopoverController *self;
414 
415     self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (controller);
416 
417     reset_state (self);
418 }
419 
420 static void
nautilus_rename_file_popover_controller_init(NautilusRenameFilePopoverController * self)421 nautilus_rename_file_popover_controller_init (NautilusRenameFilePopoverController *self)
422 {
423     g_signal_connect_after (self, "name-accepted", G_CALLBACK (on_name_accepted), self);
424 }
425 
426 static void
nautilus_rename_file_popover_controller_finalize(GObject * object)427 nautilus_rename_file_popover_controller_finalize (GObject *object)
428 {
429     NautilusRenameFilePopoverController *self;
430 
431     self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (object);
432 
433     reset_state (self);
434 
435     gtk_widget_destroy (self->rename_file_popover);
436     g_clear_object (&self->rename_file_popover);
437 
438     G_OBJECT_CLASS (nautilus_rename_file_popover_controller_parent_class)->finalize (object);
439 }
440 
441 static void
nautilus_rename_file_popover_controller_class_init(NautilusRenameFilePopoverControllerClass * klass)442 nautilus_rename_file_popover_controller_class_init (NautilusRenameFilePopoverControllerClass *klass)
443 {
444     GObjectClass *object_class = G_OBJECT_CLASS (klass);
445     NautilusFileNameWidgetControllerClass *parent_class = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_CLASS (klass);
446 
447     object_class->finalize = nautilus_rename_file_popover_controller_finalize;
448 
449     parent_class->name_is_valid = nautilus_rename_file_popover_controller_name_is_valid;
450     parent_class->ignore_existing_file = nautilus_rename_file_popover_controller_ignore_existing_file;
451 }
452