]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilechooserdialog.c
Split function into realized and unrealized variants, and consolidate the
[~andy/gtk] / gtk / gtkfilechooserdialog.c
1 /* GTK - The GIMP Toolkit
2  * gtkfilechooserdialog.c: File selector dialog
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 "gtkfilechooserdialog.h"
22 #include "gtkfilechooserwidget.h"
23 #include "gtkfilechooserutils.h"
24 #include "gtkfilechooserembed.h"
25 #include "gtkfilesystem.h"
26 #include "gtktypebuiltins.h"
27
28 #include <stdarg.h>
29
30 struct _GtkFileChooserDialogPrivate
31 {
32   GtkWidget *widget;
33   
34   char *file_system;
35
36   /* for use with GtkFileChooserEmbed */
37   gint default_width;
38   gint default_height;
39   gboolean resize_horizontally;
40   gboolean resize_vertically;
41 };
42
43 #define GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE(o)  (GTK_FILE_CHOOSER_DIALOG (o)->priv)
44
45 static void gtk_file_chooser_dialog_class_init (GtkFileChooserDialogClass *class);
46 static void gtk_file_chooser_dialog_init       (GtkFileChooserDialog      *dialog);
47 static void gtk_file_chooser_dialog_finalize   (GObject                   *object);
48
49 static GObject* gtk_file_chooser_dialog_constructor  (GType                  type,
50                                                       guint                  n_construct_properties,
51                                                       GObjectConstructParam *construct_params);
52 static void     gtk_file_chooser_dialog_set_property (GObject               *object,
53                                                       guint                  prop_id,
54                                                       const GValue          *value,
55                                                       GParamSpec            *pspec);
56 static void     gtk_file_chooser_dialog_get_property (GObject               *object,
57                                                       guint                  prop_id,
58                                                       GValue                *value,
59                                                       GParamSpec            *pspec);
60
61 static void     gtk_file_chooser_dialog_style_set    (GtkWidget             *widget,
62                                                       GtkStyle              *previous_style);
63
64 static GObjectClass *parent_class;
65
66 GType
67 gtk_file_chooser_dialog_get_type (void)
68 {
69   static GType file_chooser_dialog_type = 0;
70
71   if (!file_chooser_dialog_type)
72     {
73       static const GTypeInfo file_chooser_dialog_info =
74       {
75         sizeof (GtkFileChooserDialogClass),
76         NULL,           /* base_init */
77         NULL,           /* base_finalize */
78         (GClassInitFunc) gtk_file_chooser_dialog_class_init,
79         NULL,           /* class_finalize */
80         NULL,           /* class_data */
81         sizeof (GtkFileChooserDialog),
82         0,              /* n_preallocs */
83         (GInstanceInitFunc) gtk_file_chooser_dialog_init,
84       };
85
86       static const GInterfaceInfo file_chooser_info =
87       {
88         (GInterfaceInitFunc) _gtk_file_chooser_delegate_iface_init, /* interface_init */
89         NULL,                                                       /* interface_finalize */
90         NULL                                                        /* interface_data */
91       };
92
93       file_chooser_dialog_type = g_type_register_static (GTK_TYPE_DIALOG, "GtkFileChooserDialog",
94                                                          &file_chooser_dialog_info, 0);
95       g_type_add_interface_static (file_chooser_dialog_type,
96                                    GTK_TYPE_FILE_CHOOSER,
97                                    &file_chooser_info);
98     }
99
100   return file_chooser_dialog_type;
101 }
102
103 static void
104 gtk_file_chooser_dialog_class_init (GtkFileChooserDialogClass *class)
105 {
106   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
107   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
108
109   parent_class = g_type_class_peek_parent (class);
110
111   gobject_class->constructor = gtk_file_chooser_dialog_constructor;
112   gobject_class->set_property = gtk_file_chooser_dialog_set_property;
113   gobject_class->get_property = gtk_file_chooser_dialog_get_property;
114   gobject_class->finalize = gtk_file_chooser_dialog_finalize;
115
116   widget_class->style_set = gtk_file_chooser_dialog_style_set;
117
118   _gtk_file_chooser_install_properties (gobject_class);
119
120   g_type_class_add_private (class, sizeof (GtkFileChooserDialogPrivate));
121 }
122
123 static void
124 gtk_file_chooser_dialog_init (GtkFileChooserDialog *dialog)
125 {
126   GtkFileChooserDialogPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (dialog,
127                                                                    GTK_TYPE_FILE_CHOOSER_DIALOG,
128                                                                    GtkFileChooserDialogPrivate);
129   dialog->priv = priv;
130   dialog->priv->default_width = -1;
131   dialog->priv->default_height = -1;
132   dialog->priv->resize_horizontally = TRUE;
133   dialog->priv->resize_vertically = TRUE;
134
135   gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
136 }
137
138 static void
139 gtk_file_chooser_dialog_finalize (GObject *object)
140 {
141   GtkFileChooserDialog *dialog = GTK_FILE_CHOOSER_DIALOG (object);
142
143   g_free (dialog->priv->file_system);
144
145   G_OBJECT_CLASS (parent_class)->finalize (object);  
146 }
147
148 /* Callback used when the user activates a file in the file chooser widget */
149 static void
150 file_chooser_widget_file_activated (GtkFileChooser       *chooser,
151                                     GtkFileChooserDialog *dialog)
152 {
153   gtk_window_activate_default (GTK_WINDOW (dialog));
154 }
155
156 static void
157 file_chooser_widget_update_hints (GtkFileChooserDialog *dialog)
158 {
159   GtkFileChooserDialogPrivate *priv;
160   GdkGeometry geometry;
161
162   priv = GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE (dialog);
163
164   geometry.min_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);
168
169   gtk_window_set_geometry_hints (GTK_WINDOW (dialog), NULL,
170                                  &geometry,
171                                  GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE);
172 }
173
174 static void
175 file_chooser_widget_default_realized_size_changed (GtkWidget            *widget,
176                                                    GtkFileChooserDialog *dialog)
177 {
178   GtkFileChooserDialogPrivate *priv;
179   gint width;
180   gint height;
181   gint default_width, default_height;
182   GtkRequisition req;
183   gboolean resize_horizontally;
184   gboolean resize_vertically;
185   gboolean update_hints;
186   gint dx = 0, dy = 0;
187   gint cur_width, cur_height;
188
189   priv = GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE (dialog);
190
191   /* Force a size request of everything before we start.  This will make sure
192    * that widget->requisition is meaningful. */
193   gtk_widget_size_request (GTK_WIDGET (dialog), &req);
194   gtk_window_get_size (GTK_WINDOW (dialog), &cur_width, &cur_height);
195   width = GTK_WIDGET (dialog)->requisition.width - priv->widget->requisition.width;
196   height = GTK_WIDGET (dialog)->requisition.height - priv->widget->requisition.height;
197   _gtk_file_chooser_embed_get_default_size (GTK_FILE_CHOOSER_EMBED (priv->widget),
198                                             &default_width, &default_height);
199
200   /* Ideal target size modulo any resizing */
201   width = default_width + width;
202   height = default_height + height;
203
204   /* Now, we test for resizability */
205   update_hints = FALSE;
206   _gtk_file_chooser_embed_get_resizable_hints (GTK_FILE_CHOOSER_EMBED (priv->widget),
207                                                &resize_horizontally,
208                                                &resize_vertically);
209   resize_vertically = (!! resize_vertically);     /* normalize */
210   resize_horizontally = (!! resize_horizontally);
211
212   if (resize_horizontally && priv->resize_horizontally)
213     {
214       dx = default_width - priv->default_width;
215       priv->default_width = default_width;
216     }
217   else if (resize_horizontally && ! priv->resize_horizontally)
218     {
219       /* We restore to the ideal size + any change in default_size (which is not
220        * expected).  It would be nicer to store the older size to restore to in
221        * the future. */
222       dx = default_width - priv->default_width;
223       dx += width - cur_width;
224       priv->default_width = default_width;
225       update_hints = TRUE;
226     }
227   else
228     {
229       update_hints = TRUE;
230     }
231   
232   if (resize_vertically && priv->resize_vertically)
233     {
234       dy = default_height - priv->default_height;
235       priv->default_height = default_height;
236     }
237   else if (resize_vertically && ! priv->resize_vertically)
238     {
239       dy = default_height - priv->default_height;
240       dy += height - cur_height;
241       priv->default_height = default_height;
242       update_hints = TRUE;
243     }
244   else
245     {
246       update_hints = TRUE;
247     }
248
249   priv->resize_horizontally = resize_horizontally;
250   priv->resize_vertically = resize_vertically;
251
252   /* FIXME: We should make sure that we arent' bigger than the current screen */
253   if (dx != 0 || dy != 0)
254     {
255       gtk_window_resize (GTK_WINDOW (dialog),
256                          cur_width + dx,
257                          cur_height + dy);
258     }
259
260   /* Only store the size if we can resize in that direction. */
261   if (update_hints)
262     file_chooser_widget_update_hints (dialog);
263 }
264
265 static void
266 file_chooser_widget_default_unrealized_size_changed (GtkWidget            *widget,
267                                                      GtkFileChooserDialog *dialog)
268 {
269   GtkFileChooserDialogPrivate *priv;
270   GtkRequisition req;
271   gint width, height;
272
273   priv = GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE (dialog);
274   gtk_widget_size_request (GTK_WIDGET (dialog), &req);
275
276   _gtk_file_chooser_embed_get_resizable_hints (GTK_FILE_CHOOSER_EMBED (priv->widget),
277                                                &(priv->resize_horizontally),
278                                                &(priv->resize_vertically));
279   _gtk_file_chooser_embed_get_default_size (GTK_FILE_CHOOSER_EMBED (priv->widget),
280                                             &(priv->default_width), &(priv->default_height));
281   
282   /* Determine how much space the rest of the dialog uses compared to priv->widget */
283   width = priv->default_width + GTK_WIDGET (dialog)->requisition.width - priv->widget->requisition.width;
284   height = priv->default_height + GTK_WIDGET (dialog)->requisition.height - priv->widget->requisition.height;
285
286   gtk_window_set_default_size (GTK_WINDOW (dialog), width, height);
287   file_chooser_widget_update_hints (dialog);
288 }
289
290 static void
291 file_chooser_widget_default_size_changed (GtkWidget            *widget,
292                                           GtkFileChooserDialog *dialog)
293 {
294   if (GTK_WIDGET_REALIZED (dialog))
295     file_chooser_widget_default_realized_size_changed (widget, dialog);
296   else
297     file_chooser_widget_default_unrealized_size_changed (widget, dialog);
298 }
299   
300 static GObject*
301 gtk_file_chooser_dialog_constructor (GType                  type,
302                                      guint                  n_construct_properties,
303                                      GObjectConstructParam *construct_params)
304 {
305   GtkFileChooserDialogPrivate *priv;
306   GObject *object;
307
308   object = parent_class->constructor (type,
309                                       n_construct_properties,
310                                       construct_params);
311   priv = GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE (object);
312
313   gtk_widget_push_composite_child ();
314
315   if (priv->file_system)
316     priv->widget = g_object_new (GTK_TYPE_FILE_CHOOSER_WIDGET,
317                                  "file-system-backend", priv->file_system,
318                                  NULL);
319   else
320     priv->widget = g_object_new (GTK_TYPE_FILE_CHOOSER_WIDGET, NULL);
321
322   g_signal_connect (priv->widget, "file-activated",
323                     G_CALLBACK (file_chooser_widget_file_activated), object);
324   g_signal_connect (priv->widget, "default-size-changed",
325                     G_CALLBACK (file_chooser_widget_default_size_changed), object);
326
327   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (object)->vbox), priv->widget, TRUE, TRUE, 0);
328   gtk_widget_show (priv->widget);
329
330   _gtk_file_chooser_set_delegate (GTK_FILE_CHOOSER (object),
331                                   GTK_FILE_CHOOSER (priv->widget));
332
333   gtk_widget_pop_composite_child ();
334
335   return object;
336 }
337
338 static void
339 gtk_file_chooser_dialog_set_property (GObject         *object,
340                                       guint            prop_id,
341                                       const GValue    *value,
342                                       GParamSpec      *pspec)
343
344 {
345   GtkFileChooserDialogPrivate *priv = GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE (object);
346
347   switch (prop_id)
348     {
349     case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
350       g_free (priv->file_system);
351       priv->file_system = g_value_dup_string (value);
352       break;
353     default:
354       g_object_set_property (G_OBJECT (priv->widget), pspec->name, value);
355       break;
356     }
357 }
358
359 static void
360 gtk_file_chooser_dialog_get_property (GObject         *object,
361                                       guint            prop_id,
362                                       GValue          *value,
363                                       GParamSpec      *pspec)
364 {
365   GtkFileChooserDialogPrivate *priv = GTK_FILE_CHOOSER_DIALOG_GET_PRIVATE (object);
366
367   g_object_get_property (G_OBJECT (priv->widget), pspec->name, value);
368 }
369
370 #if 0
371 static void
372 set_default_size (GtkFileChooserDialog *dialog)
373 {
374   GtkWidget *widget;
375   GtkWindow *window;
376   int default_width, default_height;
377   int width, height;
378   int font_size;
379   GdkScreen *screen;
380   int monitor_num;
381   GtkRequisition req;
382   GdkRectangle monitor;
383
384   widget = GTK_WIDGET (dialog);
385   window = GTK_WINDOW (dialog);
386
387   /* Size based on characters */
388
389   font_size = pango_font_description_get_size (widget->style->font_desc);
390   font_size = PANGO_PIXELS (font_size);
391
392   width = font_size * NUM_CHARS;
393   height = font_size * NUM_LINES;
394
395   /* Use at least the requisition size... */
396
397   gtk_widget_size_request (widget, &req);
398   width = MAX (width, req.width);
399   height = MAX (height, req.height);
400
401   /* ... but no larger than the monitor */
402
403   screen = gtk_widget_get_screen (widget);
404   monitor_num = gdk_screen_get_monitor_at_window (screen, widget->window);
405
406   gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
407
408   width = MIN (width, monitor.width * 3 / 4);
409   height = MIN (height, monitor.height * 3 / 4);
410
411   /* Set size */
412
413   gtk_window_get_default_size (window, &default_width, &default_height);
414
415   gtk_window_set_default_size (window,
416                                (default_width == -1) ? width : default_width,
417                                (default_height == -1) ? height : default_height);
418 }
419 #endif
420
421 static void
422 gtk_file_chooser_dialog_style_set (GtkWidget *widget,
423                                    GtkStyle  *previous_style)
424 {
425   GtkDialog *dialog;
426
427   if (GTK_WIDGET_CLASS (parent_class)->style_set)
428     GTK_WIDGET_CLASS (parent_class)->style_set (widget, previous_style);
429
430   dialog = GTK_DIALOG (widget);
431
432   /* Override the style properties with HIG-compliant spacings.  Ugh.
433    * http://developer.gnome.org/projects/gup/hig/1.0/layout.html#layout-dialogs
434    * http://developer.gnome.org/projects/gup/hig/1.0/windows.html#alert-spacing
435    */
436
437   gtk_container_set_border_width (GTK_CONTAINER (dialog->vbox), 12);
438   gtk_box_set_spacing (GTK_BOX (dialog->vbox), 24);
439
440   gtk_container_set_border_width (GTK_CONTAINER (dialog->action_area), 0);
441   gtk_box_set_spacing (GTK_BOX (dialog->action_area), 6);
442 }
443
444 static GtkWidget *
445 gtk_file_chooser_dialog_new_valist (const gchar          *title,
446                                     GtkWindow            *parent,
447                                     GtkFileChooserAction  action,
448                                     const gchar          *backend,
449                                     const gchar          *first_button_text,
450                                     va_list               varargs)
451 {
452   GtkWidget *result;
453   const char *button_text = first_button_text;
454   gint response_id;
455
456   result = g_object_new (GTK_TYPE_FILE_CHOOSER_DIALOG,
457                          "title", title,
458                          "action", action,
459                          "file-system-backend", backend,
460                          NULL);
461
462   if (parent)
463     gtk_window_set_transient_for (GTK_WINDOW (result), parent);
464
465   while (button_text)
466     {
467       response_id = va_arg (varargs, gint);
468       gtk_dialog_add_button (GTK_DIALOG (result), button_text, response_id);
469       button_text = va_arg (varargs, const gchar *);
470     }
471
472   return result;
473 }
474
475 /**
476  * gtk_file_chooser_dialog_new:
477  * @title: Title of the dialog, or %NULL
478  * @parent: Transient parent of the dialog, or %NULL
479  * @action: Open or save mode for the dialog
480  * @first_button_text: stock ID or text to go in the first button, or %NULL
481  * @Varargs: response ID for the first button, then additional (button, id) pairs, ending with %NULL
482  *
483  * Creates a new #GtkFileChooserDialog.  This function is analogous to
484  * gtk_dialog_new_with_buttons().
485  *
486  * Return value: a new #GtkFileChooserDialog
487  *
488  * Since: 2.4
489  **/
490 GtkWidget *
491 gtk_file_chooser_dialog_new (const gchar         *title,
492                              GtkWindow           *parent,
493                              GtkFileChooserAction action,
494                              const gchar         *first_button_text,
495                              ...)
496 {
497   GtkWidget *result;
498   va_list varargs;
499   
500   va_start (varargs, first_button_text);
501   result = gtk_file_chooser_dialog_new_valist (title, parent, action,
502                                                NULL, first_button_text,
503                                                varargs);
504   va_end (varargs);
505
506   return result;
507 }
508
509 /**
510  * gtk_file_chooser_dialog_new_with_backend:
511  * @title: Title of the dialog, or %NULL
512  * @parent: Transient parent of the dialog, or %NULL
513  * @backend: The name of the specific filesystem backend to use.
514  * @action: Open or save mode for the dialog
515  * @first_button_text: stock ID or text to go in the first button, or %NULL
516  * @Varargs: response ID for the first button, then additional (button, id) pairs, ending with %NULL
517  *
518  * Creates a new #GtkFileChooserDialog with a specified backend. This is
519  * especially useful if you use gtk_file_chooser_set_local_only() to allow
520  * non-local files and you use a more expressive vfs, such as gnome-vfs,
521  * to load files.
522  *
523  * Return value: a new #GtkFileChooserDialog
524  *
525  * Since: 2.4
526  **/
527 GtkWidget *
528 gtk_file_chooser_dialog_new_with_backend (const gchar          *title,
529                                           GtkWindow            *parent,
530                                           GtkFileChooserAction  action,
531                                           const gchar          *backend,
532                                           const gchar          *first_button_text,
533                                           ...)
534 {
535   GtkWidget *result;
536   va_list varargs;
537   
538   va_start (varargs, first_button_text);
539   result = gtk_file_chooser_dialog_new_valist (title, parent, action,
540                                                backend, first_button_text,
541                                                varargs);
542   va_end (varargs);
543
544   return result;
545 }