1 /* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
2 /* GTK - The GIMP Toolkit
3 * gtkfilechooserdialog.c: File selector dialog
4 * Copyright (C) 2003, Red Hat, Inc.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
23 #include "gtkfilechooserprivate.h"
24 #include "gtkfilechooserdialog.h"
25 #include "gtkfilechooserwidget.h"
26 #include "gtkfilechooserutils.h"
27 #include "gtkfilechooserembed.h"
28 #include "gtkfilesystem.h"
29 #include "gtktypebuiltins.h"
35 #define GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE(o) (GTK_FILE_CHOOSER_DIALOG (o)->priv)
37 static void gtk_file_chooser_dialog_finalize (GObject *object);
39 static GObject* gtk_file_chooser_dialog_constructor (GType type,
40 guint n_construct_properties,
41 GObjectConstructParam *construct_params);
42 static void gtk_file_chooser_dialog_set_property (GObject *object,
46 static void gtk_file_chooser_dialog_get_property (GObject *object,
51 static void gtk_file_chooser_dialog_map (GtkWidget *widget);
52 static void gtk_file_chooser_dialog_unmap (GtkWidget *widget);
54 static void response_cb (GtkDialog *dialog,
57 G_DEFINE_TYPE_WITH_CODE (GtkFileChooserDialog, gtk_file_chooser_dialog, GTK_TYPE_DIALOG,
58 G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER,
59 _gtk_file_chooser_delegate_iface_init))
62 gtk_file_chooser_dialog_class_init (GtkFileChooserDialogClass *class)
64 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
65 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
67 gobject_class->constructor = gtk_file_chooser_dialog_constructor;
68 gobject_class->set_property = gtk_file_chooser_dialog_set_property;
69 gobject_class->get_property = gtk_file_chooser_dialog_get_property;
70 gobject_class->finalize = gtk_file_chooser_dialog_finalize;
72 widget_class->map = gtk_file_chooser_dialog_map;
73 widget_class->unmap = gtk_file_chooser_dialog_unmap;
75 _gtk_file_chooser_install_properties (gobject_class);
77 g_type_class_add_private (class, sizeof (GtkFileChooserDialogPrivate));
81 gtk_file_chooser_dialog_init (GtkFileChooserDialog *dialog)
83 GtkFileChooserDialogPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (dialog,
84 GTK_TYPE_FILE_CHOOSER_DIALOG,
85 GtkFileChooserDialogPrivate);
86 GtkDialog *fc_dialog = GTK_DIALOG (dialog);
89 dialog->priv->default_width = -1;
90 dialog->priv->default_height = -1;
91 dialog->priv->resize_horizontally = TRUE;
92 dialog->priv->resize_vertically = TRUE;
93 dialog->priv->response_requested = FALSE;
95 gtk_dialog_set_has_separator (fc_dialog, FALSE);
96 gtk_container_set_border_width (GTK_CONTAINER (fc_dialog), 5);
97 gtk_box_set_spacing (GTK_BOX (fc_dialog->vbox), 2); /* 2 * 5 + 2 = 12 */
98 gtk_container_set_border_width (GTK_CONTAINER (fc_dialog->action_area), 5);
100 /* We do a signal connection here rather than overriding the method in
101 * class_init because GtkDialog::response is a RUN_LAST signal. We want *our*
102 * handler to be run *first*, regardless of whether the user installs response
103 * handlers of his own.
105 g_signal_connect (dialog, "response",
106 G_CALLBACK (response_cb), NULL);
110 gtk_file_chooser_dialog_finalize (GObject *object)
112 GtkFileChooserDialog *dialog = GTK_FILE_CHOOSER_DIALOG (object);
114 g_free (dialog->priv->file_system);
116 G_OBJECT_CLASS (gtk_file_chooser_dialog_parent_class)->finalize (object);
119 /* Callback used when the user activates a file in the file chooser widget */
121 file_chooser_widget_file_activated (GtkFileChooser *chooser,
122 GtkFileChooserDialog *dialog)
126 if (gtk_window_activate_default (GTK_WINDOW (dialog)))
129 /* There probably isn't a default widget, so make things easier for the
130 * programmer by looking for a reasonable button on our own.
133 children = gtk_container_get_children (GTK_CONTAINER (GTK_DIALOG (dialog)->action_area));
135 for (l = children; l; l = l->next)
140 widget = GTK_WIDGET (l->data);
141 response_id = gtk_dialog_get_response_for_widget (GTK_DIALOG (dialog), widget);
142 if (response_id == GTK_RESPONSE_ACCEPT
143 || response_id == GTK_RESPONSE_OK
144 || response_id == GTK_RESPONSE_YES
145 || response_id == GTK_RESPONSE_APPLY)
147 gtk_widget_activate (widget); /* Should we gtk_dialog_response (dialog, response_id) instead? */
152 g_list_free (children);
156 file_chooser_widget_update_hints (GtkFileChooserDialog *dialog,
159 GtkFileChooserDialogPrivate *priv;
160 GdkGeometry geometry;
162 priv = GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE (dialog);
164 geometry.min_width = (!priv->resize_horizontally ? width : -1);
165 geometry.min_height = -1;
166 geometry.max_width = (priv->resize_horizontally?G_MAXSHORT:-1);
167 geometry.max_height = (priv->resize_vertically?G_MAXSHORT:-1);
169 gtk_window_set_geometry_hints (GTK_WINDOW (dialog), NULL,
171 GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE);
175 clamp_to_screen (GtkWidget *widget,
181 GdkRectangle monitor;
183 g_return_if_fail (GTK_WIDGET_REALIZED (widget));
185 screen = gtk_widget_get_screen (widget);
186 monitor_num = gdk_screen_get_monitor_at_window (screen, widget->window);
188 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
191 *width = MIN (*width, (monitor.width * 3) / 4);
194 *height = MIN (*height, (monitor.height * 3) / 4);
198 file_chooser_widget_default_realized_size_changed (GtkWidget *widget,
199 GtkFileChooserDialog *dialog)
201 GtkFileChooserDialogPrivate *priv;
204 gint default_width, default_height;
206 gboolean resize_horizontally;
207 gboolean resize_vertically;
208 gboolean update_hints;
210 gint cur_width, cur_height;
212 priv = GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE (dialog);
214 /* Force a size request of everything before we start. This will make sure
215 * that widget->requisition is meaningful. */
216 gtk_widget_size_request (GTK_WIDGET (dialog), &req);
217 gtk_window_get_size (GTK_WINDOW (dialog), &cur_width, &cur_height);
218 width = GTK_WIDGET (dialog)->requisition.width - priv->widget->requisition.width;
219 height = GTK_WIDGET (dialog)->requisition.height - priv->widget->requisition.height;
220 _gtk_file_chooser_embed_get_default_size (GTK_FILE_CHOOSER_EMBED (priv->widget),
221 &default_width, &default_height);
223 /* Ideal target size modulo any resizing */
224 width = default_width + width;
225 height = default_height + height;
227 /* Now, we test for resizability */
228 update_hints = FALSE;
229 _gtk_file_chooser_embed_get_resizable_hints (GTK_FILE_CHOOSER_EMBED (priv->widget),
230 &resize_horizontally,
232 resize_vertically = (!! resize_vertically); /* normalize */
233 resize_horizontally = (!! resize_horizontally);
235 if (resize_horizontally && priv->resize_horizontally)
237 dx = default_width - priv->default_width;
238 priv->default_width = default_width;
240 else if (resize_horizontally && ! priv->resize_horizontally)
242 /* We restore to the ideal size + any change in default_size (which is not
243 * expected). It would be nicer to store the older size to restore to in
245 dx = default_width - priv->default_width;
246 dx += width - cur_width;
247 priv->default_width = default_width;
255 if (resize_vertically && priv->resize_vertically)
257 dy = default_height - priv->default_height;
258 priv->default_height = default_height;
260 else if (resize_vertically && ! priv->resize_vertically)
262 dy = default_height - priv->default_height;
263 dy += height - cur_height;
264 priv->default_height = default_height;
272 priv->resize_horizontally = resize_horizontally;
273 priv->resize_vertically = resize_vertically;
275 if (dx != 0 || dy != 0)
277 gint new_width = cur_width + dx;
278 gint new_height = cur_height + dy;
280 clamp_to_screen (GTK_WIDGET (dialog), &new_width, &new_height);
282 gtk_window_resize (GTK_WINDOW (dialog), new_width, new_height);
285 /* Only store the size if we can resize in that direction. */
287 file_chooser_widget_update_hints (dialog, width);
291 file_chooser_widget_default_unrealized_size_changed (GtkWidget *widget,
292 GtkFileChooserDialog *dialog)
294 GtkFileChooserDialogPrivate *priv;
298 priv = GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE (dialog);
299 gtk_widget_size_request (GTK_WIDGET (dialog), &req);
301 _gtk_file_chooser_embed_get_resizable_hints (GTK_FILE_CHOOSER_EMBED (priv->widget),
302 &(priv->resize_horizontally),
303 &(priv->resize_vertically));
304 _gtk_file_chooser_embed_get_default_size (GTK_FILE_CHOOSER_EMBED (priv->widget),
305 &(priv->default_width), &(priv->default_height));
307 /* Determine how much space the rest of the dialog uses compared to priv->widget */
308 width = priv->default_width + GTK_WIDGET (dialog)->requisition.width - priv->widget->requisition.width;
309 height = priv->default_height + GTK_WIDGET (dialog)->requisition.height - priv->widget->requisition.height;
311 gtk_window_set_default_size (GTK_WINDOW (dialog), width, height);
312 file_chooser_widget_update_hints (dialog, width);
316 file_chooser_widget_default_size_changed (GtkWidget *widget,
317 GtkFileChooserDialog *dialog)
319 if (GTK_WIDGET_REALIZED (dialog))
320 file_chooser_widget_default_realized_size_changed (widget, dialog);
322 file_chooser_widget_default_unrealized_size_changed (widget, dialog);
326 file_chooser_widget_response_requested (GtkWidget *widget,
327 GtkFileChooserDialog *dialog)
331 /* There probably isn't a default widget, so make things easier for the
332 * programmer by looking for a reasonable button on our own.
335 children = gtk_container_get_children (GTK_CONTAINER (GTK_DIALOG (dialog)->action_area));
337 for (l = children; l; l = l->next)
342 widget = GTK_WIDGET (l->data);
343 response_id = gtk_dialog_get_response_for_widget (GTK_DIALOG (dialog), widget);
344 if (response_id == GTK_RESPONSE_ACCEPT
345 || response_id == GTK_RESPONSE_OK
346 || response_id == GTK_RESPONSE_YES
347 || response_id == GTK_RESPONSE_APPLY)
349 dialog->priv->response_requested = TRUE;
350 gtk_widget_activate (widget); /* Should we gtk_dialog_response (dialog, response_id) instead? */
355 g_list_free (children);
359 gtk_file_chooser_dialog_constructor (GType type,
360 guint n_construct_properties,
361 GObjectConstructParam *construct_params)
363 GtkFileChooserDialogPrivate *priv;
366 object = G_OBJECT_CLASS (gtk_file_chooser_dialog_parent_class)->constructor (type,
367 n_construct_properties,
369 priv = GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE (object);
371 gtk_widget_push_composite_child ();
373 if (priv->file_system)
374 priv->widget = g_object_new (GTK_TYPE_FILE_CHOOSER_WIDGET,
375 "file-system-backend", priv->file_system,
378 priv->widget = g_object_new (GTK_TYPE_FILE_CHOOSER_WIDGET, NULL);
380 g_signal_connect (priv->widget, "file-activated",
381 G_CALLBACK (file_chooser_widget_file_activated), object);
382 g_signal_connect (priv->widget, "default-size-changed",
383 G_CALLBACK (file_chooser_widget_default_size_changed), object);
384 g_signal_connect (priv->widget, "response-requested",
385 G_CALLBACK (file_chooser_widget_response_requested), object);
387 gtk_container_set_border_width (GTK_CONTAINER (priv->widget), 5);
388 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (object)->vbox), priv->widget, TRUE, TRUE, 0);
390 gtk_widget_show (priv->widget);
392 _gtk_file_chooser_set_delegate (GTK_FILE_CHOOSER (object),
393 GTK_FILE_CHOOSER (priv->widget));
395 gtk_widget_pop_composite_child ();
401 gtk_file_chooser_dialog_set_property (GObject *object,
407 GtkFileChooserDialogPrivate *priv = GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE (object);
411 case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
412 g_free (priv->file_system);
413 priv->file_system = g_value_dup_string (value);
416 g_object_set_property (G_OBJECT (priv->widget), pspec->name, value);
422 gtk_file_chooser_dialog_get_property (GObject *object,
427 GtkFileChooserDialogPrivate *priv = GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE (object);
429 g_object_get_property (G_OBJECT (priv->widget), pspec->name, value);
434 set_default_size (GtkFileChooserDialog *dialog)
438 int default_width, default_height;
444 GdkRectangle monitor;
446 widget = GTK_WIDGET (dialog);
447 window = GTK_WINDOW (dialog);
449 /* Size based on characters */
451 font_size = pango_font_description_get_size (widget->style->font_desc);
452 font_size = PANGO_PIXELS (font_size);
454 width = font_size * NUM_CHARS;
455 height = font_size * NUM_LINES;
457 /* Use at least the requisition size... */
459 gtk_widget_size_request (widget, &req);
460 width = MAX (width, req.width);
461 height = MAX (height, req.height);
463 /* ... but no larger than the monitor */
465 screen = gtk_widget_get_screen (widget);
466 monitor_num = gdk_screen_get_monitor_at_window (screen, widget->window);
468 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
470 width = MIN (width, monitor.width * 3 / 4);
471 height = MIN (height, monitor.height * 3 / 4);
475 gtk_window_get_default_size (window, &default_width, &default_height);
477 gtk_window_set_default_size (window,
478 (default_width == -1) ? width : default_width,
479 (default_height == -1) ? height : default_height);
483 /* GtkWidget::map handler */
485 gtk_file_chooser_dialog_map (GtkWidget *widget)
487 GtkFileChooserDialog *dialog = GTK_FILE_CHOOSER_DIALOG (widget);
488 GtkFileChooserDialogPrivate *priv = GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE (dialog);
490 if (!GTK_WIDGET_MAPPED (priv->widget))
491 gtk_widget_map (priv->widget);
493 _gtk_file_chooser_embed_initial_focus (GTK_FILE_CHOOSER_EMBED (priv->widget));
495 GTK_WIDGET_CLASS (gtk_file_chooser_dialog_parent_class)->map (widget);
498 /* GtkWidget::unmap handler */
500 gtk_file_chooser_dialog_unmap (GtkWidget *widget)
502 GtkFileChooserDialog *dialog = GTK_FILE_CHOOSER_DIALOG (widget);
503 GtkFileChooserDialogPrivate *priv = GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE (dialog);
505 GTK_WIDGET_CLASS (gtk_file_chooser_dialog_parent_class)->unmap (widget);
507 /* See bug #145470. We unmap the GtkFileChooserWidget so that if the dialog
508 * is remapped, the widget will be remapped as well. Implementations should
509 * refresh their contents when this happens, as some applications keep a
510 * single file chooser alive and map/unmap it as needed, rather than creating
511 * a new file chooser every time they need one.
513 gtk_widget_unmap (priv->widget);
516 /* GtkDialog::response handler */
518 response_cb (GtkDialog *dialog,
521 GtkFileChooserDialogPrivate *priv;
523 priv = GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE (dialog);
525 /* Act only on response IDs we recognize */
526 if (!(response_id == GTK_RESPONSE_ACCEPT
527 || response_id == GTK_RESPONSE_OK
528 || response_id == GTK_RESPONSE_YES
529 || response_id == GTK_RESPONSE_APPLY))
532 if (!priv->response_requested && !_gtk_file_chooser_embed_should_respond (GTK_FILE_CHOOSER_EMBED (priv->widget)))
534 g_signal_stop_emission_by_name (dialog, "response");
535 priv->response_requested = FALSE;
540 gtk_file_chooser_dialog_new_valist (const gchar *title,
542 GtkFileChooserAction action,
543 const gchar *backend,
544 const gchar *first_button_text,
548 const char *button_text = first_button_text;
551 result = g_object_new (GTK_TYPE_FILE_CHOOSER_DIALOG,
554 "file-system-backend", backend,
558 gtk_window_set_transient_for (GTK_WINDOW (result), parent);
562 response_id = va_arg (varargs, gint);
563 gtk_dialog_add_button (GTK_DIALOG (result), button_text, response_id);
564 button_text = va_arg (varargs, const gchar *);
571 * gtk_file_chooser_dialog_new:
572 * @title: Title of the dialog, or %NULL
573 * @parent: Transient parent of the dialog, or %NULL
574 * @action: Open or save mode for the dialog
575 * @first_button_text: stock ID or text to go in the first button, or %NULL
576 * @Varargs: response ID for the first button, then additional (button, id) pairs, ending with %NULL
578 * Creates a new #GtkFileChooserDialog. This function is analogous to
579 * gtk_dialog_new_with_buttons().
581 * Return value: a new #GtkFileChooserDialog
586 gtk_file_chooser_dialog_new (const gchar *title,
588 GtkFileChooserAction action,
589 const gchar *first_button_text,
595 va_start (varargs, first_button_text);
596 result = gtk_file_chooser_dialog_new_valist (title, parent, action,
597 NULL, first_button_text,
605 * gtk_file_chooser_dialog_new_with_backend:
606 * @title: Title of the dialog, or %NULL
607 * @parent: Transient parent of the dialog, or %NULL
608 * @action: Open or save mode for the dialog
609 * @backend: The name of the specific filesystem backend to use.
610 * @first_button_text: stock ID or text to go in the first button, or %NULL
611 * @Varargs: response ID for the first button, then additional (button, id) pairs, ending with %NULL
613 * Creates a new #GtkFileChooserDialog with a specified backend. This is
614 * especially useful if you use gtk_file_chooser_set_local_only() to allow
615 * non-local files and you use a more expressive vfs, such as gnome-vfs,
618 * Return value: a new #GtkFileChooserDialog
623 gtk_file_chooser_dialog_new_with_backend (const gchar *title,
625 GtkFileChooserAction action,
626 const gchar *backend,
627 const gchar *first_button_text,
633 va_start (varargs, first_button_text);
634 result = gtk_file_chooser_dialog_new_valist (title, parent, action,
635 backend, first_button_text,
642 #define __GTK_FILE_CHOOSER_DIALOG_C__
643 #include "gtkaliasdef.c"