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