]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilechooserbutton.c
98ba56ef87656fd93e28f340653333b755100ef9
[~andy/gtk] / gtk / gtkfilechooserbutton.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 2 -*- */
2
3 /* GTK+: gtkfilechooserbutton.c
4  * 
5  * Copyright (c) 2004 James M. Cape <jcape@ignore-your.tv>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include <config.h>
24
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #ifdef HAVE_UNISTD_H
28 #include <unistd.h>
29 #endif
30
31 #include <string.h>
32
33 #include "gtkalias.h"
34 #include "gtkintl.h"
35 #include "gtkbutton.h"
36 #include "gtkdnd.h"
37 #include "gtkicontheme.h"
38 #include "gtkiconfactory.h"
39 #include "gtkimage.h"
40 #include "gtklabel.h"
41 #include "gtkstock.h"
42 #include "gtkvseparator.h"
43 #include "gtkfilechooserdialog.h"
44 #include "gtkfilechooserprivate.h"
45 #include "gtkfilechooserutils.h"
46
47 #include "gtkfilechooserbutton.h"
48
49
50 /* **************** *
51  *  Private Macros  *
52  * **************** */
53
54 #define GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE(object) (GTK_FILE_CHOOSER_BUTTON ((object))->priv)
55
56 #define DEFAULT_FILENAME        N_("(None)")
57 #define MIN_LABEL_WIDTH         100
58 #define ENTRY_BUTTON_SPACING    0
59 #define FALLBACK_ICON_SIZE      20
60 #define FALLBACK_ICON_NAME      "stock_unknown"
61
62 /* ********************** *
63  *  Private Enumerations  *
64  * ********************** */
65
66 /* Property IDs */
67 enum
68 {
69   PROP_0,
70
71   PROP_DIALOG,
72   PROP_TITLE,
73   PROP_WIDTH_CHARS
74 };
75
76
77 /* ******************** *
78  *  Private Structures  *
79  * ******************** */
80
81 struct _GtkFileChooserButtonPrivate
82 {
83   GtkWidget *dialog;
84   GtkWidget *button;
85   GtkWidget *image;
86   GtkWidget *label;
87
88   GtkFilePath *old_path;
89   gchar *backend;
90   gulong dialog_file_activated_id;
91   gulong dialog_folder_changed_id;
92   gulong dialog_selection_changed_id;
93   gint icon_size;
94
95   /* Used for hiding/showing the dialog when the button is hidden */
96   guint8 active : 1;
97 };
98
99
100 /* ************* *
101  *  DnD Support  *
102  * ************* */
103
104 enum
105 {
106   TEXT_PLAIN,
107   TEXT_URI_LIST
108 };
109
110 /* ********************* *
111  *  Function Prototypes  *
112  * ********************* */
113
114 /* GObject Functions */
115 static GObject *gtk_file_chooser_button_constructor        (GType             type,
116                                                             guint             n_params,
117                                                             GObjectConstructParam *params);
118 static void     gtk_file_chooser_button_set_property       (GObject          *object,
119                                                             guint             param_id,
120                                                             const GValue     *value,
121                                                             GParamSpec       *pspec);
122 static void     gtk_file_chooser_button_get_property       (GObject          *object,
123                                                             guint             param_id,
124                                                             GValue           *value,
125                                                             GParamSpec       *pspec);
126
127 /* GtkObject Functions */
128 static void     gtk_file_chooser_button_destroy            (GtkObject        *object);
129
130 /* GtkWidget Functions */
131 static void     gtk_file_chooser_button_drag_data_received (GtkWidget        *widget,
132                                                             GdkDragContext   *context,
133                                                             gint              x,
134                                                             gint              y,
135                                                             GtkSelectionData *data,
136                                                             guint             info,
137                                                             guint             drag_time);
138 static void     gtk_file_chooser_button_show_all           (GtkWidget        *widget);
139 static void     gtk_file_chooser_button_hide_all           (GtkWidget        *widget);
140 static void     gtk_file_chooser_button_show               (GtkWidget        *widget);
141 static void     gtk_file_chooser_button_hide               (GtkWidget        *widget);
142 static gboolean gtk_file_chooser_button_mnemonic_activate  (GtkWidget        *widget,
143                                                             gboolean          group_cycling);
144 static void     gtk_file_chooser_button_style_set          (GtkWidget        *widget,
145                                                             GtkStyle         *old_style);
146 static void     gtk_file_chooser_button_screen_changed     (GtkWidget        *widget,
147                                                             GdkScreen        *old_screen);
148
149 /* Child Widget Callbacks */
150 static void     dialog_update_preview_cb                   (GtkFileChooser   *dialog,
151                                                             gpointer          user_data);
152 static void     dialog_selection_changed_cb                (GtkFileChooser   *dialog,
153                                                             gpointer          user_data);
154 static void     dialog_file_activated_cb                   (GtkFileChooser   *dialog,
155                                                             gpointer          user_data);
156 static void     dialog_current_folder_changed_cb           (GtkFileChooser   *dialog,
157                                                             gpointer          user_data);
158 static void     dialog_notify_cb                           (GObject          *dialog,
159                                                             GParamSpec       *pspec,
160                                                             gpointer          user_data);
161 static gboolean dialog_delete_event_cb                     (GtkWidget        *dialog,
162                                                             GdkEvent         *event,
163                                                             gpointer          user_data);
164 static void     dialog_response_cb                         (GtkDialog        *dialog,
165                                                             gint              response,
166                                                             gpointer          user_data);
167
168 static void     button_clicked_cb                          (GtkButton        *real_button,
169                                                             gpointer          user_data);
170
171 /* Utility Functions */
172 static void     update_label_and_image                     (GtkFileChooserButton *button);
173
174
175 /* ******************* *
176  *  GType Declaration  *
177  * ******************* */
178
179 G_DEFINE_TYPE_WITH_CODE (GtkFileChooserButton, gtk_file_chooser_button, GTK_TYPE_HBOX, { \
180     G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER, _gtk_file_chooser_delegate_iface_init) \
181 });
182
183
184 /* ***************** *
185  *  GType Functions  *
186  * ***************** */
187
188 static void
189 gtk_file_chooser_button_class_init (GtkFileChooserButtonClass * class)
190 {
191   GObjectClass *gobject_class;
192   GtkObjectClass *gtkobject_class;
193   GtkWidgetClass *widget_class;
194
195   gobject_class = G_OBJECT_CLASS (class);
196   gtkobject_class = GTK_OBJECT_CLASS (class);
197   widget_class = GTK_WIDGET_CLASS (class);
198
199   gobject_class->constructor = gtk_file_chooser_button_constructor;
200   gobject_class->set_property = gtk_file_chooser_button_set_property;
201   gobject_class->get_property = gtk_file_chooser_button_get_property;
202
203   gtkobject_class->destroy = gtk_file_chooser_button_destroy;
204
205   widget_class->drag_data_received = gtk_file_chooser_button_drag_data_received;
206   widget_class->show_all = gtk_file_chooser_button_show_all;
207   widget_class->hide_all = gtk_file_chooser_button_hide_all;
208   widget_class->show = gtk_file_chooser_button_show;
209   widget_class->hide = gtk_file_chooser_button_hide;
210   widget_class->style_set = gtk_file_chooser_button_style_set;
211   widget_class->screen_changed = gtk_file_chooser_button_screen_changed;
212   widget_class->mnemonic_activate = gtk_file_chooser_button_mnemonic_activate;
213
214   /**
215    * GtkFileChooserButton:dialog:
216    * 
217    * Instance of the #GtkFileChooserDialog associated with the button.
218    *
219    * Since: 2.6
220    */
221   g_object_class_install_property (gobject_class, PROP_DIALOG,
222                                    g_param_spec_object ("dialog",
223                                                         P_("Dialog"),
224                                                         P_("The file chooser dialog to use."),
225                                                         GTK_TYPE_FILE_CHOOSER_DIALOG,
226                                                         (G_PARAM_WRITABLE |
227                                                          G_PARAM_CONSTRUCT_ONLY)));
228
229   /**
230    * GtkFileChooserButton:title:
231    * 
232    * Title to put on the #GtkFileChooserDialog associated with the button.
233    *
234    * Since: 2.6
235    */
236   g_object_class_install_property (gobject_class, PROP_TITLE,
237                                    g_param_spec_string ("title",
238                                                         P_("Title"),
239                                                         P_("The title of the file chooser dialog."),
240                                                         _("Select a File"),
241                                                         G_PARAM_READWRITE));
242
243   /**
244    * GtkFileChooserButton:width-chars:
245    * 
246    * The width of the entry and label inside the button, in characters.
247    *
248    * Since: 2.6
249    */
250   g_object_class_install_property (gobject_class, PROP_WIDTH_CHARS,
251                                    g_param_spec_int ("width-chars",
252                                                      P_("Width In Characters"),
253                                                      P_("The desired width of the button widget, in characters."),
254                                                      -1, G_MAXINT, -1,
255                                                      G_PARAM_READWRITE));
256
257   _gtk_file_chooser_install_properties (gobject_class);
258
259   g_type_class_add_private (class, sizeof (GtkFileChooserButtonPrivate));
260 }
261
262
263 static void
264 gtk_file_chooser_button_init (GtkFileChooserButton *button)
265 {
266   GtkFileChooserButtonPrivate *priv;
267   GtkWidget *box, *image, *sep;
268   GtkTargetList *target_list;
269
270   priv = G_TYPE_INSTANCE_GET_PRIVATE (button, GTK_TYPE_FILE_CHOOSER_BUTTON,
271                                       GtkFileChooserButtonPrivate);
272   button->priv = priv;
273
274   priv->icon_size = FALLBACK_ICON_SIZE;
275
276   gtk_widget_push_composite_child ();
277
278   priv->button = gtk_button_new ();
279   g_signal_connect (priv->button, "clicked", G_CALLBACK (button_clicked_cb),
280                     button);
281   gtk_container_add (GTK_CONTAINER (button), priv->button);
282   gtk_widget_show (priv->button);
283
284   box = gtk_hbox_new (FALSE, 4);
285   gtk_container_add (GTK_CONTAINER (priv->button), box);
286   gtk_widget_show (box);
287
288   priv->image = gtk_image_new ();
289   gtk_box_pack_start (GTK_BOX (box), priv->image, FALSE, FALSE, 0);
290   gtk_widget_show (priv->image);
291
292   priv->label = gtk_label_new (_(DEFAULT_FILENAME));
293   gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_START);
294   gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5);
295   gtk_container_add (GTK_CONTAINER (box), priv->label);
296   gtk_widget_show (priv->label);
297
298   sep = gtk_vseparator_new ();
299   gtk_box_pack_start (GTK_BOX (box), sep, FALSE, FALSE, 0);
300   gtk_widget_show (sep);
301
302   image = gtk_image_new_from_stock (GTK_STOCK_OPEN,
303                                     GTK_ICON_SIZE_SMALL_TOOLBAR);
304   gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0);
305   gtk_widget_show (image);
306
307   gtk_widget_pop_composite_child ();
308
309   /* DnD */
310   gtk_drag_dest_set (GTK_WIDGET (button),
311                      (GTK_DEST_DEFAULT_ALL),
312                      NULL, 0,
313                      GDK_ACTION_COPY);
314   target_list = gtk_target_list_new (NULL, 0);
315   gtk_target_list_add_uri_targets (target_list, TEXT_URI_LIST);
316   gtk_target_list_add_text_targets (target_list, TEXT_PLAIN);
317   gtk_drag_dest_set_target_list (GTK_WIDGET (button), target_list);
318   gtk_target_list_unref (target_list);
319 }
320
321
322 /* ******************* *
323  *  GObject Functions  *
324  * ******************* */
325
326 static GObject *
327 gtk_file_chooser_button_constructor (GType                  type,
328                                      guint                  n_params,
329                                      GObjectConstructParam *params)
330 {
331   GObject *object;
332   GtkFileChooserButtonPrivate *priv;
333
334   object = (*G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->constructor) (type,
335                                                                                   n_params,
336                                                                                   params);
337   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
338
339   if (!priv->dialog)
340     {
341       if (priv->backend)
342         priv->dialog = gtk_file_chooser_dialog_new_with_backend (NULL, NULL,
343                                                                  GTK_FILE_CHOOSER_ACTION_OPEN,
344                                                                  priv->backend,
345                                                                  GTK_STOCK_CANCEL,
346                                                                  GTK_RESPONSE_CANCEL,
347                                                                  GTK_STOCK_OPEN,
348                                                                  GTK_RESPONSE_ACCEPT,
349                                                                  NULL);
350       else
351         priv->dialog = gtk_file_chooser_dialog_new (NULL, NULL,
352                                                     GTK_FILE_CHOOSER_ACTION_OPEN,
353                                                     GTK_STOCK_CANCEL,
354                                                     GTK_RESPONSE_CANCEL,
355                                                     GTK_STOCK_OPEN,
356                                                     GTK_RESPONSE_ACCEPT,
357                                                     NULL);
358
359       gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog),
360                                        GTK_RESPONSE_ACCEPT);
361       gtk_dialog_set_alternative_button_order (GTK_DIALOG (priv->dialog),
362                                                GTK_RESPONSE_ACCEPT,
363                                                GTK_RESPONSE_CANCEL,
364                                                -1);
365     }
366
367   g_free (priv->backend);
368   priv->backend = NULL;
369
370   g_signal_connect (priv->dialog, "delete-event",
371                     G_CALLBACK (dialog_delete_event_cb), object);
372   g_signal_connect (priv->dialog, "response",
373                     G_CALLBACK (dialog_response_cb), object);
374
375   /* This is used, instead of the standard delegate, to ensure that signals are only
376    * delegated when the OK button is pressed. */
377   g_object_set_qdata (object, GTK_FILE_CHOOSER_DELEGATE_QUARK, priv->dialog);
378   priv->dialog_folder_changed_id =
379     g_signal_connect (priv->dialog, "current-folder-changed",
380                       G_CALLBACK (dialog_current_folder_changed_cb), object);
381   priv->dialog_file_activated_id =
382     g_signal_connect (priv->dialog, "file-activated",
383                       G_CALLBACK (dialog_file_activated_cb), object);
384   priv->dialog_selection_changed_id =
385     g_signal_connect (priv->dialog, "selection-changed",
386                       G_CALLBACK (dialog_selection_changed_cb), object);
387   g_signal_connect (priv->dialog, "update-preview",
388                     G_CALLBACK (dialog_update_preview_cb), object);
389   g_signal_connect (priv->dialog, "notify",
390                     G_CALLBACK (dialog_notify_cb), object);
391   g_object_add_weak_pointer (G_OBJECT (priv->dialog),
392                              (gpointer *) (&priv->dialog));
393
394   return object;
395 }
396
397 static void
398 gtk_file_chooser_button_set_property (GObject      *object,
399                                       guint         param_id,
400                                       const GValue *value,
401                                       GParamSpec   *pspec)
402 {
403   GtkFileChooserButtonPrivate *priv;
404
405   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
406
407   switch (param_id)
408     {
409     case PROP_DIALOG:
410       /* Construct-only */
411       priv->dialog = g_value_get_object (value);
412       break;
413     case PROP_WIDTH_CHARS:
414       gtk_file_chooser_button_set_width_chars (GTK_FILE_CHOOSER_BUTTON (object),
415                                                g_value_get_int (value));
416       break;
417
418     case GTK_FILE_CHOOSER_PROP_ACTION:
419       switch (g_value_get_enum (value))
420         {
421         case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
422         case GTK_FILE_CHOOSER_ACTION_SAVE:
423           {
424             GEnumClass *eclass;
425             GEnumValue *eval;
426
427             eclass = g_type_class_peek (GTK_TYPE_FILE_CHOOSER_ACTION);
428             eval = g_enum_get_value (eclass, g_value_get_enum (value));
429             g_warning ("%s: Choosers of type `%s' do not support `%s'.",
430                        G_STRFUNC, G_OBJECT_TYPE_NAME (object), eval->value_name);
431
432             g_value_set_enum ((GValue *) value, GTK_FILE_CHOOSER_ACTION_OPEN);
433           }
434           break;
435         }
436
437       g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
438       update_label_and_image (GTK_FILE_CHOOSER_BUTTON (object));
439       break;
440
441     case PROP_TITLE:
442     case GTK_FILE_CHOOSER_PROP_FILTER:
443     case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
444     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
445     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
446     case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
447     case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
448     case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
449       g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
450       break;
451
452     case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
453       /* Construct-only */
454       priv->backend = g_value_dup_string (value);
455       break;
456
457     case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
458       g_warning ("%s: Choosers of type `%s` do not support selecting multiple files.",
459                  G_STRFUNC, G_OBJECT_TYPE_NAME (object));
460       break;
461     default:
462       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
463       break;
464     }
465 }
466
467 static void
468 gtk_file_chooser_button_get_property (GObject    *object,
469                                       guint       param_id,
470                                       GValue     *value,
471                                       GParamSpec *pspec)
472 {
473   GtkFileChooserButtonPrivate *priv;
474
475   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
476
477   switch (param_id)
478     {
479     case PROP_WIDTH_CHARS:
480       g_value_set_int (value,
481                        gtk_label_get_width_chars (GTK_LABEL (priv->label)));
482       break;
483
484     case PROP_TITLE:
485     case GTK_FILE_CHOOSER_PROP_ACTION:
486     case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
487     case GTK_FILE_CHOOSER_PROP_FILTER:
488     case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
489     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
490     case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
491     case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
492     case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
493     case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
494     case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
495       g_object_get_property (G_OBJECT (priv->dialog), pspec->name, value);
496       break;
497
498     default:
499       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
500       break;
501     }
502 }
503
504
505 /* ********************* *
506  *  GtkObject Functions  *
507  * ********************* */
508
509 static void
510 gtk_file_chooser_button_destroy (GtkObject * object)
511 {
512   GtkFileChooserButtonPrivate *priv;
513
514   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);
515
516   if (priv->dialog != NULL)
517     gtk_widget_destroy (priv->dialog);
518
519   if (priv->old_path)
520     gtk_file_path_free (priv->old_path);
521
522   if (GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy != NULL)
523     (*GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy) (object);
524 }
525
526
527 /* ********************* *
528  *  GtkWidget Functions  *
529  * ********************* */
530
531 static void
532 gtk_file_chooser_button_drag_data_received (GtkWidget        *widget,
533                                             GdkDragContext   *context,
534                                             gint              x,
535                                             gint              y,
536                                             GtkSelectionData *data,
537                                             guint             info,
538                                             guint             drag_time)
539 {
540   GtkFileChooserButtonPrivate *priv;
541   GtkFileSystem *fs;
542   GtkFilePath *path;
543   gchar *text;
544
545   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received != NULL)
546     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received) (widget,
547                                                                                     context,
548                                                                                     x, y,
549                                                                                     data, info,
550                                                                                     drag_time);
551
552   if (widget == NULL || context == NULL || data == NULL || data->length < 0)
553     return;
554
555   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
556
557   fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
558
559   switch (info)
560     {
561     case TEXT_URI_LIST:
562       {
563         gchar **uris;
564         GtkFilePath *base_path;
565         guint i;
566         gboolean selected;
567
568         uris = gtk_selection_data_get_uris (data);
569         
570         if (uris == NULL)
571           break;
572
573         selected = FALSE;
574         for (i = 0; !selected && uris[i] != NULL; i++)
575           {
576             path = gtk_file_system_uri_to_path (fs, uris[i]);
577
578             base_path = NULL;
579             if (path != NULL &&
580                 gtk_file_system_get_parent (fs, path, &base_path, NULL))
581               {
582                 GtkFileFolder *folder;
583                 GtkFileInfo *info;
584
585                 folder = gtk_file_system_get_folder (fs, base_path,
586                                                      GTK_FILE_INFO_IS_FOLDER,
587                                                      NULL);
588
589                 info = gtk_file_folder_get_info (folder, path, NULL);
590
591                 if (info != NULL)
592                   {
593                     GtkFileChooserAction action;
594
595                     g_object_get (priv->dialog, "action", &action, NULL);
596
597                     selected = 
598                       (((action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER &&
599                          gtk_file_info_get_is_folder (info)) ||
600                         (action == GTK_FILE_CHOOSER_ACTION_OPEN &&
601                          !gtk_file_info_get_is_folder (info))) &&
602                         _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
603                                                        path, NULL));
604
605                     gtk_file_info_free (info);
606                   }
607                 else
608                   selected = FALSE;
609
610                 gtk_file_path_free (base_path);
611               }
612
613             gtk_file_path_free (path);
614           }
615
616         g_strfreev (uris);
617       }
618       break;
619
620     case TEXT_PLAIN:
621       text = gtk_selection_data_get_text (data);
622       path = gtk_file_path_new_steal (text);
623       _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog), path,
624                                      NULL);
625       gtk_file_path_free (path);
626       break;
627
628     default:
629       break;
630     }
631
632   gtk_drag_finish (context, TRUE, FALSE, drag_time);
633 }
634
635 static void
636 gtk_file_chooser_button_show_all (GtkWidget *widget)
637 {
638   gtk_widget_show (widget);
639 }
640
641 static void
642 gtk_file_chooser_button_hide_all (GtkWidget *widget)
643 {
644   gtk_widget_hide (widget);
645 }
646
647 static void
648 gtk_file_chooser_button_show (GtkWidget *widget)
649 {
650   GtkFileChooserButtonPrivate *priv;
651
652   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
653
654   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show)
655     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->show) (widget);
656
657   if (priv->active)
658     gtk_widget_show (priv->dialog);
659 }
660
661 static void
662 gtk_file_chooser_button_hide (GtkWidget *widget)
663 {
664   GtkFileChooserButtonPrivate *priv;
665
666   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
667
668   gtk_widget_hide (priv->dialog);
669
670   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide)
671     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->hide) (widget);
672 }
673
674 static gboolean
675 gtk_file_chooser_button_mnemonic_activate (GtkWidget *widget,
676                                            gboolean   group_cycling)
677 {
678   GtkFileChooserButtonPrivate *priv;
679
680   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);
681   gtk_widget_grab_focus (priv->button);
682
683   return TRUE;
684 }
685
686 /* Changes the icons wherever it is needed */
687 static void
688 change_icon_theme (GtkFileChooserButton *button)
689 {
690   GtkSettings *settings;
691   gint width, height;
692
693   settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (button)));
694
695   if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_SMALL_TOOLBAR,
696                                          &width, &height))
697     button->priv->icon_size = MAX (width, height);
698   else
699     button->priv->icon_size = FALLBACK_ICON_SIZE;
700
701   update_label_and_image (button);
702 }
703
704 static void
705 gtk_file_chooser_button_style_set (GtkWidget *widget,
706                                    GtkStyle  *old_style)
707 {
708   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_set)
709     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->style_set) (widget,
710                                                                            old_style);
711
712   if (gtk_widget_has_screen (widget))
713     change_icon_theme (GTK_FILE_CHOOSER_BUTTON (widget));
714 }
715
716 static void
717 gtk_file_chooser_button_screen_changed (GtkWidget *widget,
718                                         GdkScreen *old_screen)
719 {
720   GtkFileChooserButton *button;
721
722   button = GTK_FILE_CHOOSER_BUTTON (widget);
723
724   if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed)
725     (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->screen_changed) (widget,
726                                                                                 old_screen);
727
728   change_icon_theme (button); 
729 }
730
731
732 /* ************************************************************************** *
733  *  Public API                                                                *
734  * ************************************************************************** */
735
736 /**
737  * gtk_file_chooser_button_new:
738  * @title: the title of the browse dialog.
739  * 
740  * Creates a new file-selecting button widget.
741  * 
742  * Returns: a new button widget.
743  * 
744  * Since: 2.6
745  **/
746 GtkWidget *
747 gtk_file_chooser_button_new (const gchar *title)
748 {
749   return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
750                        "title", title,
751                        NULL);
752 }
753
754 /**
755  * gtk_file_chooser_button_new_with_backend:
756  * @title: the title of the browse dialog.
757  * @backend: the name of the #GtkFileSystem backend to use.
758  * 
759  * Creates a new file-selecting button widget using @backend.
760  * 
761  * Returns: a new button widget.
762  * 
763  * Since: 2.6
764  **/
765 GtkWidget *
766 gtk_file_chooser_button_new_with_backend (const gchar *title,
767                                           const gchar *backend)
768 {
769   return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
770                        "title", title,
771                        "file-system-backend", backend,
772                        NULL);
773 }
774
775 /**
776  * gtk_file_chooser_button_new_with_dialog:
777  * @dialog: the #GtkDialog widget to use.
778  * 
779  * Creates a #GtkFileChooserButton widget which uses @dialog as it's
780  * file-picking window. Note that @dialog must be a #GtkFileChooserDialog (or
781  * subclass).
782  * 
783  * Returns: a new button widget.
784  * 
785  * Since: 2.6
786  **/
787 GtkWidget *
788 gtk_file_chooser_button_new_with_dialog (GtkWidget *dialog)
789 {
790   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_DIALOG (dialog), NULL);
791
792   return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON,
793                        "dialog", dialog,
794                        NULL);
795 }
796
797 /**
798  * gtk_file_chooser_button_set_title:
799  * @button: the button widget to modify.
800  * @title: the new browse dialog title.
801  * 
802  * Modifies the @title of the browse dialog used by @button.
803  * 
804  * Since: 2.6
805  **/
806 void
807 gtk_file_chooser_button_set_title (GtkFileChooserButton *button,
808                                    const gchar          *title)
809 {
810   g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
811
812   gtk_window_set_title (GTK_WINDOW (button->priv->dialog), title);
813   g_object_notify (G_OBJECT (button), "title");
814 }
815
816 /**
817  * gtk_file_chooser_button_get_title:
818  * @button: the button widget to examine.
819  * 
820  * Retrieves the title of the browse dialog used by @button. The returned value
821  * should not be modified or freed.
822  * 
823  * Returns: a pointer to the browse dialog's title.
824  * 
825  * Since: 2.6
826  **/
827 G_CONST_RETURN gchar *
828 gtk_file_chooser_button_get_title (GtkFileChooserButton *button)
829 {
830   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), NULL);
831
832   return gtk_window_get_title (GTK_WINDOW (button->priv->dialog));
833 }
834
835 /**
836  * gtk_file_chooser_button_get_width_chars:
837  * @button: the button widget to examine.
838  * 
839  * Retrieves the width in characters of the @button widget's entry and/or label.
840  * 
841  * Returns: an integer width (in characters) that the button will use to size itself.
842  * 
843  * Since: 2.6
844  **/
845 gint
846 gtk_file_chooser_button_get_width_chars (GtkFileChooserButton *button)
847 {
848   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), -1);
849
850   return gtk_label_get_width_chars (GTK_LABEL (button->priv->label));
851 }
852
853 /**
854  * gtk_file_chooser_button_set_width_chars:
855  * @button: the button widget to examine.
856  * @n_chars: the new width, in chracters.
857  * 
858  * Sets the width (in characters) that @button will use to @n_chars.
859  * 
860  * Since: 2.6
861  **/
862 void
863 gtk_file_chooser_button_set_width_chars (GtkFileChooserButton *button,
864                                          gint                  n_chars)
865 {
866   g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
867
868   gtk_label_set_width_chars (GTK_LABEL (button->priv->label), n_chars);
869   g_object_notify (G_OBJECT (button), "width-chars");
870 }
871
872
873 /* ******************* *
874  *  Utility Functions  *
875  * ******************* */
876
877 static inline GtkIconTheme *
878 get_icon_theme (GtkWidget *widget)
879 {
880   if (gtk_widget_has_screen (widget))
881     return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
882
883   return gtk_icon_theme_get_default ();
884 }
885
886 static void
887 update_label_and_image (GtkFileChooserButton *button)
888 {
889   GtkFileChooserButtonPrivate *priv;
890   GdkPixbuf *pixbuf;
891   gchar *label_text;
892   GSList *paths;
893
894   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (button);
895   paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (button->priv->dialog));
896   label_text = NULL;
897   pixbuf = NULL;
898
899   if (paths)
900     {
901       GtkFileSystem *fs;
902       GtkFilePath *path, *parent_path;
903       GtkFileSystemVolume *volume;
904       GtkFileFolder *folder;
905     
906       path = paths->data;
907
908       fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
909
910       volume = gtk_file_system_get_volume_for_path (fs, path);
911       if (volume)
912         {
913           GtkFilePath *base_path;
914
915           base_path = gtk_file_system_volume_get_base_path (fs, volume);
916           if (base_path && gtk_file_path_compare (base_path, path) == 0)
917             {
918               label_text = gtk_file_system_volume_get_display_name (fs, volume);
919               pixbuf = gtk_file_system_volume_render_icon (fs, volume,
920                                                            GTK_WIDGET (button),
921                                                            priv->icon_size,
922                                                            NULL);
923             }
924
925           if (base_path)
926             gtk_file_path_free (base_path);
927
928           gtk_file_system_volume_free (fs, volume);
929
930           if (label_text)
931             goto out;
932         }
933
934       if (!pixbuf)
935         pixbuf = gtk_file_system_render_icon (fs, path, GTK_WIDGET (button),
936                                               priv->icon_size, NULL);
937
938       parent_path = NULL;
939       gtk_file_system_get_parent (fs, path, &parent_path, NULL);
940
941       folder = gtk_file_system_get_folder (fs, parent_path ? parent_path : path,
942                                            GTK_FILE_INFO_DISPLAY_NAME, NULL);
943       if (folder)
944         {
945           GtkFileInfo *info;
946
947           info = gtk_file_folder_get_info (folder, path, NULL);
948           g_object_unref (folder);
949
950           if (info)
951             {
952               label_text = g_strdup (gtk_file_info_get_display_name (info));
953               gtk_file_info_free (info);
954             }
955         }
956
957      out:
958       gtk_file_paths_free (paths);
959     }
960
961   if (label_text)
962     {
963       gtk_label_set_text (GTK_LABEL (priv->label), label_text);
964       g_free (label_text);
965     }
966   else
967     gtk_label_set_text (GTK_LABEL (priv->label), _(DEFAULT_FILENAME));
968   
969   if (!pixbuf)
970     pixbuf = gtk_icon_theme_load_icon (get_icon_theme (GTK_WIDGET (button)),
971                                        FALLBACK_ICON_NAME,
972                                        priv->icon_size, 0, NULL);
973
974   gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), pixbuf);
975   if (pixbuf)
976     g_object_unref (pixbuf);
977 }
978
979 /* ************************ *
980  *  Child-Widget Callbacks  *
981  * ************************ */
982
983 static void
984 dialog_current_folder_changed_cb (GtkFileChooser *dialog,
985                                   gpointer        user_data)
986 {
987   g_signal_emit_by_name (user_data, "current-folder-changed");
988 }
989
990 static void
991 dialog_file_activated_cb (GtkFileChooser *dialog,
992                           gpointer        user_data)
993 {
994   g_signal_emit_by_name (user_data, "file-activated");
995 }
996
997 static void
998 dialog_selection_changed_cb (GtkFileChooser *dialog,
999                              gpointer        user_data)
1000 {
1001   update_label_and_image (user_data);
1002   g_signal_emit_by_name (user_data, "selection-changed");
1003 }
1004
1005 static void
1006 dialog_update_preview_cb (GtkFileChooser *dialog,
1007                           gpointer        user_data)
1008 {
1009   g_signal_emit_by_name (user_data, "update-preview");
1010 }
1011
1012 static void
1013 dialog_notify_cb (GObject    *dialog,
1014                   GParamSpec *pspec,
1015                   gpointer    user_data)
1016 {
1017   gpointer iface;
1018
1019   iface = g_type_interface_peek (g_type_class_peek (G_OBJECT_TYPE (dialog)),
1020                                  GTK_TYPE_FILE_CHOOSER);
1021   if (g_object_interface_find_property (iface, pspec->name))
1022     g_object_notify (user_data, pspec->name);
1023 }
1024
1025 static gboolean
1026 dialog_delete_event_cb (GtkWidget *dialog,
1027                         GdkEvent  *event,
1028                         gpointer   user_data)
1029 {
1030   g_signal_emit_by_name (dialog, "response", GTK_RESPONSE_DELETE_EVENT);
1031
1032   return TRUE;
1033 }
1034
1035 static void
1036 dialog_response_cb (GtkDialog *dialog,
1037                     gint       response,
1038                     gpointer   user_data)
1039 {
1040   GtkFileChooserButtonPrivate *priv;
1041
1042   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1043
1044   if (response == GTK_RESPONSE_ACCEPT)
1045     {
1046       g_signal_emit_by_name (user_data, "current-folder-changed");
1047       g_signal_emit_by_name (user_data, "selection-changed");
1048     }
1049   else if (priv->old_path)
1050     {
1051       switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dialog)))
1052         {
1053         case GTK_FILE_CHOOSER_ACTION_OPEN:
1054           _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (dialog), priv->old_path,
1055                                          NULL);
1056           break;
1057         case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1058           _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (dialog),
1059                                                      priv->old_path, NULL);
1060           break;
1061         default:
1062           g_assert_not_reached ();
1063           break;
1064         }
1065     }
1066
1067   if (priv->old_path)
1068     {
1069       gtk_file_path_free (priv->old_path);
1070       priv->old_path = NULL;
1071     }
1072
1073   update_label_and_image (user_data);
1074
1075   g_signal_handler_unblock (priv->dialog,
1076                             priv->dialog_folder_changed_id);
1077   g_signal_handler_unblock (priv->dialog,
1078                             priv->dialog_file_activated_id);
1079   g_signal_handler_unblock (priv->dialog,
1080                             priv->dialog_selection_changed_id);
1081   priv->active = FALSE;
1082   gtk_widget_hide (priv->dialog);
1083 }
1084
1085
1086 static void
1087 button_clicked_cb (GtkButton *real_button,
1088                    gpointer   user_data)
1089 {
1090   GtkFileChooserButtonPrivate *priv;
1091
1092   priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
1093
1094   if (!priv->active)
1095     {
1096       GSList *paths;
1097
1098       /* Setup the dialog parent to be chooser button's toplevel, and be modal
1099          as needed. */
1100       if (!GTK_WIDGET_VISIBLE (priv->dialog))
1101         {
1102           GtkWidget *toplevel;
1103
1104           toplevel = gtk_widget_get_toplevel (user_data);
1105
1106           if (GTK_WIDGET_TOPLEVEL (toplevel) && GTK_IS_WINDOW (toplevel))
1107             {
1108               if (GTK_WINDOW (toplevel) != gtk_window_get_transient_for (GTK_WINDOW (priv->dialog)))
1109                 gtk_window_set_transient_for (GTK_WINDOW (priv->dialog),
1110                                               GTK_WINDOW (toplevel));
1111               
1112               gtk_window_set_modal (GTK_WINDOW (priv->dialog),
1113                                     gtk_window_get_modal (GTK_WINDOW (toplevel)));
1114             }
1115         }
1116
1117       g_signal_handler_block (priv->dialog,
1118                               priv->dialog_folder_changed_id);
1119       g_signal_handler_block (priv->dialog,
1120                               priv->dialog_file_activated_id);
1121       g_signal_handler_block (priv->dialog,
1122                               priv->dialog_selection_changed_id);
1123       paths = _gtk_file_chooser_get_paths (GTK_FILE_CHOOSER (priv->dialog));
1124       if (paths)
1125         {
1126           if (paths->data)
1127             priv->old_path = gtk_file_path_copy (paths->data);
1128
1129           gtk_file_paths_free (paths);
1130         }
1131
1132       priv->active = TRUE;
1133     }
1134
1135   gtk_window_present (GTK_WINDOW (priv->dialog));
1136 }