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