Re: Notes from GTK+ BOF



On Tue, 2004-07-13 at 14:51 -0400, Matthias Clasen wrote:
> Ok, upon looking closer, I found a couple more things:
> 
> - The button constructs a file chooser dialog and keeps it around,
>   which could be a little expensive. The reason for doing it that
>   way is probably to simply delegate all the GtkFileChooser 
>   functions to the dialog. 
> - You don't actually delegate them, though, since you're missing
>   a _gtk_file_chooser_set_delegate() call.

It actually does do the delegating, just manually so it doesn't emit the
"selection-changed" signal until the user actually hits OK.

> - While you try to shorten pathnames by recognizing $HOME and 
>   folding it to ~, the names can still easily be too long for
>   display in the entry/button. This may be acceptable for the 
>   entry, since you can at least scroll it to see the basename,
>   that is not possible for the label inside the button. We really
>   need to fix this, otherwise the file picker will be basically
>   useless in OPEN or SELECT_FOLDER mode.
> 
> Matthias
> 

There's a patch to implement an "ellipsize" property on GtkLabel at

I'm attaching a fixed-up gtkfilechooserbutton.{c,h} which
1.) Overrides show/hide_all(),
2.) Should have a filtered DnD (files->file actions, folders->folder
actions)
3.) Re-organizes the code to be GTK+ like.
4.) Ellipsizes the label at the start, but requires the patch to
GtkLabel at http://bugzilla.gnome.org/attachment.cgi?
id=29596&action=view (to add an "ellipsize" property, now that Pango
supports ellipsizing),

and a diff to integrate them (and the changes they require) into the GTK
+ build.

