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