-- 
Peace,

    Jim Cape
    http://esco.mine.nu
    http://ignore-your.tv

    "If even one reporter had stood up during a pre-Iraq Bush press
     conference last year and shouted, `Bullshit!' it might have made a
     difference."
        -- Matt Taibbi, New York Press

Index: gtk/Makefile.am
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/Makefile.am,v
retrieving revision 1.244
diff -u -u -r1.244 Makefile.am
--- gtk/Makefile.am	16 Jul 2004 20:27:39 -0000	1.244
+++ gtk/Makefile.am	17 Jul 2004 02:56:16 -0000
@@ -148,6 +148,7 @@
 	gtkeventbox.h		\
 	gtkexpander.h		\
 	gtkfilechooser.h        \
+	gtkfilechooserbutton.h  \
 	gtkfilechooserdialog.h  \
 	gtkfilechooserwidget.h  \
 	gtkfilefilter.h		\
@@ -355,6 +356,7 @@
 	gtkeventbox.c		\
 	gtkexpander.c		\
 	gtkfilechooser.c	\
+	gtkfilechooserbutton.c	\
 	gtkfilechooserdialog.c	\
 	gtkfilechooserembed.c	\
 	gtkfilechooserentry.c	\
@@ -718,4 +720,3 @@
 	makefile.msc.in
 
 install-data-local:
-
Index: gtk/gtk.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtk.h,v
retrieving revision 1.70
diff -u -u -r1.70 gtk.h
--- gtk/gtk.h	16 Jul 2004 20:27:39 -0000	1.70
+++ gtk/gtk.h	17 Jul 2004 02:56:17 -0000
@@ -77,6 +77,7 @@
 #include <gtk/gtkexpander.h>
 #include <gtk/gtkfilesel.h>
 #include <gtk/gtkfixed.h>
+#include <gtk/gtkfilechooserbutton.h>
 #include <gtk/gtkfilechooserdialog.h>
 #include <gtk/gtkfilechooserwidget.h>
 #include <gtk/gtkfontbutton.h>
Index: gtk/gtkfilechooserdefault.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkfilechooserdefault.c,v
retrieving revision 1.180
diff -u -u -r1.180 gtkfilechooserdefault.c
--- gtk/gtkfilechooserdefault.c	14 Jul 2004 17:08:40 -0000	1.180
+++ gtk/gtkfilechooserdefault.c	17 Jul 2004 02:56:18 -0000
@@ -5278,7 +5278,7 @@
 {
   GtkWidget *entry;
 
-  entry = _gtk_file_chooser_entry_new ();
+  entry = _gtk_file_chooser_entry_new (TRUE);
   /* Pick a good width for the entry */
   gtk_entry_set_width_chars (GTK_ENTRY (entry), 30);
   gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
Index: gtk/gtkfilechooserutils.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkfilechooserutils.c,v
retrieving revision 1.20
diff -u -u -r1.20 gtkfilechooserutils.c
--- gtk/gtkfilechooserutils.c	6 Jul 2004 04:08:32 -0000	1.20
+++ gtk/gtkfilechooserutils.c	17 Jul 2004 02:56:18 -0000
@@ -174,10 +174,22 @@
 		    G_CALLBACK (delegate_file_activated), receiver);
 }
 
+GQuark
+_gtk_file_chooser_delegate_get_quark (void)
+{
+  static GQuark quark = 0;
+
+  if (G_UNLIKELY (quark == 0))
+    quark = g_quark_from_static_string ("gtk-file-chooser-delegate");
+  
+  return quark;
+}
+
 static GtkFileChooser *
 get_delegate (GtkFileChooser *receiver)
 {
-  return g_object_get_data (G_OBJECT (receiver), "gtk-file-chooser-delegate");
+  return g_object_get_qdata (G_OBJECT (receiver),
+			     GTK_FILE_CHOOSER_DELEGATE_QUARK);
 }
 
 static gboolean
Index: gtk/gtkfilechooserutils.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkfilechooserutils.h,v
retrieving revision 1.8
diff -u -u -r1.8 gtkfilechooserutils.h
--- gtk/gtkfilechooserutils.h	29 Feb 2004 06:35:15 -0000	1.8
+++ gtk/gtkfilechooserutils.h	17 Jul 2004 02:56:18 -0000
@@ -26,6 +26,8 @@
 
 G_BEGIN_DECLS
 
+#define GTK_FILE_CHOOSER_DELEGATE_QUARK	  (_gtk_file_chooser_delegate_get_quark ())
+
 typedef enum {
   GTK_FILE_CHOOSER_PROP_FIRST                  = 0x1000,
   GTK_FILE_CHOOSER_PROP_ACTION                 = GTK_FILE_CHOOSER_PROP_FIRST,
@@ -46,6 +48,8 @@
 void _gtk_file_chooser_delegate_iface_init (GtkFileChooserIface *iface);
 void _gtk_file_chooser_set_delegate        (GtkFileChooser *receiver,
 					    GtkFileChooser *delegate);
+
+GQuark _gtk_file_chooser_delegate_get_quark (void) G_GNUC_CONST;
 
 G_END_DECLS
 
/* GTK+: gtkfilechooserbutton.h
 * 
 * Copyright (c) 2004 James M. Cape <jcape ignore-your tv>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifndef __GTK_FILE_CHOOSER_BUTTON_H__
#define __GTK_FILE_CHOOSER_BUTTON_H__ 1

#include "gtkhbox.h"
#include "gtkfilechooser.h"

G_BEGIN_DECLS


#define GTK_TYPE_FILE_CHOOSER_BUTTON \
  (gtk_file_chooser_button_get_type ())
#define GTK_FILE_CHOOSER_BUTTON(object) \
  (G_TYPE_CHECK_INSTANCE_CAST ((object), GTK_TYPE_FILE_CHOOSER_BUTTON, GtkFileChooserButton))
#define GTK_FILE_CHOOSER_BUTTON_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_CHOOSER_BUTTON, GtkFileChooserButtonClass))
#define GTK_IS_FILE_CHOOSER_BUTTON(object) \
  (G_TYPE_CHECK_INSTANCE_TYPE ((object), GTK_TYPE_FILE_CHOOSER_BUTTON))
#define GTK_IS_FILE_CHOOSER_BUTTON_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_CHOOSER_BUTTON))
#define GTK_FILE_CHOOSER_BUTTON_GET_CLASS(object) \
  (G_TYPE_INSTANCE_GET_CLASS ((object), GTK_TYPE_FILE_CHOOSER_BUTTON, GtkFileChooserButtonClass))


typedef struct _GtkFileChooserButton GtkFileChooserButton;
typedef struct _GtkFileChooserButtonPrivate GtkFileChooserButtonPrivate;
typedef struct _GtkFileChooserButtonClass GtkFileChooserButtonClass;

struct _GtkFileChooserButton
{
  /* <private> */
  GtkHBox parent;

  GtkFileChooserButtonPrivate *priv;
};

struct _GtkFileChooserButtonClass
{
  /* <private> */
  GtkHBoxClass parent_class;

  void (*__gtk_reserved1);
  void (*__gtk_reserved2);
  void (*__gtk_reserved3);
  void (*__gtk_reserved4);
  void (*__gtk_reserved5);
  void (*__gtk_reserved6);
  void (*__gtk_reserved7);
  void (*__gtk_reserved8);
};


GType gtk_file_chooser_button_get_type (void) G_GNUC_CONST;

GtkWidget *gtk_file_chooser_button_new               (const gchar *title);
GtkWidget *gtk_file_chooser_button_new_with_backend  (const gchar *title,
						      const gchar *file_system);

G_CONST_RETURN gchar *gtk_file_chooser_button_get_title  (GtkFileChooserButton *button);
void		      gtk_file_chooser_button_set_title  (GtkFileChooserButton *button,
							  const gchar          *title);
gboolean	      gtk_file_chooser_button_get_active (GtkFileChooserButton *button);
void		      gtk_file_chooser_button_set_active (GtkFileChooserButton *button,
							  gboolean              is_active);


G_END_DECLS

#endif /* !__GTK_FILE_CHOOSER_BUTTON_H__ */
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 2 -*- */

/* GTK+: gtkfilechooserbutton.c
 * 
 * Copyright (c) 2004 James M. Cape <jcape ignore-your tv>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <string.h>

#include "gtkintl.h"
#include "gtkdnd.h"
#include "gtkentry.h"
#include "gtkhbox.h"
#include "gtkicontheme.h"
#include "gtkimage.h"
#include "gtklabel.h"
#include "gtkstock.h"
#include "gtktogglebutton.h"
#include "gtkvseparator.h"
#include "gtkfilechooserdialog.h"
#include "gtkfilechooserentry.h"
#include "gtkfilechooserprivate.h"
#include "gtkfilechooserutils.h"

#include "gtkfilechooserbutton.h"


/* **************** *
 *  Private Macros  *
 * **************** */

#define GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE(object) (GTK_FILE_CHOOSER_BUTTON ((object))->priv)

#define DEFAULT_TITLE		N_("Select a File")
#define DEFAULT_FILENAME	N_("(None)")
#define DEFAULT_SPACING		0


/* ********************** *
 *  Private Enumerations  *
 * ********************** */

/* Property IDs */
enum
{
  PROP_0,

  PROP_TITLE,
  PROP_ACTIVE
};


/* ******************** *
 *  Private Structures  *
 * ******************** */

struct _GtkFileChooserButtonPrivate
{
  GtkWidget *dialog;
  GtkWidget *entry;
  GtkWidget *label;
  GtkWidget *separator;
  GtkWidget *button;

  gchar *filesystem;
  gulong entry_changed_id;
  gulong dialog_file_activated_id;
  gulong dialog_folder_changed_id;
  gulong dialog_selection_changed_id;
  guint update_id;
};


/* ************* *
 *  DnD Support  *
 * ************* */

enum
{
  TEXT_URI_LIST,
  TEXT_PLAIN
};

static const GtkTargetEntry drop_targets[] = {
  { "text/uri-list", 0, TEXT_URI_LIST },
  { "text/plain", 0, TEXT_PLAIN }
};


/* ********************* *
 *  Function Prototypes  *
 * ********************* */

/* GObject Functions */
static GObject *gtk_file_chooser_button_constructor (GType                  type,
						     guint                  n_params,
						     GObjectConstructParam *params);
static void gtk_file_chooser_button_set_property    (GObject      *object,
						     guint         id,
						     const GValue *value,
						     GParamSpec   *pspec);
static void gtk_file_chooser_button_get_property    (GObject    *object,
						     guint       id,
						     GValue     *value,
						     GParamSpec *pspec);

/* GtkObject Functions */
static void gtk_file_chooser_button_destroy (GtkObject *object);

/* GtkWidget Functions */
static void gtk_file_chooser_button_drag_data_received (GtkWidget        *widget,
						        GdkDragContext   *context,
						        gint	          x,
							gint              y,
						        GtkSelectionData *data,
							guint             info,
						        guint             drag_time);
static void gtk_file_chooser_button_show_all           (GtkWidget        *widget);
static void gtk_file_chooser_button_hide_all           (GtkWidget        *widget);

/* Child Widget Callbacks */
static void     dialog_update_preview_cb         (GtkFileChooser *dialog,
						  gpointer        user_data);
static void     dialog_selection_changed_cb      (GtkFileChooser *dialog,
						  gpointer        user_data);
static void     dialog_file_activated_cb         (GtkFileChooser *dialog,
						  gpointer        user_data);
static void     dialog_current_folder_changed_cb (GtkFileChooser *dialog,
						  gpointer        user_data);
static void     dialog_notify_cb                 (GObject        *dialog,
						  GParamSpec     *pspec,
						  gpointer        user_data);
static gboolean dialog_delete_event_cb           (GtkWidget      *dialog,
						  GdkEvent       *event,
						  gpointer        user_data);
static void     dialog_response_cb               (GtkFileChooser *dialog,
						  gint response,
						  gpointer user_data);

static void button_toggled_cb       (GtkToggleButton *real_button,
				     gpointer         user_data);
static void button_notify_active_cb (GObject         *real_button,
				     GParamSpec      *pspec,
				     gpointer         user_data);

static void entry_size_allocate_cb (GtkWidget     *entry,
				    GtkAllocation *allocation,
				    gpointer       user_data);
static void entry_changed_cb       (GtkEditable   *chooser_entry,
				    gpointer       user_data);

/* Utility Functions */
static gboolean update_dialog (gpointer              user_data);
static void     update_entry  (GtkFileChooserButton *button);


/* ******************* *
 *  GType Declaration  *
 * ******************* */

G_DEFINE_TYPE_WITH_CODE (GtkFileChooserButton, gtk_file_chooser_button, GTK_TYPE_HBOX, { \
    G_IMPLEMENT_INTERFACE (GTK_TYPE_FILE_CHOOSER, _gtk_file_chooser_delegate_iface_init) \
});


/* ***************** *
 *  GType Functions  *
 * ***************** */

static void
gtk_file_chooser_button_class_init (GtkFileChooserButtonClass * class)
{
  GObjectClass *gobject_class;
  GtkObjectClass *gtkobject_class;
  GtkWidgetClass *widget_class;

  gobject_class = G_OBJECT_CLASS (class);
  gtkobject_class = GTK_OBJECT_CLASS (class);
  widget_class = GTK_WIDGET_CLASS (class);

  gobject_class->constructor = gtk_file_chooser_button_constructor;
  gobject_class->set_property = gtk_file_chooser_button_set_property;
  gobject_class->get_property = gtk_file_chooser_button_get_property;

  gtkobject_class->destroy = gtk_file_chooser_button_destroy;

  widget_class->drag_data_received = gtk_file_chooser_button_drag_data_received;
  widget_class->show_all = gtk_file_chooser_button_show_all;
  widget_class->hide_all = gtk_file_chooser_button_hide_all;

  g_object_class_install_property (gobject_class, PROP_TITLE,
				   g_param_spec_string ("title", "Title",
							"The title of the file chooser dialog.",
							_(DEFAULT_TITLE), G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class, PROP_ACTIVE,
				   g_param_spec_boolean ("active", "Active",
							 "Whether the browse dialog is visible or "
							 "not.", FALSE, G_PARAM_READWRITE));

  _gtk_file_chooser_install_properties (gobject_class);

  g_type_class_add_private (class, sizeof (GtkFileChooserButtonPrivate));
}


static void
gtk_file_chooser_button_init (GtkFileChooserButton *button)
{
  button->priv = G_TYPE_INSTANCE_GET_PRIVATE (button, GTK_TYPE_FILE_CHOOSER_BUTTON,
					      GtkFileChooserButtonPrivate);
}


/* ******************* *
 *  GObject Functions  *
 * ******************* */

static GObject *
gtk_file_chooser_button_constructor (GType                  type,
				     guint                  n_params,
				     GObjectConstructParam *params)
{
  GObject *object;
  GtkFileChooserButtonPrivate *priv;
  GtkFilePath *path;
  GtkWidget *box, *image;

  object =
    (*G_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->constructor) (type,
									   n_params,
									   params);
  gtk_box_set_spacing (GTK_BOX (object), DEFAULT_SPACING);
  priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);

  gtk_widget_push_composite_child ();

  priv->dialog = g_object_new (GTK_TYPE_FILE_CHOOSER_DIALOG,
			       "file-system-backend", priv->filesystem, NULL);
  gtk_dialog_add_button (GTK_DIALOG (priv->dialog),
			 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT);
  gtk_dialog_add_button (GTK_DIALOG (priv->dialog),
			 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT);
  g_signal_connect (priv->dialog, "delete-event",
		    G_CALLBACK (dialog_delete_event_cb), object);
  g_signal_connect (priv->dialog, "response",
		    G_CALLBACK (dialog_response_cb), object);

  /* This is used, instead of the standard delegate, to ensure that signals are only
   * delegated when the OK button is pressed. */
  g_object_set_qdata (object, GTK_FILE_CHOOSER_DELEGATE_QUARK, priv->dialog);
  priv->dialog_folder_changed_id =
    g_signal_connect (priv->dialog, "current-folder-changed",
		      G_CALLBACK (dialog_current_folder_changed_cb), object);
  priv->dialog_file_activated_id =
    g_signal_connect (priv->dialog, "file-activated",
		      G_CALLBACK (dialog_file_activated_cb), object);
  priv->dialog_selection_changed_id =
    g_signal_connect (priv->dialog, "selection-changed",
		      G_CALLBACK (dialog_selection_changed_cb), object);
  g_signal_connect (priv->dialog, "update-preview",
		    G_CALLBACK (dialog_update_preview_cb), object);
  g_signal_connect (priv->dialog, "notify",
		    G_CALLBACK (dialog_notify_cb), object);
  g_object_add_weak_pointer (G_OBJECT (priv->dialog),
			     (gpointer *) (&priv->dialog));

  g_free (priv->filesystem);
  priv->filesystem = NULL;

  priv->entry = _gtk_file_chooser_entry_new (FALSE);
  _gtk_file_chooser_entry_set_file_system (GTK_FILE_CHOOSER_ENTRY (priv->entry),
					   _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog)));
  path = gtk_file_path_new_steal ("file:///");
  _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (priv->entry), path);
  priv->entry_changed_id = g_signal_connect_after (priv->entry, "changed",
						   G_CALLBACK (entry_changed_cb), object);
  gtk_container_add (GTK_CONTAINER (object), priv->entry);

  priv->button = gtk_toggle_button_new ();
  g_signal_connect (priv->button, "toggled",
		    G_CALLBACK (button_toggled_cb), object);
  g_signal_connect (priv->button, "notify::active",
		    G_CALLBACK (button_notify_active_cb), object);
  g_signal_connect (priv->entry, "size-allocate",
		    G_CALLBACK (entry_size_allocate_cb), priv->button);
  gtk_box_pack_start (GTK_BOX (object), priv->button, TRUE, TRUE, 0);
  gtk_widget_show (priv->button);

  box = gtk_hbox_new (FALSE, 4);
  gtk_container_add (GTK_CONTAINER (priv->button), box);
  gtk_widget_show (box);

  priv->label = gtk_label_new (_(DEFAULT_FILENAME));
  gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_START);
  gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5);
  gtk_box_pack_start (GTK_BOX (box), priv->label, TRUE, TRUE, 2);
  gtk_widget_show (priv->label);

  image = gtk_image_new_from_stock (GTK_STOCK_OPEN,
				    GTK_ICON_SIZE_SMALL_TOOLBAR);
  gtk_box_pack_end (GTK_BOX (box), image, FALSE, FALSE, 0);
  gtk_widget_show (image);

  priv->separator = gtk_vseparator_new ();
  gtk_box_pack_end (GTK_BOX (box), priv->separator, FALSE, FALSE, 0);
  gtk_widget_show (priv->separator);

  gtk_widget_pop_composite_child ();

  /* DnD */
  gtk_drag_dest_unset (priv->entry);
  gtk_drag_dest_set (GTK_WIDGET (object),
                     (GTK_DEST_DEFAULT_MOTION |
                      GTK_DEST_DEFAULT_HIGHLIGHT |
                      GTK_DEST_DEFAULT_DROP),
		     drop_targets, G_N_ELEMENTS (drop_targets),
		     GDK_ACTION_COPY);

  return object;
}


static void
gtk_file_chooser_button_set_property (GObject      *object,
				      guint         id,
				      const GValue *value,
				      GParamSpec   *pspec)
{
  GtkFileChooserButtonPrivate *priv;

  priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);

  switch (id)
    {
    case PROP_ACTIVE:
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button),
				    g_value_get_boolean (value));
      break;

    case GTK_FILE_CHOOSER_PROP_ACTION:
      g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);

      switch (g_value_get_enum (value))
	{
	case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
	  gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
	  /* Fall through to set the widget states */
	case GTK_FILE_CHOOSER_ACTION_OPEN:
	  gtk_widget_hide (priv->entry);
	  gtk_widget_show (priv->label);
	  gtk_widget_show (priv->separator);
	  gtk_box_set_child_packing (GTK_BOX (object), priv->button,
				     TRUE, TRUE, 0, GTK_PACK_START);
	  break;

	case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
	  gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
	  /* Fall through to set the widget states */
	case GTK_FILE_CHOOSER_ACTION_SAVE:
	  gtk_widget_show (priv->entry);
	  gtk_widget_hide (priv->label);
	  gtk_widget_hide (priv->separator);
	  gtk_box_set_child_packing (GTK_BOX (object), priv->button,
				     FALSE, FALSE, 0, GTK_PACK_START);
	  break;
	}
      break;

    case PROP_TITLE:
    case GTK_FILE_CHOOSER_PROP_FILTER:
    case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
    case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
    case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
    case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
    case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
    case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
      g_object_set_property (G_OBJECT (priv->dialog), pspec->name, value);
      break;

    case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
      /* Construct-only */
      priv->filesystem = g_value_dup_string (value);
      break;

    case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
      g_warning ("%s: Choosers of type `%s` do not support selecting multiple files.",
		 G_STRFUNC, G_OBJECT_TYPE_NAME (object));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec);
      break;
    }
}


static void
gtk_file_chooser_button_get_property (GObject    *object,
				      guint       id,
				      GValue     *value,
				      GParamSpec *pspec)
{
  GtkFileChooserButtonPrivate *priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);

  switch (id)
    {
    case PROP_ACTIVE:
      g_value_set_boolean (value, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->button)));
      break;

    case PROP_TITLE:
    case GTK_FILE_CHOOSER_PROP_ACTION:
    case GTK_FILE_CHOOSER_PROP_FILE_SYSTEM_BACKEND:
    case GTK_FILE_CHOOSER_PROP_FILTER:
    case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY:
    case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET:
    case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET_ACTIVE:
    case GTK_FILE_CHOOSER_PROP_USE_PREVIEW_LABEL:
    case GTK_FILE_CHOOSER_PROP_EXTRA_WIDGET:
    case GTK_FILE_CHOOSER_PROP_SELECT_MULTIPLE:
    case GTK_FILE_CHOOSER_PROP_SHOW_HIDDEN:
      g_object_get_property (G_OBJECT (priv->dialog), pspec->name, value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec);
      break;
    }
}


/* ********************* *
 *  GtkObject Functions  *
 * ********************* */

static void
gtk_file_chooser_button_destroy (GtkObject * object)
{
  GtkFileChooserButtonPrivate *priv;

  priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (object);

  if (priv->dialog != NULL)
    gtk_widget_destroy (priv->dialog);
  
  if (priv->update_id != 0)
    g_source_remove (priv->update_id);

  if (GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy != NULL)
    (*GTK_OBJECT_CLASS (gtk_file_chooser_button_parent_class)->destroy) (object);
}


/* ********************* *
 *  GtkWidget Functions  *
 * ********************* */

static void
gtk_file_chooser_button_drag_data_received (GtkWidget	     *widget,
					    GdkDragContext   *context,
					    gint	      x,
					    gint	      y,
					    GtkSelectionData *data,
					    guint	      info,
					    guint	      drag_time)
{
  GtkFileChooserButtonPrivate *priv;

  if (GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received != NULL)
    (*GTK_WIDGET_CLASS (gtk_file_chooser_button_parent_class)->drag_data_received) (widget,
										    context,
										    x, y,
										    data, info,
										    drag_time);

  if (widget == NULL || context == NULL || data == NULL || data->length < 0)
    return;

  priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (widget);

  switch (info)
    {
    case TEXT_URI_LIST:
      {
	gchar **uris;
	guint i;
	gboolean selected;

	uris = g_strsplit (data->data, "\r\n", -1);

	if (uris == NULL)
	  break;

	selected = FALSE;
	g_signal_handler_block (priv->entry, priv->entry_changed_id);
	for (i = 0; !selected && uris[i] != NULL; i++)
	  {
	    GtkFileSystem *fs;
	    GtkFilePath *path, *base_path;

	    fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
	    path = gtk_file_system_uri_to_path (fs, uris[i]);

	    base_path = NULL;
	    if (path != NULL &&
		gtk_file_system_get_parent (fs, path, &base_path, NULL))
	      {
		GtkFileFolder *folder;
		GtkFileInfo *info;

		folder = gtk_file_system_get_folder (fs, base_path,
						     GTK_FILE_INFO_IS_FOLDER,
						     NULL);

		info = gtk_file_folder_get_info (folder, base_path, NULL);

		if (info != NULL)
		  {
		    GtkFileChooserAction action;

		    g_object_get (priv->dialog, "action", &action, NULL);
		  
		    if (((action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER ||
			  action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) &&
			 !gtk_file_info_get_is_folder (info)) ||
			((action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER ||
			  action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) &&
			 gtk_file_info_get_is_folder (info)))
		      selected = FALSE;
		    else
		      selected = _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
								path, NULL);

		    gtk_file_info_free (info);
		  }
		else
		  selected = FALSE;

		gtk_file_path_free (base_path);
	      }

	    gtk_file_path_free (path);
	  }
	g_signal_handler_unblock (priv->entry, priv->entry_changed_id);

	g_strfreev (uris);
      }
      break;

    case TEXT_PLAIN:
      gtk_entry_set_text (GTK_ENTRY (priv->entry), data->data);
      break;
    }

  gtk_drag_finish (context, FALSE, FALSE, drag_time);
}


static void
gtk_file_chooser_button_show_all (GtkWidget *widget)
{
  gtk_widget_show (widget);
}


static void
gtk_file_chooser_button_hide_all (GtkWidget *widget)
{
  gtk_widget_hide (widget);
}


/* ************************************************************************** *
 *  Public API                                                                *
 * ************************************************************************** */

/**
 * gtk_file_chooser_button_new:
 * @title: the title of the browse dialog.
 * 
 * Creates a new file-selecting button widget.
 * 
 * Returns: a new button widget.
 * 
 * Since: 2.6
 **/
GtkWidget *
gtk_file_chooser_button_new (const gchar *title)
{
  return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON, "title", title, NULL);
}

/**
 * gtk_file_chooser_button_new_with_backend:
 * @title: the title of the browse dialog.
 * @backend: the name of the #GtkFileSystem backend to use.
 * 
 * Creates a new file-selecting button widget using @backend.
 * 
 * Returns: a new button widget.
 * 
 * Since: 2.6
 **/
GtkWidget *
gtk_file_chooser_button_new_with_backend (const gchar *title,
					  const gchar *backend)
{
  return g_object_new (GTK_TYPE_FILE_CHOOSER_BUTTON, "title", title,
		       "file-system-backend", backend, NULL);
}


/**
 * gtk_file_chooser_button_set_title:
 * @button: the button widget to modify.
 * @title: the new browse dialog title.
 * 
 * Modifies the @title of the browse dialog used by @button.
 * 
 * Since: 2.6
 **/
void
gtk_file_chooser_button_set_title (GtkFileChooserButton *button,
				    const gchar           *title)
{
  g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));

  gtk_window_set_title (GTK_WINDOW (button->priv->dialog), title);
  g_object_notify (G_OBJECT (button), "title");
}


/**
 * gtk_file_chooser_button_get_title:
 * @button: the button widget to examine.
 * 
 * Retrieves the title of the browse dialog used by @button. The returned value
 * should not be modified or freed.
 * 
 * Returns: a pointer to the browse dialog's title.
 * 
 * Since: 2.6
 **/
G_CONST_RETURN gchar *
gtk_file_chooser_button_get_title (GtkFileChooserButton * button)
{
  g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), NULL);

  return gtk_window_get_title (GTK_WINDOW (button->priv->dialog));
}


/**
 * gtk_file_chooser_button_set_active:
 * @button: the button widget to modify.
 * @is_active: whether or not the dialog is visible.
 * 
 * Modifies whether or not the dialog attached to @button is visible or not.
 * 
 * Since: 2.6
 **/
void
gtk_file_chooser_button_set_active (GtkFileChooserButton *button,
				     gboolean		    is_active)
{
  g_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));

  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button->priv->button), is_active);
}


/**
 * gtk_file_chooser_button_get_active:
 * @button: the button widget to examine.
 * 
 * Retrieves whether or not the dialog attached to @button is visible.
 * 
 * Returns: a boolean whether the dialog is visible or not.
 * 
 * Since: 2.6
 **/
gboolean
gtk_file_chooser_button_get_active (GtkFileChooserButton * button)
{
  g_return_val_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button), FALSE);

  return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button->priv->button));
}


/* ******************* *
 *  Utility Functions  *
 * ******************* */

static gchar *
get_display_name (gchar *filename)
{
  const gchar *home_dir;
  gchar *tmp;
  gsize filename_len, home_dir_len;

  filename_len = strlen (filename);

  if (g_file_test (filename, G_FILE_TEST_IS_DIR))
    {
      tmp = g_new (gchar, filename_len + 2);
      strcpy (tmp, filename);
      tmp[filename_len] = '/';
      tmp[filename_len + 1] = '\0';
      g_free (filename);
      filename = tmp;
    }

  home_dir = g_get_home_dir ();
  if (home_dir != NULL)
    {
      home_dir_len = strlen (home_dir);

      if (strncmp (home_dir, filename, home_dir_len) == 0)
	{
	  tmp = g_build_filename ("~", filename + home_dir_len, NULL);
	  g_free (filename);
	  filename = tmp;
	}
    }

  return filename;
}


static void
update_entry (GtkFileChooserButton *button)
{
  gchar *filename;

  switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (button->priv->dialog)))
    {
    case GTK_FILE_CHOOSER_ACTION_OPEN:
    case GTK_FILE_CHOOSER_ACTION_SAVE:
      filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (button->priv->dialog));
      break;
    case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
    case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
      filename = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (button->priv->dialog));
      break;
    default:
      g_assert_not_reached ();
      filename = NULL;
      break;
    }

  if (filename != NULL)
    filename = get_display_name (filename);

  g_signal_handler_block (button->priv->entry, button->priv->entry_changed_id);
  if (filename != NULL)
    gtk_entry_set_text (GTK_ENTRY (button->priv->entry), filename);
  else
    gtk_entry_set_text (GTK_ENTRY (button->priv->entry), "");
  g_signal_handler_unblock (button->priv->entry, button->priv->entry_changed_id);

  if (filename != NULL)
    gtk_label_set_text (GTK_LABEL (button->priv->label), filename);
  else
    gtk_label_set_text (GTK_LABEL (button->priv->label), _(DEFAULT_FILENAME));
  g_free (filename);
}


static gboolean
update_dialog (gpointer user_data)
{
  GtkFileChooserButtonPrivate *priv;
  const GtkFilePath *folder_path;
  const gchar *file_part;
  gchar *full_uri;

  priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);
  folder_path =
    _gtk_file_chooser_entry_get_current_folder (GTK_FILE_CHOOSER_ENTRY (priv->entry));
  file_part =
    _gtk_file_chooser_entry_get_file_part (GTK_FILE_CHOOSER_ENTRY (priv->entry));

  if (folder_path != NULL)
    full_uri = g_build_filename (gtk_file_path_get_string (folder_path),
				 file_part, NULL);
  else if (file_part != NULL)
    full_uri = g_build_filename ("file://", file_part, NULL);
  else
    full_uri = NULL;

  if (full_uri != NULL)
    {
      gchar *display_name;

      display_name = g_filename_from_uri (full_uri, NULL, NULL);
      display_name = get_display_name (display_name);
      gtk_label_set_text (GTK_LABEL (priv->label), display_name);
      g_free (display_name);
    }
  else
    {
      gtk_label_set_text (GTK_LABEL (priv->label), _(DEFAULT_FILENAME));
    }

  switch (gtk_file_chooser_get_action (GTK_FILE_CHOOSER (priv->dialog)))
    {
    case GTK_FILE_CHOOSER_ACTION_OPEN:
      if (folder_path != NULL)
	{
	  GtkFileSystem *fs;
	  GtkFileFolder *folder;
	  GtkFilePath *full_path;
	  GtkFileInfo *info;

	  fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
	  folder = gtk_file_system_get_folder (fs, folder_path,
					       GTK_FILE_INFO_IS_FOLDER, NULL);

	  full_path = gtk_file_system_make_path (fs, folder_path, file_part, NULL);
	  info = gtk_file_folder_get_info (folder, full_path, NULL);

	  /* Entry contents don't exist. */
	  if (info == NULL)
	    _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
						       folder_path, NULL);
	  /* Entry contents are a folder */
	  else if (gtk_file_info_get_is_folder (info))
	    _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
						       full_path, NULL);
	  /* Entry contents must be a file. */
	  else
	    _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
					   full_path, NULL);

	  gtk_file_info_free (info);
	  gtk_file_path_free (full_path);
	}
      else
	g_free (full_uri);
      break;
    case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
      if (folder_path != NULL)
	{
	  GtkFileSystem *fs;
	  GtkFilePath *full_path;

	  fs = _gtk_file_chooser_get_file_system (GTK_FILE_CHOOSER (priv->dialog));
	  full_path = gtk_file_system_make_path (fs, folder_path, file_part, NULL);

	  /* Entry contents don't exist. */
	  if (full_path != NULL)
	    _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (priv->dialog),
					   full_path, NULL);
	  else
	    _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
						       folder_path, NULL);

	  gtk_file_path_free (full_path);
	}
      else
	g_free (full_uri);
      break;

    case GTK_FILE_CHOOSER_ACTION_SAVE:
    case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
      if (folder_path != NULL)
	_gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (priv->dialog),
						   folder_path, NULL);

      gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (priv->dialog),
					 file_part);
      g_free (full_uri);
      break;
    }
  
  priv->update_id = 0;
  return FALSE;
}

/* ************************ *
 *  Child-Widget Callbacks  *
 * ************************ */

static void
dialog_current_folder_changed_cb (GtkFileChooser *dialog,
				  gpointer        user_data)
{
  g_signal_emit_by_name (user_data, "current-folder-changed");
}


static void
dialog_file_activated_cb (GtkFileChooser *dialog,
			  gpointer        user_data)
{
  g_signal_emit_by_name (user_data, "file-activated");
}


static void
dialog_selection_changed_cb (GtkFileChooser *dialog,
			     gpointer        user_data)
{
  update_entry (user_data);
  g_signal_emit_by_name (user_data, "selection-changed");
}


static void
dialog_update_preview_cb (GtkFileChooser *dialog,
			  gpointer        user_data)
{
  g_signal_emit_by_name (user_data, "update-preview");
}


static void
dialog_notify_cb (GObject    *dialog,
		  GParamSpec *pspec,
		  gpointer    user_data)
{
  gpointer iface;

  iface = g_type_interface_peek (g_type_class_peek (G_OBJECT_TYPE (dialog)),
				 GTK_TYPE_FILE_CHOOSER);
  if (g_object_interface_find_property (iface, pspec->name))
    g_object_notify (user_data, pspec->name);
}


static gboolean
dialog_delete_event_cb (GtkWidget *dialog,
			GdkEvent  *event,
		        gpointer   user_data)
{
  g_signal_emit_by_name (dialog, "response", GTK_RESPONSE_DELETE_EVENT);

  return TRUE;
}


static void
dialog_response_cb (GtkFileChooser *dialog,
		    gint            response,
		    gpointer        user_data)
{
  GtkFileChooserButtonPrivate *priv;

  priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);

  if (response == GTK_RESPONSE_ACCEPT)
    {
      update_entry (user_data);

      g_signal_emit_by_name (user_data, "current-folder-changed");
      g_signal_emit_by_name (user_data, "selection-changed");
    }
  else
    {
      update_dialog (user_data);
    }

  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), FALSE);
}


static void
button_toggled_cb (GtkToggleButton *real_button,
		   gpointer         user_data)
{
  GtkFileChooserButtonPrivate *priv;

  priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);

  if (gtk_toggle_button_get_active (real_button))
    {
      /* Setup the dialog parent to be chooser button's toplevel, and be modal
	 as needed. */
      if (!GTK_WIDGET_VISIBLE (priv->dialog))
	{
	  GtkWidget *toplevel;

	  toplevel = gtk_widget_get_toplevel (user_data);

	  if (GTK_WIDGET_TOPLEVEL (toplevel) && GTK_IS_WINDOW (toplevel))
	    {
	      if (GTK_WINDOW (toplevel) !=
		  gtk_window_get_transient_for (GTK_WINDOW (priv->dialog)))
		{
		  gtk_window_set_transient_for (GTK_WINDOW (priv->dialog),
						GTK_WINDOW (toplevel));
		}
	      gtk_window_set_modal (GTK_WINDOW (priv->dialog),
				    gtk_window_get_modal (GTK_WINDOW (toplevel)));
	    }
	}

      g_signal_handler_block (priv->dialog,
			      priv->dialog_folder_changed_id);
      g_signal_handler_block (priv->dialog,
			      priv->dialog_file_activated_id);
      g_signal_handler_block (priv->dialog,
			      priv->dialog_selection_changed_id);
      gtk_widget_set_sensitive (priv->entry, FALSE);
      gtk_window_present (GTK_WINDOW (priv->dialog));
    }
  else
    {
      g_signal_handler_unblock (priv->dialog,
				priv->dialog_folder_changed_id);
      g_signal_handler_unblock (priv->dialog,
				priv->dialog_file_activated_id);
      g_signal_handler_unblock (priv->dialog,
				priv->dialog_selection_changed_id);
      gtk_widget_set_sensitive (priv->entry, TRUE);
      gtk_widget_hide (priv->dialog);
    }
}


static void
button_notify_active_cb (GObject    *real_button,
			 GParamSpec *pspec,
			 gpointer    user_data)
{
  g_object_notify (user_data, "active");
}


/* Ensure the button height == entry height */
static void
entry_size_allocate_cb (GtkWidget     *entry,
			GtkAllocation *allocation,
			gpointer       user_data)
{
  gtk_widget_set_size_request (user_data, -1, allocation->height);
}


static void
entry_changed_cb (GtkEditable *chooser_entry,
		  gpointer     user_data)
{
  GtkFileChooserButtonPrivate *priv;

  priv = GTK_FILE_CHOOSER_BUTTON_GET_PRIVATE (user_data);

  /* We do this in an idle handler to avoid totally screwing up chooser_entry's
   * completion */
  if (priv->update_id != 0)
    priv->update_id = g_idle_add (update_dialog, user_data);
}

Attachment: signature.asc
Description: This is a digitally signed message part



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]