From 9a2a4c5ee028085dc0a8bc6113e501c09409153c Mon Sep 17 00:00:00 2001 From: Yevgen Muntyan <17531749+muntyan@users.noreply.github.com> Date: Fri, 5 Aug 2005 06:32:53 +0000 Subject: [PATCH] Added MooPaned and MooFileView from junk --- moo.kdevelop | 32 +- moo/mooedit/mooedit-private.h | 10 +- moo/mooedit/mooeditprefs.c | 2 +- moo/mooutils/Makefile.am | 5 + moo/mooutils/moofileview-icons.c | 319 ++++++ moo/mooutils/moofileview.c | 1179 ++++++++++++++++++++ moo/mooutils/moofileview.h | 88 ++ moo/mooutils/moomarshals.list | 1 + moo/mooutils/moopaned.c | 1719 ++++++++++++++++++++++++++++++ moo/mooutils/moopaned.h | 78 ++ tests/Makefile.am | 21 +- tests/testfileview.c | 25 + tests/testpaned.c | 116 ++ 13 files changed, 3570 insertions(+), 25 deletions(-) create mode 100644 moo/mooutils/moofileview-icons.c create mode 100644 moo/mooutils/moofileview.c create mode 100644 moo/mooutils/moofileview.h create mode 100644 moo/mooutils/moopaned.c create mode 100644 moo/mooutils/moopaned.h create mode 100644 tests/testfileview.c create mode 100644 tests/testpaned.c diff --git a/moo.kdevelop b/moo.kdevelop index cbd3376c..b785f992 100644 --- a/moo.kdevelop +++ b/moo.kdevelop @@ -24,7 +24,7 @@ . false - + C @@ -36,17 +36,17 @@ debug - tests/editor + tests/testfileview executable / - + false false - --enable-debug=full --enable-all-gcc-warnings=fatal --enable-developer-mode --disable-moo-module + --enable-debug=full --enable-all-gcc-warnings=fatal --enable-developer-mode --disable-moo-module --without-mooui --without-mooapp --without-mooterm --without-mooedit --without-python build/debug kdevgccoptions kdevgppoptions @@ -54,13 +54,13 @@ -O0 -g3 -pg -O0 -g3 -pg - - - - - - - + + + + + + + --enable-all-gcc-warnings=fatal --enable-developer-mode @@ -166,10 +166,10 @@ libtool --g-fatal-warnings --sync - - - - + + + + true false true @@ -270,7 +270,7 @@ - + set m_,_ theValue diff --git a/moo/mooedit/mooedit-private.h b/moo/mooedit/mooedit-private.h index 35bd8652..907f7e0d 100644 --- a/moo/mooedit/mooedit-private.h +++ b/moo/mooedit/mooedit-private.h @@ -56,11 +56,11 @@ int _moo_edit_extend_selection (MooEdit *edit, /* Preferences /*/ void _moo_edit_set_default_settings (void); -void _moo_edit_apply_settings (MooEdit *edit); -void _moo_edit_apply_style_settings (MooEdit *edit); -void _moo_edit_settings_changed (const char *key, - const char *newval, - MooEdit *edit); +void _moo_edit_apply_settings (MooEdit *edit); +void _moo_edit_apply_style_settings (MooEdit *edit); +void _moo_edit_settings_changed (const char *key, + const GValue *newval, + MooEdit *edit); /***********************************************************************/ /* File operations diff --git a/moo/mooedit/mooeditprefs.c b/moo/mooedit/mooeditprefs.c index 45f49277..20a46adc 100644 --- a/moo/mooedit/mooeditprefs.c +++ b/moo/mooedit/mooeditprefs.c @@ -162,7 +162,7 @@ void _moo_edit_apply_style_settings (MooEdit *edit) void _moo_edit_settings_changed (const char *key, - G_GNUC_UNUSED const char *newval, + G_GNUC_UNUSED const GValue *newval, MooEdit *edit) { GtkSourceBuffer *buffer = edit->priv->source_buffer; diff --git a/moo/mooutils/Makefile.am b/moo/mooutils/Makefile.am index 8bcb3168..3ab3252c 100644 --- a/moo/mooutils/Makefile.am +++ b/moo/mooutils/Makefile.am @@ -48,12 +48,17 @@ libmooutils_la_SOURCES = \ moodialogs.h \ moofileutils.c \ moofileutils.h \ + moofileview.c \ + moofileview.h \ + moofileview-icons.c \ moolog.c \ moolog.h \ moomarkup.c \ moomarkup.h \ mooobjectfactory.c \ mooobjectfactory.h \ + moopaned.c \ + moopaned.h \ mooparam.c \ mooparam.h \ mooprefs.c \ diff --git a/moo/mooutils/moofileview-icons.c b/moo/mooutils/moofileview-icons.c new file mode 100644 index 00000000..20b033cb --- /dev/null +++ b/moo/mooutils/moofileview-icons.c @@ -0,0 +1,319 @@ +/* + * mooutils/moofileview-icons.c + * + * Copyright (C) 2004-2005 by Yevgen Muntyan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * See COPYING file that comes with this distribution. + */ + +#include "mooutils/moofileview.h" +#include +#include +#include + + +/* Icon type, supplemented by MIME type + */ +typedef enum { + ICON_NOENT, + ICON_NONE, /* "Could not compute the icon type" */ + ICON_REGULAR, /* Use mime type for icon */ + ICON_BLOCK_DEVICE, + ICON_BROKEN_SYMBOLIC_LINK, + ICON_CHARACTER_DEVICE, + ICON_DIRECTORY, + ICON_EXECUTABLE, + ICON_FIFO, + ICON_SOCKET +} IconType; + + +typedef struct { + gint size; + GdkPixbuf *pixbuf; +} IconCacheElement; + +static void icon_cache_element_free (IconCacheElement *element) +{ + if (element->pixbuf) + g_object_unref (element->pixbuf); + g_free (element); +} + +static void icon_theme_changed (GtkIconTheme *icon_theme) +{ + GHashTable *cache; + + /* Difference from the initial creation is that we don't + * reconnect the signal */ + cache = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify)g_free, + (GDestroyNotify)icon_cache_element_free); + g_object_set_data_full (G_OBJECT (icon_theme), "gtk-file-icon-cache", + cache, (GDestroyNotify)g_hash_table_destroy); +} + + +static IconType get_icon_type_from_stat (const struct stat *statp) +{ + if (S_ISBLK (statp->st_mode)) + return ICON_BLOCK_DEVICE; + else if (S_ISLNK (statp->st_mode)) + return ICON_BROKEN_SYMBOLIC_LINK; /* See get_icon_type */ + else if (S_ISCHR (statp->st_mode)) + return ICON_CHARACTER_DEVICE; + else if (S_ISDIR (statp->st_mode)) + return ICON_DIRECTORY; +#ifdef S_ISFIFO + else if (S_ISFIFO (statp->st_mode)) + return ICON_FIFO; +#endif +#ifdef S_ISSOCK + else if (S_ISSOCK (statp->st_mode)) + return ICON_SOCKET; +#endif + else + return ICON_REGULAR; +} + + +static IconType get_icon_type (MooFileViewFile *file) +{ + const struct stat *statp = moo_file_view_file_get_stat (file); + if (statp) + return get_icon_type_from_stat (statp); + else + return ICON_NOENT; +} + + +static GdkPixbuf *get_cached_icon (GtkWidget *widget, + const gchar *name, + gint pixel_size) +{ + GtkIconTheme *icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)); + GHashTable *cache = g_object_get_data (G_OBJECT (icon_theme), "gtk-file-icon-cache"); + IconCacheElement *element; + + if (!cache) + { + cache = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify)g_free, + (GDestroyNotify)icon_cache_element_free); + + g_object_set_data_full (G_OBJECT (icon_theme), "gtk-file-icon-cache", + cache, (GDestroyNotify)g_hash_table_destroy); + g_signal_connect (icon_theme, "changed", + G_CALLBACK (icon_theme_changed), NULL); + } + + element = g_hash_table_lookup (cache, name); + if (!element) + { + element = g_new0 (IconCacheElement, 1); + g_hash_table_insert (cache, g_strdup (name), element); + } + + if (element->size != pixel_size) + { + if (element->pixbuf) + g_object_unref (element->pixbuf); + element->size = pixel_size; + element->pixbuf = gtk_icon_theme_load_icon (icon_theme, name, + pixel_size, 0, NULL); + } + + return element->pixbuf; +} + + +static GdkPixbuf *get_icon_for_mime_type (GtkWidget *widget, + const char *mime_type, + gint pixel_size) +{ + const char *separator; + GString *icon_name; + GdkPixbuf *pixbuf; + + separator = strchr (mime_type, '/'); + if (!separator) + return NULL; /* maybe we should return a GError with "invalid MIME-type" */ + + icon_name = g_string_new ("gnome-mime-"); + g_string_append_len (icon_name, mime_type, separator - mime_type); + g_string_append_c (icon_name, '-'); + g_string_append (icon_name, separator + 1); + pixbuf = get_cached_icon (widget, icon_name->str, pixel_size); + g_string_free (icon_name, TRUE); + if (pixbuf) + return pixbuf; + + icon_name = g_string_new ("gnome-mime-"); + g_string_append_len (icon_name, mime_type, separator - mime_type); + pixbuf = get_cached_icon (widget, icon_name->str, pixel_size); + g_string_free (icon_name, TRUE); + + return pixbuf; +} + + +/* Returns the name of the icon to be used for a path which is known to be a + * directory. This can vary for Home, Desktop, etc. + */ +static const char *get_icon_name_for_directory (const char *path) +{ + static char *desktop_path = NULL; + + if (!g_get_home_dir ()) + return "gnome-fs-directory"; + + if (!desktop_path) + desktop_path = g_build_filename (g_get_home_dir (), "Desktop", NULL); + + if (strcmp (g_get_home_dir (), path) == 0) + return "gnome-fs-home"; + else if (strcmp (desktop_path, path) == 0) + return "gnome-fs-desktop"; + else + return "gnome-fs-directory"; +} + + +/* Renders an icon for a non-ICON_REGULAR file */ +static GdkPixbuf *get_special_icon (IconType icon_type, + MooFileViewFile *file, + GtkWidget *widget, + gint pixel_size) +{ + const char *name; + + g_assert (icon_type != ICON_REGULAR); + + switch (icon_type) + { + case ICON_BLOCK_DEVICE: + name = "gnome-fs-blockdev"; + break; + case ICON_BROKEN_SYMBOLIC_LINK: + name = "gnome-fs-symlink"; + break; + case ICON_CHARACTER_DEVICE: + name = "gnome-fs-chardev"; + break; + case ICON_DIRECTORY: + name = get_icon_name_for_directory + (moo_file_view_file_path (file)); + break; + case ICON_EXECUTABLE: + name ="gnome-fs-executable"; + break; + case ICON_FIFO: + name = "gnome-fs-fifo"; + break; + case ICON_SOCKET: + name = "gnome-fs-socket"; + break; + default: + g_assert_not_reached (); + return NULL; + } + + return get_cached_icon (widget, name, pixel_size); +} + + +/* Renders a fallback icon from the stock system */ +static GdkPixbuf *get_fallback_icon (GtkWidget *widget, + IconType icon_type, + GtkIconSize size) +{ + const char *stock_name; + GdkPixbuf *pixbuf; + + switch (icon_type) + { + case ICON_BLOCK_DEVICE: + stock_name = GTK_STOCK_HARDDISK; + break; + + case ICON_DIRECTORY: + stock_name = GTK_STOCK_DIRECTORY; + break; + + case ICON_EXECUTABLE: + stock_name = GTK_STOCK_EXECUTE; + break; + + case ICON_NOENT: + stock_name = GTK_STOCK_MISSING_IMAGE; + break; + + default: + stock_name = GTK_STOCK_FILE; + break; + } + + pixbuf = gtk_widget_render_icon (widget, stock_name, size, NULL); + + if (!pixbuf) + { + g_warning ("%s: could not get a stock icon for %s", + G_STRLOC, stock_name); + } + + return pixbuf; +} + + +GdkPixbuf *moo_get_icon_for_file (GtkWidget *widget, + MooFileViewFile *file, + GtkIconSize size) +{ + IconType icon_type; + GdkPixbuf *pixbuf; + int pixel_size; + + icon_type = get_icon_type (file); + + if (icon_type == ICON_REGULAR && !moo_file_view_file_mime_type (file)) + icon_type = ICON_NONE; + + if (!gtk_icon_size_lookup (size, &pixel_size, NULL)) + if (!gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &pixel_size, NULL)) + pixel_size = 16; + + switch (icon_type) + { + case ICON_NONE: + goto fallback; + + case ICON_NOENT: + pixbuf = get_fallback_icon (widget, icon_type, size); + break; + + case ICON_REGULAR: + pixbuf = get_icon_for_mime_type (widget, + moo_file_view_file_mime_type (file), + pixel_size); + break; + + default: + pixbuf = get_special_icon (icon_type, file, widget, pixel_size); + } + + if (pixbuf) + goto out; + +fallback: + pixbuf = get_cached_icon (widget, "gnome-fs-regular", pixel_size); + if (!pixbuf) + pixbuf = get_fallback_icon (widget, icon_type, size); + +out: + return pixbuf; +} diff --git a/moo/mooutils/moofileview.c b/moo/mooutils/moofileview.c new file mode 100644 index 00000000..19440b71 --- /dev/null +++ b/moo/mooutils/moofileview.c @@ -0,0 +1,1179 @@ +/* + * mooutils/moofileview.c + * + * Copyright (C) 2004-2005 by Yevgen Muntyan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * See COPYING file that comes with this distribution. + */ + +#include "mooutils/moofileview.h" +#include "mooutils/moomarshals.h" +#include +#include +#include +#include +#include + + +#define TREEVIEW_UPDATE_TIMEOUT 0.5 + +enum { + TREEVIEW_PAGE = 0, + ICONVIEW_PAGE = 1 +}; + + +typedef struct _History History; + +struct _MooFileViewFile { + char *basename; + char *fullname; + char *uri; + char *display_name; + char *mime_type; + GdkPixbuf *pixbuf; + gboolean is_dir; + struct stat statbuf; + gboolean exists; + gpointer time; /* struct tm* */ + gpointer date_string; + guint size; + guint ref_count; +}; + +struct _MooFileViewPrivate { + GtkListStore *store; + GtkTreeModel *filter_model; + MooFileViewType view_type; + GtkWidget *treeview; + GtkWidget *iconview; + char *current_dir; + gboolean show_hidden_files; + History *history; + guint populate_idle; + GDir *populate_dir; +}; + + +static MooFileViewFile *file_new (MooFileView *fileview, + const char *basename, + const char *fullname); +static MooFileViewFile *file_ref (MooFileViewFile *file); +static void file_unref (MooFileViewFile *file); + + +static void moo_file_view_finalize (GObject *object); + +static gboolean moo_file_view_chdir_real(MooFileView *fileview, + const char *dir, + GError **error); +static void moo_file_view_go_up (MooFileView *fileview); +static void moo_file_view_go_home (MooFileView *fileview); +static void moo_file_view_go_back (MooFileView *fileview); +static void moo_file_view_go_forward(MooFileView *fileview); + +static void history_init (MooFileView *fileview); +static void history_free (MooFileView *fileview); +static void history_goto (MooFileView *fileview, + const char *dirname); +static const char *history_go (MooFileView *fileview, + GtkDirectionType where); +static void history_revert_go (MooFileView *fileview); + +static gboolean populate_tree (MooFileView *fileview, + GError **error); +static gboolean filter_visible_func (GtkTreeModel *model, + GtkTreeIter *iter, + MooFileView *fileview); +static int tree_compare_func (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b); + +static void init_gui (MooFileView *fileview); +static GtkWidget *create_toolbar (MooFileView *fileview); +static void toolbar_button_clicked (GtkToolButton *button, + MooFileView *fileview); +static GtkWidget *create_notebook (MooFileView *fileview); +static GtkWidget *create_filter_combo (MooFileView *fileview); + +static GtkWidget *create_treeview (MooFileView *fileview); +static void tree_row_activated (GtkTreeView *treeview, + GtkTreePath *treepath, + GtkTreeViewColumn *column, + MooFileView *fileview); + +static GtkWidget *create_iconview (MooFileView *fileview); +static void icon_item_activated (GtkIconView *iconview, + GtkTreePath *treepath, + MooFileView *fileview); + +static void get_icon (MooFileView *fileview, + MooFileViewFile*file); + + +/* MOO_TYPE_FILE_VIEW */ +G_DEFINE_TYPE (MooFileView, moo_file_view, GTK_TYPE_VBOX) + +enum { + PROP_0, + PROP_DIRECTORY +}; + +enum { + COLUMN_FILE = 0, + COLUMN_PIXBUF = 1, + COLUMN_DISPLAY_NAME = 2, + COLUMN_SIZE = 3, + COLUMN_DATE = 4 +}; + +enum { + CHDIR, + ACTIVATE, + GO_UP, + GO_BACK, + GO_FORWARD, + GO_HOME, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +static void moo_file_view_class_init (MooFileViewClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkBindingSet *binding_set; + + gobject_class->finalize = moo_file_view_finalize; + + klass->chdir = moo_file_view_chdir_real; + klass->go_up = moo_file_view_go_up; + klass->go_home = moo_file_view_go_home; + klass->go_back = moo_file_view_go_back; + klass->go_forward = moo_file_view_go_forward; + + signals[CHDIR] = g_signal_new ("chdir", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (MooFileViewClass, chdir), + NULL, NULL, + _moo_marshal_BOOLEAN__STRING_POINTER, + G_TYPE_BOOLEAN, 2, + G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE, + G_TYPE_POINTER); + + signals[ACTIVATE] = g_signal_new ("activate", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (MooFileViewClass, activate), + NULL, NULL, + _moo_marshal_VOID__BOXED, + G_TYPE_NONE, 1, + MOO_TYPE_FILE_VIEW_FILE | G_SIGNAL_TYPE_STATIC_SCOPE); + + signals[GO_UP] = g_signal_new ("go-up", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (MooFileViewClass, go_up), + NULL, NULL, + _moo_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[GO_FORWARD] = g_signal_new ("go-forward", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (MooFileViewClass, go_forward), + NULL, NULL, + _moo_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[GO_BACK] = g_signal_new ("go-back", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (MooFileViewClass, go_back), + NULL, NULL, + _moo_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[GO_HOME] = g_signal_new ("go-home", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (MooFileViewClass, go_home), + NULL, NULL, + _moo_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + binding_set = gtk_binding_set_by_class (klass); + + gtk_binding_entry_add_signal (binding_set, + GDK_Up, GDK_MOD1_MASK, + "go-up", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_KP_Up, GDK_MOD1_MASK, + "go-up", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_Left, GDK_MOD1_MASK, + "go-back", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_KP_Left, GDK_MOD1_MASK, + "go-back", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_Right, GDK_MOD1_MASK, + "go-forward", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_KP_Right, GDK_MOD1_MASK, + "go-forward", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_Home, GDK_MOD1_MASK, + "home-folder", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_KP_Home, GDK_MOD1_MASK, + "home-folder", + 0); +} + + +static void moo_file_view_init (MooFileView *fileview) +{ + fileview->priv = g_new0 (MooFileViewPrivate, 1); + + fileview->priv->current_dir = NULL; + fileview->priv->show_hidden_files = FALSE; + + fileview->priv->view_type = MOO_FILE_VIEW_LIST; + + history_init (fileview); + + fileview->priv->store = + gtk_list_store_new (5, + MOO_TYPE_FILE_VIEW_FILE, + GDK_TYPE_PIXBUF, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (fileview->priv->store), + COLUMN_FILE, + (GtkTreeIterCompareFunc) tree_compare_func, + fileview, NULL); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (fileview->priv->store), + COLUMN_FILE, GTK_SORT_ASCENDING); + + fileview->priv->filter_model = + gtk_tree_model_filter_new (GTK_TREE_MODEL (fileview->priv->store), + NULL); + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (fileview->priv->filter_model), + (GtkTreeModelFilterVisibleFunc) filter_visible_func, + fileview, NULL); + + init_gui (fileview); +} + + +static void moo_file_view_finalize (GObject *object) +{ + MooFileView *fileview = MOO_FILE_VIEW (object); + + g_object_unref (fileview->priv->filter_model); + g_object_unref (fileview->priv->store); + + history_free (fileview); + + g_free (fileview->priv->current_dir); + + g_free (fileview->priv); + + G_OBJECT_CLASS (moo_file_view_parent_class)->finalize (object); +} + + +GtkWidget *moo_file_view_new (void) +{ + return GTK_WIDGET (g_object_new (MOO_TYPE_FILE_VIEW, NULL)); +} + + +static gboolean moo_file_view_chdir_real(MooFileView *fileview, + const char *new_dir, + GError **error) +{ + char *real_new_dir; + + g_return_val_if_fail (MOO_IS_FILE_VIEW (fileview), FALSE); + g_return_val_if_fail (new_dir != NULL, FALSE); + + if (fileview->priv->current_dir && !strcmp (fileview->priv->current_dir, new_dir)) + return TRUE; + + if (g_path_is_absolute (new_dir)) + { + real_new_dir = g_strdup (new_dir); + } + else + { + char *current_dir = g_get_current_dir (); + real_new_dir = g_build_filename (current_dir, new_dir); + } + + if (!g_file_test (real_new_dir, G_FILE_TEST_IS_DIR)) + { + g_set_error (error, + G_FILE_ERROR, + G_FILE_ERROR_NOTDIR, + "'%s' is not a directory", + real_new_dir); + g_free (real_new_dir); + return FALSE; + } + + g_free (fileview->priv->current_dir); + fileview->priv->current_dir = real_new_dir; + + history_goto (fileview, real_new_dir); + + gtk_list_store_clear (fileview->priv->store); + return populate_tree (fileview, error); +} + + +static void init_gui (MooFileView *fileview) +{ + GtkBox *box; + GtkWidget *toolbar, *notebook, *filter_combo; + + box = GTK_BOX (fileview); + + toolbar = create_toolbar (fileview); + gtk_widget_show (toolbar); + gtk_box_pack_start (box, toolbar, FALSE, FALSE, 0); + + notebook = create_notebook (fileview); + gtk_widget_show (notebook); + gtk_box_pack_start (box, notebook, TRUE, TRUE, 0); + + filter_combo = create_filter_combo (fileview); + gtk_widget_show (filter_combo); + gtk_box_pack_start (box, filter_combo, FALSE, FALSE, 0); + + if (fileview->priv->view_type == MOO_FILE_VIEW_ICON) + { + gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), + ICONVIEW_PAGE); + gtk_widget_grab_focus (fileview->priv->iconview); + } + else + { + gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), + TREEVIEW_PAGE); + gtk_widget_grab_focus (fileview->priv->treeview); + } +} + + +static GtkWidget *create_toolbar (MooFileView *fileview) +{ + GtkWidget *toolbar, *icon; + GtkToolItem *item; + GtkTooltips *tooltips; + + tooltips = gtk_tooltips_new (); + + /*********************************************************/ + /* Navigation toolbar */ + + toolbar = gtk_toolbar_new (); + gtk_toolbar_set_show_arrow (GTK_TOOLBAR (toolbar), TRUE); + gtk_toolbar_set_icon_size (GTK_TOOLBAR (toolbar), + GTK_ICON_SIZE_MENU); + gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), + GTK_TOOLBAR_ICONS); + + /* Up */ + icon = gtk_image_new_from_stock (GTK_STOCK_GO_UP, + GTK_ICON_SIZE_MENU); + item = gtk_tool_button_new (icon, NULL); + gtk_tool_item_set_tooltip (item, tooltips, "Up", "Up"); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1); + g_object_set_data (G_OBJECT (item), "moo-file-view-signal", + (gpointer) "go-up"); + g_signal_connect (item, "clicked", + G_CALLBACK (toolbar_button_clicked), + fileview); + + /* Back */ + icon = gtk_image_new_from_stock (GTK_STOCK_GO_BACK, + GTK_ICON_SIZE_MENU); + item = gtk_tool_button_new (icon, NULL); + gtk_tool_item_set_tooltip (item, tooltips, "Back", "Back"); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1); + g_object_set_data (G_OBJECT (item), "moo-file-view-signal", + (gpointer) "go-back"); + g_signal_connect (item, "clicked", + G_CALLBACK (toolbar_button_clicked), + fileview); + + /* Forward */ + icon = gtk_image_new_from_stock (GTK_STOCK_GO_FORWARD, + GTK_ICON_SIZE_MENU); + item = gtk_tool_button_new (icon, NULL); + gtk_tool_item_set_tooltip (item, tooltips, "Forward", "Forward"); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1); + g_object_set_data (G_OBJECT (item), "moo-file-view-signal", + (gpointer) "go-forward"); + g_signal_connect (item, "clicked", + G_CALLBACK (toolbar_button_clicked), + fileview); + + /* Home */ + icon = gtk_image_new_from_stock (GTK_STOCK_HOME, + GTK_ICON_SIZE_MENU); + item = gtk_tool_button_new (icon, NULL); + gtk_tool_item_set_tooltip (item, tooltips, "Home", "Home"); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1); + g_object_set_data (G_OBJECT (item), "moo-file-view-signal", + (gpointer) "go-home"); + g_signal_connect (item, "clicked", + G_CALLBACK (toolbar_button_clicked), + fileview); + + return toolbar; +} + + +static void toolbar_button_clicked (GtkToolButton *button, + MooFileView *fileview) +{ + const char *signal = g_object_get_data (G_OBJECT (button), + "moo-file-view-signal"); + g_return_if_fail (signal != NULL); + g_signal_emit_by_name (fileview, signal); +} + + +static GtkWidget *create_notebook (MooFileView *fileview) +{ + GtkWidget *notebook, *swin, *treeview, *iconview; + + notebook = gtk_notebook_new (); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), FALSE); + + swin = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (swin); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), swin, NULL); + fileview->priv->treeview = treeview = create_treeview (fileview); + gtk_widget_show (treeview); + gtk_container_add (GTK_CONTAINER (swin), treeview); + + swin = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (swin); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), swin, NULL); + fileview->priv->iconview = iconview = create_iconview (fileview); + gtk_widget_show (iconview); + gtk_container_add (GTK_CONTAINER (swin), iconview); + + return notebook; +} + + +static GtkWidget *create_filter_combo (G_GNUC_UNUSED MooFileView *fileview) +{ + GtkWidget *hbox, *button, *combo; + + hbox = gtk_hbox_new (FALSE, 0); + + button = gtk_toggle_button_new_with_label ("Filter"); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + + combo = gtk_combo_box_entry_new (); + gtk_widget_show (combo); + gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0); + + return hbox; +} + + +static GtkWidget *create_treeview (MooFileView *fileview) +{ + GtkWidget *treeview; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + + treeview = gtk_tree_view_new_with_model (fileview->priv->filter_model); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); + gtk_tree_view_set_search_column (GTK_TREE_VIEW (treeview), COLUMN_DISPLAY_NAME); + + g_signal_connect (treeview, "row-activated", + G_CALLBACK (tree_row_activated), fileview); + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, "Name"); + gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); + + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_set_attributes (column, cell, + "pixbuf", COLUMN_PIXBUF, + NULL); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", COLUMN_DISPLAY_NAME, + NULL); + +#if 0 + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, "Size"); + gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_set_attributes (column, cell, + "text", COLUMN_SIZE, + NULL); + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, "Date"); + gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_set_attributes (column, cell, + "text", COLUMN_DATE, + NULL); +#endif + + return treeview; +} + + +static void get_icon (MooFileView *fileview, + MooFileViewFile*file) +{ + GdkPixbuf *pixbuf = NULL; + + g_return_if_fail (file != NULL); + + pixbuf = moo_get_icon_for_file (GTK_WIDGET (fileview), file, + GTK_ICON_SIZE_MENU); + + if (file->pixbuf) + g_object_unref (file->pixbuf); + + if (pixbuf) + file->pixbuf = g_object_ref (pixbuf); + else + file->pixbuf = NULL; +} + + +#define MAX_DATE_LEN 1024 +static gboolean populate_a_bit (MooFileView *fileview) +{ + const char *path; + const char *name; + GDir *dir; + GtkTreeIter iter; + GTimer *timer; + gboolean done = FALSE; + + dir = fileview->priv->populate_dir; + path = fileview->priv->current_dir; + + if (!dir || !path) + { + fileview->priv->populate_idle = 0; + g_dir_close (fileview->priv->populate_dir); + fileview->priv->populate_dir = NULL; + g_return_val_if_reached (FALSE); + } + + timer = g_timer_new (); + + while (TRUE) + { + char *fullname; + char *size = NULL; + MooFileViewFile *file; + + name = g_dir_read_name (dir); + + if (!name) + { + done = TRUE; + break; + } + + fullname = g_build_filename (path, name, NULL); + + file = file_new (fileview, name, fullname); + size = g_strdup_printf ("%d", file->size); + + gtk_list_store_append (fileview->priv->store, &iter); + gtk_list_store_set (fileview->priv->store, &iter, + COLUMN_FILE, file, + COLUMN_DISPLAY_NAME, file->display_name, + COLUMN_PIXBUF, file->pixbuf, + COLUMN_SIZE, size, + COLUMN_DATE, file->date_string, + -1); + + file_unref (file); + g_free (fullname); + g_free (size); + + if (g_timer_elapsed (timer, NULL) > TREEVIEW_UPDATE_TIMEOUT) + break; + } + + g_timer_destroy (timer); + + if (done) + { + fileview->priv->populate_idle = 0; + g_dir_close (fileview->priv->populate_dir); + fileview->priv->populate_dir = NULL; + return FALSE; + } + else + { + return TRUE; + } +} +#undef MAX_DATE_LEN + + +static gboolean populate_tree (MooFileView *fileview, + GError **error) +{ + const char *path = fileview->priv->current_dir; + GtkTreeIter iter; + + g_return_val_if_fail (path != NULL, FALSE); + + if (fileview->priv->populate_idle) + { + g_source_remove (fileview->priv->populate_idle); + fileview->priv->populate_idle = 0; + g_dir_close (fileview->priv->populate_dir); + } + + fileview->priv->populate_dir = g_dir_open (path, 0, error); + if (!fileview->priv->populate_dir) return FALSE; + + if (populate_a_bit (fileview)) + fileview->priv->populate_idle = + g_idle_add ((GSourceFunc) populate_a_bit, fileview); + + if (gtk_tree_model_get_iter_first (fileview->priv->filter_model, &iter)) + { + GtkTreeSelection *selection; + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (fileview->priv->treeview)); + gtk_tree_selection_select_iter (selection, &iter); + } + + return TRUE; +} + + +static gboolean filter_visible_func (GtkTreeModel *model, + GtkTreeIter *iter, + MooFileView *fileview) +{ + MooFileViewFile *file; + gboolean visible = TRUE; + + gtk_tree_model_get (model, iter, COLUMN_FILE, &file, -1); + + if (!file) + { + visible = FALSE; + goto out; + } + + if (!fileview->priv->show_hidden_files && file->basename[0] == '.') + { + visible = FALSE; + goto out; + } + +out: + file_unref (file); + return visible; +} + + +static int tree_compare_func (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b) +{ + MooFileViewFile *f1, *f2; + gboolean result = 0; + + gtk_tree_model_get (model, a, COLUMN_FILE, &f1, -1); + gtk_tree_model_get (model, b, COLUMN_FILE, &f2, -1); + + if (!f1 || !f2) + { + if (f1 < f2) + result = -1; + else if (f1 == f2) + result = 0; + else + result = 1; + goto out; + } + + if (f1->is_dir != f2->is_dir) + { + if (f1->is_dir && !f2->is_dir) + result = -1; + else + result = 1; + goto out; + } + + result = strcmp (f1->basename, f2->basename); + +out: + file_unref (f1); + file_unref (f2); + return result; +} + + +static MooFileViewFile *file_new (MooFileView *fileview, + const char *basename, + const char *fullname) +{ + MooFileViewFile *file; + + g_return_val_if_fail (basename != NULL, NULL); + g_return_val_if_fail (fullname != NULL, NULL); + + file = g_new (MooFileViewFile, 1); + file->ref_count = 1; + + file->basename = g_strdup (basename); + file->fullname = g_strdup (fullname); + file->uri = g_strdup_printf ("file://%s", fullname); + file->display_name = g_filename_display_basename (basename); + file->mime_type = NULL; + + file->time = NULL; /* struct tm* */ + file->date_string = NULL; + file->size = 0; + + file->is_dir = FALSE; + file->exists = TRUE; + + if (g_stat (fullname, &file->statbuf) != 0) + { + if (errno == ENOENT) + { + gchar *display_name = g_filename_display_name (fullname); + g_warning ("%s: file '%s' doesn't exist", + G_STRLOC, display_name); + g_free (display_name); + file->exists = FALSE; + } + else if (g_lstat (fullname, &file->statbuf) != 0) + { + int save_errno = errno; + gchar *display_name = g_filename_display_name (fullname); + g_warning ("%s: error getting information for '%s': %s", + G_STRLOC, display_name, + g_strerror (save_errno)); + g_free (display_name); + file->exists = FALSE; + } + } + + if (file->exists) + { + if (S_ISDIR (file->statbuf.st_mode)) + file->is_dir = TRUE; + } + + file->pixbuf = NULL; + get_icon (fileview, file); + + return file; +} + + +static MooFileViewFile *file_ref (MooFileViewFile *file) +{ + g_return_val_if_fail (file != NULL, NULL); + file->ref_count++; + return file; +} + + +static void file_unref (MooFileViewFile *file) +{ + if (file && !--file->ref_count) + { + g_free (file->basename); + g_free (file->fullname); + g_free (file->uri); + g_free (file->display_name); + g_free (file->mime_type); + if (file->pixbuf) g_object_unref (file->pixbuf); + g_free (file->time); /* struct tm* */ + g_free (file->date_string); + g_free (file); + } +} + + +GType moo_file_view_file_get_type (void) +{ + static GType type = 0; + + if (!type) + { + type = g_boxed_type_register_static ("MooFileViewFile", + (GBoxedCopyFunc) file_ref, + (GBoxedFreeFunc) file_unref); + } + + return type; +} + + +gboolean moo_file_view_chdir (MooFileView *fileview, + const char *dir, + GError **error) +{ + gboolean result; + + g_return_val_if_fail (MOO_IS_FILE_VIEW (fileview), FALSE); + g_return_val_if_fail (dir != NULL, FALSE); + + g_signal_emit (fileview, signals[CHDIR], 0, dir, error, &result); + + return result; +} + + +static void moo_file_view_go_up (MooFileView *fileview) +{ + char *dirname; + GError *error = NULL; + + dirname = g_path_get_dirname (fileview->priv->current_dir); + + if (!moo_file_view_chdir (fileview, dirname, &error)) + { + g_warning ("%s: could not go up", G_STRLOC); + + if (error) + { + g_warning ("%s: %s", G_STRLOC, error->message); + g_error_free (error); + } + } + + g_free (dirname); +} + + +static void moo_file_view_go_home (MooFileView *fileview) +{ + const char *dir; + GError *error = NULL; + + dir = g_get_home_dir (); + + if (!moo_file_view_chdir (fileview, dir, &error)) + { + g_warning ("%s: could not go up", G_STRLOC); + + if (error) + { + g_warning ("%s: %s", G_STRLOC, error->message); + g_error_free (error); + } + } +} + + +static void tree_path_activated (MooFileView *fileview, + GtkTreePath *filter_treepath) +{ + GtkTreePath *treepath = NULL; + MooFileViewFile *file = NULL; + GtkTreeIter iter; + + treepath = gtk_tree_model_filter_convert_path_to_child_path ( + GTK_TREE_MODEL_FILTER (fileview->priv->filter_model), filter_treepath); + g_return_if_fail (treepath != NULL); + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (fileview->priv->store), + &iter, treepath)) + { + gtk_tree_path_free (treepath); + return; + } + + gtk_tree_model_get (GTK_TREE_MODEL (fileview->priv->store), + &iter, COLUMN_FILE, &file, -1); + if (!file) + { + gtk_tree_path_free (treepath); + g_return_if_reached (); + } + + if (file->is_dir) + { + GError *error = NULL; + + if (!moo_file_view_chdir (fileview, file->fullname, &error)) + { + g_warning ("%s: could not go into '%s'", + G_STRLOC, file->fullname); + + if (error) + { + g_warning ("%s: %s", G_STRLOC, error->message); + g_error_free (error); + } + } + } + else + { + g_signal_emit (fileview, signals[ACTIVATE], 0, file); + } + + gtk_tree_path_free (treepath); + file_unref (file); +} + + +static void tree_row_activated (G_GNUC_UNUSED GtkTreeView *treeview, + GtkTreePath *filter_treepath, + G_GNUC_UNUSED GtkTreeViewColumn *column, + MooFileView *fileview) +{ + tree_path_activated (fileview, filter_treepath); +} + + +static void icon_item_activated (G_GNUC_UNUSED GtkIconView *iconview, + GtkTreePath *filter_treepath, + MooFileView *fileview) +{ + tree_path_activated (fileview, filter_treepath); +} + + +static void moo_file_view_go (MooFileView *fileview, + GtkDirectionType where) +{ + const char *dir; + GError *error = NULL; + + dir = history_go (fileview, where); + + if (dir) + { + if (!moo_file_view_chdir (fileview, dir, &error)) + { + g_warning ("%s: could not go into '%s'", + G_STRLOC, dir); + + if (error) + { + g_warning ("%s: %s", G_STRLOC, error->message); + g_error_free (error); + } + + history_revert_go (fileview); + } + } +} + + +static void moo_file_view_go_back (MooFileView *fileview) +{ + moo_file_view_go (fileview, GTK_DIR_LEFT); +} + + +static void moo_file_view_go_forward(MooFileView *fileview) +{ + moo_file_view_go (fileview, GTK_DIR_RIGHT); +} + + +struct _History { + gboolean done; + GtkDirectionType direction; + + GList *list; + GList *current; +}; + + +static void history_init (MooFileView *fileview) +{ + History *hist; + + fileview->priv->history = hist = g_new0 (History, 1); +} + + +static void history_free (MooFileView *fileview) +{ + g_list_foreach (fileview->priv->history->list, (GFunc) g_free, NULL); + g_list_free (fileview->priv->history->list); + g_free (fileview->priv->history); + fileview->priv->history = NULL; +} + + +static const char *history_go (MooFileView *fileview, + GtkDirectionType where) +{ + History *hist = fileview->priv->history; + const char *dir; + + g_assert (where == GTK_DIR_LEFT || where == GTK_DIR_RIGHT); + + if (!hist->current) + return NULL; + + if (where == GTK_DIR_RIGHT) + { + if (!hist->current->next) + return NULL; + + dir = hist->current->next->data; + hist->current = hist->current->next; + } + else + { + if (!hist->current->prev) + return NULL; + dir = hist->current->prev->data; + hist->current = hist->current->prev; + } + + hist->done = TRUE; + hist->direction = where; + return dir; +} + + +static void history_revert_go (MooFileView *fileview) +{ + History *hist = fileview->priv->history; + + g_assert (hist->done); + + if (hist->direction == GTK_DIR_LEFT) + { + g_assert (hist->current && hist->current->next); + hist->current = hist->current->next; + } + else + { + g_assert (hist->current && hist->current->prev); + hist->current = hist->current->prev; + } + + hist->done = FALSE; +} + + +static void history_goto (MooFileView *fileview, + const char *dirname) +{ + History *hist = fileview->priv->history; + + g_return_if_fail (dirname != NULL); + + if (hist->done) + { + hist->done = FALSE; + return; + } + + if (hist->current && hist->current->next) + { + GList *l; + for (l = hist->current->next; l != NULL; l = l->next) + g_free (l->data); + l = hist->current->next; + hist->current->next = NULL; + g_list_free (l); + } + + if (!hist->current || strcmp (dirname, (char*) hist->current)) + { + hist->list = g_list_append (hist->list, g_strdup (dirname)); + hist->current = g_list_last (hist->list); + } +} + + +static GtkWidget *create_iconview (MooFileView *fileview) +{ + GtkWidget *iconview; + + iconview = gtk_icon_view_new_with_model (fileview->priv->filter_model); + gtk_icon_view_set_text_column (GTK_ICON_VIEW (iconview), + COLUMN_DISPLAY_NAME); + gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (iconview), + COLUMN_PIXBUF); + gtk_icon_view_set_orientation (GTK_ICON_VIEW (iconview), + GTK_ORIENTATION_HORIZONTAL); + + g_signal_connect (iconview, "item-activated", + G_CALLBACK (icon_item_activated), fileview); + + return iconview; +} + + +gconstpointer moo_file_view_file_get_stat (MooFileViewFile *file) +{ + g_return_val_if_fail (file != NULL, NULL); + if (file->exists) + return &file->statbuf; + else + return NULL; +} + + +const char *moo_file_view_file_path (MooFileViewFile *file) +{ + g_return_val_if_fail (file != NULL, NULL); + return file->fullname; +} + + +const char *moo_file_view_file_mime_type (MooFileViewFile *file) +{ + g_return_val_if_fail (file != NULL, NULL); + return file->mime_type; +} diff --git a/moo/mooutils/moofileview.h b/moo/mooutils/moofileview.h new file mode 100644 index 00000000..4e7c3bca --- /dev/null +++ b/moo/mooutils/moofileview.h @@ -0,0 +1,88 @@ +/* + * mooutils/moofileview.h + * + * Copyright (C) 2004-2005 by Yevgen Muntyan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * See COPYING file that comes with this distribution. + */ + +#ifndef MOOUTILS_MOOFILEVIEW_H +#define MOOUTILS_MOOFILEVIEW_H + +#include + +G_BEGIN_DECLS + + +#define MOO_TYPE_FILE_VIEW_FILE (moo_file_view_file_get_type ()) +#define MOO_TYPE_FILE_VIEW_TYPE (moo_file_view_type_get_type ()) +#define MOO_TYPE_FILE_VIEW (moo_file_view_get_type ()) +#define MOO_FILE_VIEW(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), MOO_TYPE_FILE_VIEW, MooFileView)) +#define MOO_FILE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MOO_TYPE_FILE_VIEW, MooFileViewClass)) +#define MOO_IS_FILE_VIEW(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), MOO_TYPE_FILE_VIEW)) +#define MOO_IS_FILE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MOO_TYPE_FILE_VIEW)) +#define MOO_FILE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MOO_TYPE_FILE_VIEW, MooFileViewClass)) + + +typedef enum { + MOO_FILE_VIEW_LIST, + MOO_FILE_VIEW_ICON +} MooFileViewType; + +typedef struct _MooFileView MooFileView; +typedef struct _MooFileViewFile MooFileViewFile; +typedef struct _MooFileViewPrivate MooFileViewPrivate; +typedef struct _MooFileViewClass MooFileViewClass; + +struct _MooFileView +{ + GtkVBox vbox; + MooFileViewPrivate *priv; +}; + +struct _MooFileViewClass +{ + GtkVBoxClass vbox_class; + + gboolean (*chdir) (MooFileView *fileview, + const char *dir, + GError **error); + void (*activate) (MooFileView *fileview, + MooFileViewFile *file); + void (*go_back) (MooFileView *fileview); + void (*go_forward) (MooFileView *fileview); + void (*go_home) (MooFileView *fileview); + void (*go_up) (MooFileView *fileview); +}; + + +GType moo_file_view_get_type (void) G_GNUC_CONST; +GType moo_file_view_file_get_type (void) G_GNUC_CONST; +GType moo_file_view_type_get_type (void) G_GNUC_CONST; + +GtkWidget *moo_file_view_new (void); + +gboolean moo_file_view_chdir (MooFileView *fileview, + const char *dir, + GError **error); + +void moo_file_view_set_view_type (MooFileView *fileview, + MooFileViewType type); + +gconstpointer moo_file_view_file_get_stat (MooFileViewFile *file); +const char *moo_file_view_file_path (MooFileViewFile *file); +const char *moo_file_view_file_mime_type (MooFileViewFile *file); + +GdkPixbuf *moo_get_icon_for_file (GtkWidget *widget, + MooFileViewFile *file, + GtkIconSize size); + + +G_END_DECLS + +#endif /* MOOUTILS_MOOFILEVIEW_H */ diff --git a/moo/mooutils/moomarshals.list b/moo/mooutils/moomarshals.list index 994a486f..776fae71 100644 --- a/moo/mooutils/moomarshals.list +++ b/moo/mooutils/moomarshals.list @@ -1,6 +1,7 @@ BOOL:VOID BOOL:POINTER BOOL:STRING +BOOL:STRING,POINTER BOOL:STRING,STRING BOOL:STRING,STRING,POINTER INT:VOID diff --git a/moo/mooutils/moopaned.c b/moo/mooutils/moopaned.c new file mode 100644 index 00000000..ba805926 --- /dev/null +++ b/moo/mooutils/moopaned.c @@ -0,0 +1,1719 @@ +/* + * mooutils/moopaned.c + * + * Copyright (C) 2004-2005 by Yevgen Muntyan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * See COPYING file that comes with this distribution. + */ + +#include "mooutils/moopaned.h" + + +static int MIN_PANE_SIZE = 10; +static int SPACING_IN_BUTTON = 4; + + +typedef struct { + GtkWidget *widget; + GtkWidget *button; +} Pane; + + +struct _MooPanedPrivate { + GtkPositionType pane_position; + + GdkWindow *handle_window; + GdkWindow *pane_window; + + Pane *current_pane; + GSList *panes; + + gboolean close_on_child_focus; + + int position; + + gboolean button_box_visible; + int button_box_size; + gboolean handle_visible; + int handle_size; + gboolean pane_widget_visible; + int pane_widget_size; + gboolean sticky; + + gboolean handle_prelit; + gboolean in_drag; + int drag_start; +}; + + +static void moo_paned_finalize (GObject *object); +static void moo_paned_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void moo_paned_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static GObject *moo_paned_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties); + +static void moo_paned_realize (GtkWidget *widget); +static void moo_paned_unrealize (GtkWidget *widget); +static void moo_paned_map (GtkWidget *widget); + +static void moo_paned_set_focus_child (GtkContainer *container, + GtkWidget *widget); + +static void moo_paned_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void moo_paned_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); + +static gboolean moo_paned_expose (GtkWidget *widget, + GdkEventExpose *event); +static gboolean moo_paned_motion (GtkWidget *widget, + GdkEventMotion *event); +static gboolean moo_paned_enter (GtkWidget *widget, + GdkEventCrossing *event); +static gboolean moo_paned_leave (GtkWidget *widget, + GdkEventCrossing *event); +static gboolean moo_paned_button_press (GtkWidget *widget, + GdkEventButton *event); +static gboolean moo_paned_button_release(GtkWidget *widget, + GdkEventButton *event); + +static void moo_paned_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data); +static void moo_paned_remove (GtkContainer *container, + GtkWidget *widget); +static void moo_paned_remove_pane (MooPaned *paned, + GtkWidget *pane_widget); + +static void realize_handle (MooPaned *paned); +static void realize_pane (MooPaned *paned); +static void draw_handle (MooPaned *paned); +static void button_toggled (GtkToggleButton *button, + MooPaned *paned); +static void button_box_visible_notify (MooPaned *paned); + + +/* MOO_TYPE_PANED */ +G_DEFINE_TYPE (MooPaned, moo_paned, GTK_TYPE_BIN) + +enum { + PROP_0, + PROP_PANE_POSITION, + PROP_CLOSE_PANE_ON_CHILD_FOCUS, + PROP_STICKY_PANE +}; + + +static void moo_paned_class_init (MooPanedClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + gobject_class->finalize = moo_paned_finalize; + gobject_class->set_property = moo_paned_set_property; + gobject_class->get_property = moo_paned_get_property; + gobject_class->constructor = moo_paned_constructor; + + widget_class->realize = moo_paned_realize; + widget_class->unrealize = moo_paned_unrealize; + widget_class->map = moo_paned_map; + widget_class->expose_event = moo_paned_expose; + widget_class->size_request = moo_paned_size_request; + widget_class->size_allocate = moo_paned_size_allocate; + widget_class->motion_notify_event = moo_paned_motion; + widget_class->enter_notify_event = moo_paned_enter; + widget_class->leave_notify_event = moo_paned_leave; + widget_class->button_press_event = moo_paned_button_press; + widget_class->button_release_event = moo_paned_button_release; + + container_class->forall = moo_paned_forall; + container_class->set_focus_child = moo_paned_set_focus_child; + container_class->remove = moo_paned_remove; + + g_object_class_install_property (gobject_class, + PROP_PANE_POSITION, + g_param_spec_enum ("pane-position", + "pane-position", + "pane-position", + GTK_TYPE_POSITION_TYPE, + GTK_POS_LEFT, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (gobject_class, + PROP_CLOSE_PANE_ON_CHILD_FOCUS, + g_param_spec_boolean ("close-pane-on-child-focus", + "close-pane-on-child-focus", + "close-pane-on-child-focus", + TRUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (gobject_class, + PROP_STICKY_PANE, + g_param_spec_boolean ("sticky-pane", + "sticky-pane", + "sticky-pane", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("handle-size", + "handle-size", + "handle-size", + 0, + G_MAXINT, + 5, + G_PARAM_READABLE)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("button-spacing", + "button-spacing", + "button-spacing", + 0, + G_MAXINT, + 0, + G_PARAM_READABLE)); +} + + +static void moo_paned_init (MooPaned *paned) +{ + paned->priv = g_new0 (MooPanedPrivate, 1); + + paned->button_box = NULL; + + paned->priv->pane_position = -1; + paned->priv->handle_window = NULL; + paned->priv->pane_window = NULL; + paned->priv->current_pane = NULL; + paned->priv->panes = NULL; + paned->priv->button_box_visible = FALSE; + paned->priv->button_box_size = 0; + paned->priv->handle_visible = FALSE; + paned->priv->handle_size = 0; + paned->priv->pane_widget_visible = FALSE; + paned->priv->pane_widget_size = 0; + paned->priv->sticky = FALSE; + paned->priv->position = -1; + paned->priv->handle_prelit = FALSE; + paned->priv->in_drag = FALSE; + paned->priv->drag_start = -1; +} + + +static GObject *moo_paned_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *object; + MooPaned *paned; + int button_spacing; + + object = G_OBJECT_CLASS(moo_paned_parent_class)->constructor (type, + n_construct_properties, construct_properties); + paned = MOO_PANED (object); + + gtk_widget_style_get (GTK_WIDGET (paned), + "button-spacing", &button_spacing, NULL); + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + paned->button_box = gtk_vbox_new (FALSE, button_spacing); + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + paned->button_box = gtk_hbox_new (FALSE, button_spacing); + break; + default: + g_warning ("%s: invalid 'pane-position' property value '%d'," + "falling back to GTK_POS_LEFT", G_STRLOC, + paned->priv->pane_position); + paned->priv->pane_position = GTK_POS_LEFT; + paned->button_box = gtk_vbox_new (FALSE, button_spacing); + break; + } + + gtk_object_sink (gtk_object_ref (GTK_OBJECT (paned->button_box))); + gtk_widget_set_parent (paned->button_box, GTK_WIDGET (paned)); + gtk_widget_show (paned->button_box); + g_signal_connect_swapped (paned->button_box, "notify::visible", + G_CALLBACK (button_box_visible_notify), + paned); + + return object; +} + + +static void moo_paned_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MooPaned *paned = MOO_PANED (object); + + switch (prop_id) + { + case PROP_PANE_POSITION: + paned->priv->pane_position = g_value_get_enum (value); + break; + + case PROP_CLOSE_PANE_ON_CHILD_FOCUS: + paned->priv->close_on_child_focus = + g_value_get_boolean (value); + g_object_notify (object, "close-pane-on-child-focus"); + break; + + case PROP_STICKY_PANE: + moo_paned_set_sticky_pane (paned, + g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + + +static void moo_paned_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MooPaned *paned = MOO_PANED (object); + + switch (prop_id) + { + case PROP_PANE_POSITION: + g_value_set_enum (value, paned->priv->pane_position); + break; + + case PROP_CLOSE_PANE_ON_CHILD_FOCUS: + g_value_set_boolean (value, paned->priv->close_on_child_focus); + break; + + case PROP_STICKY_PANE: + g_value_set_boolean (value, paned->priv->sticky); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + + +static void moo_paned_finalize (GObject *object) +{ + MooPaned *paned = MOO_PANED (object); + g_free (paned->priv); + G_OBJECT_CLASS (moo_paned_parent_class)->finalize (object); +} + + +GtkWidget *moo_paned_new (GtkPositionType pane_position) +{ + return GTK_WIDGET (g_object_new (MOO_TYPE_PANED, + "pane-position", pane_position, + NULL)); +} + + +static void moo_paned_realize (GtkWidget *widget) +{ + static GdkWindowAttr attributes; + gint attributes_mask; + MooPaned *paned; + + paned = MOO_PANED (widget); + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.event_mask = gtk_widget_get_events (widget) + | GDK_EXPOSURE_MASK; + + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.wclass = GDK_INPUT_OUTPUT; + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + gdk_window_set_user_data (widget->window, widget); + + widget->style = gtk_style_attach (widget->style, widget->window); + gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); + + realize_pane (paned); +} + + +static void realize_handle (MooPaned *paned) +{ + static GdkWindowAttr attributes; + gint attributes_mask; + GtkWidget *widget = GTK_WIDGET (paned); + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + attributes.y = 0; + attributes.width = paned->priv->handle_size; + attributes.height = widget->allocation.height; + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + attributes.x = 0; + attributes.width = widget->allocation.width; + attributes.height = paned->priv->handle_size; + break; + } + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + attributes.x = paned->priv->pane_widget_size; + break; + case GTK_POS_RIGHT: + attributes.x = 0; + break; + case GTK_POS_TOP: + attributes.y = paned->priv->pane_widget_size; + break; + case GTK_POS_BOTTOM: + attributes.y = 0; + break; + } + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.event_mask = gtk_widget_get_events (widget) + | GDK_POINTER_MOTION_HINT_MASK + | GDK_POINTER_MOTION_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_EXPOSURE_MASK + | GDK_ENTER_NOTIFY_MASK + | GDK_LEAVE_NOTIFY_MASK; + + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.wclass = GDK_INPUT_OUTPUT; + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + attributes.cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW); + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + attributes.cursor = gdk_cursor_new (GDK_SB_V_DOUBLE_ARROW); + break; + } + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | + GDK_WA_COLORMAP | GDK_WA_CURSOR; + + paned->priv->handle_window = gdk_window_new (paned->priv->pane_window, + &attributes, attributes_mask); + gdk_window_set_user_data (paned->priv->handle_window, widget); + + gtk_style_set_background (widget->style, + paned->priv->handle_window, + GTK_STATE_NORMAL); + + gdk_cursor_unref (attributes.cursor); +} + + +static void realize_pane (MooPaned *paned) +{ + static GdkWindowAttr attributes; + gint attributes_mask; + GtkWidget *widget = GTK_WIDGET (paned); + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + attributes.y = 0; + attributes.height = widget->allocation.height; + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + attributes.x = 0; + attributes.width = widget->allocation.width; + break; + } + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + attributes.x = paned->priv->button_box_size; + attributes.width = paned->priv->pane_widget_size + + paned->priv->handle_size; + break; + case GTK_POS_RIGHT: + attributes.width = paned->priv->pane_widget_size + + paned->priv->handle_size; + attributes.x = widget->allocation.width - + paned->priv->button_box_size - + attributes.width; + break; + case GTK_POS_TOP: + attributes.y = paned->priv->button_box_size; + attributes.height = paned->priv->pane_widget_size + + paned->priv->handle_size; + break; + case GTK_POS_BOTTOM: + attributes.height = paned->priv->pane_widget_size + + paned->priv->handle_size; + attributes.y = widget->allocation.height - + paned->priv->button_box_size - + attributes.height; + break; + } + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.event_mask = gtk_widget_get_events (widget) + | GDK_EXPOSURE_MASK; + + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW); + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | + GDK_WA_COLORMAP; + + paned->priv->pane_window = + gdk_window_new (widget->window, &attributes, attributes_mask); + gdk_window_set_user_data (paned->priv->pane_window, widget); + + gtk_style_set_background (widget->style, + paned->priv->pane_window, + GTK_STATE_NORMAL); + + realize_handle (paned); +} + + +static void moo_paned_unrealize (GtkWidget *widget) +{ + MooPaned *paned = MOO_PANED (widget); + + if (GTK_WIDGET_CLASS (moo_paned_parent_class)->unrealize) + GTK_WIDGET_CLASS (moo_paned_parent_class)->unrealize (widget); + + if (paned->priv->handle_window) + { + gdk_window_set_user_data (paned->priv->handle_window, NULL); + gdk_window_destroy (paned->priv->handle_window); + paned->priv->handle_window = NULL; + paned->priv->handle_visible = FALSE; + paned->priv->handle_size = 0; + } + + if (paned->priv->pane_window) + { + gdk_window_set_user_data (paned->priv->pane_window, NULL); + gdk_window_destroy (paned->priv->pane_window); + paned->priv->pane_window = NULL; + paned->priv->pane_widget_visible = FALSE; + paned->priv->pane_widget_size = 0; + } +} + + +static void add_button_box_requisition (MooPaned *paned, + GtkRequisition *requisition, + GtkRequisition *child_requisition) +{ + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + requisition->width += child_requisition->width; + requisition->height = MAX (child_requisition->height, requisition->height); + paned->priv->button_box_size = child_requisition->width; + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + requisition->height += child_requisition->height; + requisition->width = MAX (child_requisition->width, requisition->width); + paned->priv->button_box_size = child_requisition->height; + break; + } +} + + +static void add_handle_requisition (MooPaned *paned, + GtkRequisition *requisition) +{ + gtk_widget_style_get (GTK_WIDGET (paned), + "handle_size", &paned->priv->handle_size, + NULL); + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + requisition->width += paned->priv->handle_size; + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + requisition->height += paned->priv->handle_size; + break; + } +} + + +static void add_pane_widget_requisition (MooPaned *paned, + GtkRequisition *requisition, + GtkRequisition *child_requisition) +{ + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + requisition->height = MAX (child_requisition->height, requisition->height); + + if (paned->priv->sticky) + { + requisition->width += child_requisition->width; + } + else + { + requisition->width = MAX (child_requisition->width + + paned->priv->button_box_size + + paned->priv->handle_size, requisition->width); + } + + break; + + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + requisition->width = MAX (child_requisition->width, requisition->width); + + if (paned->priv->sticky) + { + requisition->height += child_requisition->height; + } + else + { + requisition->height = MAX (child_requisition->height + + paned->priv->button_box_size + + paned->priv->handle_size, requisition->height); + } + + break; + } + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + paned->priv->pane_widget_size = MAX (paned->priv->position, + child_requisition->width); + paned->priv->position = paned->priv->pane_widget_size; + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + paned->priv->pane_widget_size = MAX (paned->priv->position, + child_requisition->height); + paned->priv->position = paned->priv->pane_widget_size; + break; + } +} + + +static void moo_paned_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GtkBin *bin = GTK_BIN (widget); + MooPaned *paned = MOO_PANED (widget); + GtkRequisition child_requisition; + + requisition->width = 0; + requisition->height = 0; + + if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) + { + gtk_widget_size_request (bin->child, &child_requisition); + requisition->width += child_requisition.width; + requisition->height += child_requisition.height; + } + + if (paned->priv->button_box_visible) + { + gtk_widget_size_request (paned->button_box, &child_requisition); + add_button_box_requisition (paned, requisition, &child_requisition); + } + else + { + paned->priv->button_box_size = 0; + } + + if (paned->priv->handle_visible) + add_handle_requisition (paned, requisition); + else + paned->priv->handle_size = 0; + + if (paned->priv->pane_widget_visible) + { + gtk_widget_size_request (paned->priv->current_pane->widget, &child_requisition); + add_pane_widget_requisition (paned, requisition, &child_requisition); + } + else + { + paned->priv->pane_widget_size = 0; + } +} + + +static void get_pane_widget_allocation (MooPaned *paned, + GtkAllocation *allocation) +{ + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + allocation->x = 0; + allocation->y = 0; + allocation->width = paned->priv->pane_widget_size; + allocation->height = GTK_WIDGET(paned)->allocation.height; + break; + case GTK_POS_RIGHT: + allocation->x = paned->priv->handle_size; + allocation->y = 0; + allocation->width = paned->priv->pane_widget_size; + allocation->height = GTK_WIDGET(paned)->allocation.height; + break; + case GTK_POS_TOP: + allocation->x = 0; + allocation->y = 0; + allocation->width = GTK_WIDGET(paned)->allocation.width; + allocation->height = paned->priv->pane_widget_size; + break; + case GTK_POS_BOTTOM: + allocation->x = 0; + allocation->y = paned->priv->handle_size; + allocation->width = GTK_WIDGET(paned)->allocation.width; + allocation->height = paned->priv->pane_widget_size; + break; + } +} + + +static void get_button_box_allocation (MooPaned *paned, + GtkAllocation *allocation) +{ + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + allocation->y = 0; + allocation->height = GTK_WIDGET(paned)->allocation.height; + allocation->width = paned->priv->button_box_size; + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + allocation->x = 0; + allocation->width = GTK_WIDGET(paned)->allocation.width; + allocation->height = paned->priv->button_box_size; + break; + } + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + allocation->x = 0; + break; + case GTK_POS_RIGHT: + allocation->x = GTK_WIDGET(paned)->allocation.width - + allocation->width; + break; + case GTK_POS_TOP: + allocation->y = 0; + break; + case GTK_POS_BOTTOM: + allocation->y = GTK_WIDGET(paned)->allocation.height - + allocation->height; + break; + } +} + + +static void get_bin_child_allocation (MooPaned *paned, + GtkAllocation *allocation) +{ + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + allocation->y = 0; + allocation->height = GTK_WIDGET(paned)->allocation.height; + allocation->width = GTK_WIDGET(paned)->allocation.width - + paned->priv->button_box_size - + paned->priv->handle_size; + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + allocation->x = 0; + allocation->width = GTK_WIDGET(paned)->allocation.width; + allocation->height = GTK_WIDGET(paned)->allocation.height - + paned->priv->button_box_size - + paned->priv->handle_size; + break; + } + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + allocation->x = paned->priv->button_box_size + + paned->priv->handle_size; + break; + case GTK_POS_RIGHT: + allocation->x = 0; + break; + case GTK_POS_TOP: + allocation->y = paned->priv->button_box_size + + paned->priv->handle_size; + break; + case GTK_POS_BOTTOM: + allocation->y = 0; + break; + } + + if (paned->priv->sticky) + { + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + allocation->x += paned->priv->pane_widget_size; + allocation->width -= paned->priv->pane_widget_size; + break; + case GTK_POS_RIGHT: + allocation->width -= paned->priv->pane_widget_size; + break; + case GTK_POS_TOP: + allocation->y += paned->priv->pane_widget_size; + allocation->height -= paned->priv->pane_widget_size; + break; + case GTK_POS_BOTTOM: + allocation->height -= paned->priv->pane_widget_size; + break; + } + } +} + + +static void clamp_handle_size (MooPaned *paned) +{ + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + paned->priv->handle_size = CLAMP (paned->priv->handle_size, 0, + GTK_WIDGET(paned)->allocation.width); + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + paned->priv->handle_size = CLAMP (paned->priv->handle_size, 0, + GTK_WIDGET(paned)->allocation.height); + break; + } +} + + +static void clamp_button_box_size (MooPaned *paned) +{ + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + paned->priv->button_box_size = CLAMP (paned->priv->button_box_size, 0, + GTK_WIDGET(paned)->allocation.width - + paned->priv->handle_size); + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + paned->priv->button_box_size = CLAMP (paned->priv->button_box_size, 0, + GTK_WIDGET(paned)->allocation.height - + paned->priv->handle_size); + break; + } +} + + +static void clamp_child_requisition (MooPaned *paned, + GtkRequisition *requisition) +{ + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + requisition->width = CLAMP (requisition->width, 0, + GTK_WIDGET(paned)->allocation.width - + paned->priv->handle_size - + paned->priv->button_box_size); + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + requisition->height = CLAMP (requisition->height, 0, + GTK_WIDGET(paned)->allocation.height - + paned->priv->handle_size - + paned->priv->button_box_size); + break; + } +} + + +static void clamp_pane_widget_size (MooPaned *paned, + GtkRequisition *child_requisition) +{ + int min_size, max_size; + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + max_size = GTK_WIDGET(paned)->allocation.width - + paned->priv->handle_size - + paned->priv->button_box_size; + if (paned->priv->sticky) + max_size -= child_requisition->width; + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + max_size = GTK_WIDGET(paned)->allocation.height - + paned->priv->handle_size - + paned->priv->button_box_size; + if (paned->priv->sticky) + max_size -= child_requisition->height; + break; + } + + min_size = CLAMP (MIN_PANE_SIZE, 0, max_size); + + paned->priv->pane_widget_size = + CLAMP (paned->priv->pane_widget_size, min_size, max_size); +} + + +static void get_pane_window_rect (MooPaned *paned, + GdkRectangle *rect) +{ + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + rect->width = paned->priv->pane_widget_size + paned->priv->handle_size; + rect->height = GTK_WIDGET(paned)->allocation.height; + rect->y = 0; + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + rect->height = paned->priv->pane_widget_size + paned->priv->handle_size; + rect->width = GTK_WIDGET(paned)->allocation.width; + rect->x = 0; + break; + } + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + rect->x = paned->priv->button_box_size; + break; + case GTK_POS_RIGHT: + rect->x = GTK_WIDGET(paned)->allocation.width - + rect->width - + paned->priv->button_box_size; + break; + case GTK_POS_TOP: + rect->y = paned->priv->button_box_size; + break; + case GTK_POS_BOTTOM: + rect->y = GTK_WIDGET(paned)->allocation.height - + rect->height - + paned->priv->button_box_size; + break; + } +} + + +static void get_handle_window_rect (MooPaned *paned, + GdkRectangle *rect) +{ + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + rect->y = 0; + rect->width = paned->priv->handle_size; + rect->height = GTK_WIDGET(paned)->allocation.height; + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + rect->x = 0; + rect->height = paned->priv->handle_size; + rect->width = GTK_WIDGET(paned)->allocation.width; + break; + } + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + rect->x = paned->priv->pane_widget_size; + break; + case GTK_POS_RIGHT: + rect->x = 0; + break; + case GTK_POS_TOP: + rect->y = paned->priv->pane_widget_size; + break; + case GTK_POS_BOTTOM: + rect->y = 0; + break; + } +} + + +static void moo_paned_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkBin *bin; + MooPaned *paned; + GtkAllocation child_allocation; + GtkRequisition child_requisition = {0, 0}; + + widget->allocation = *allocation; + bin = GTK_BIN (widget); + paned = MOO_PANED (widget); + + if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) + gtk_widget_get_child_requisition (bin->child, &child_requisition); + + if (paned->priv->handle_visible) + clamp_handle_size (paned); + + if (paned->priv->button_box_visible) + clamp_button_box_size (paned); + + clamp_child_requisition (paned, &child_requisition); + + if (paned->priv->pane_widget_visible) + { + clamp_pane_widget_size (paned, &child_requisition); + paned->priv->position = paned->priv->pane_widget_size; + } + + if (GTK_WIDGET_REALIZED (widget)) + { + GdkRectangle rect; + + gdk_window_move_resize (widget->window, + allocation->x, + allocation->y, + allocation->width, + allocation->height); + + if (paned->priv->pane_window) + { + get_pane_window_rect (paned, &rect); + gdk_window_move_resize (paned->priv->pane_window, + rect.x, rect.y, + rect.width, rect.height); + } + + if (paned->priv->handle_visible) + { + get_handle_window_rect (paned, &rect); + gdk_window_move_resize (paned->priv->handle_window, + rect.x, rect.y, + rect.width, rect.height); + } + } + + if (paned->priv->button_box_visible) + { + get_button_box_allocation (paned, &child_allocation); + gtk_widget_size_allocate (paned->button_box, &child_allocation); + } + + if (bin->child) + { + get_bin_child_allocation (paned, &child_allocation); + gtk_widget_size_allocate (bin->child, &child_allocation); + } + + if (paned->priv->pane_widget_visible) + { + get_pane_widget_allocation (paned, &child_allocation); + gtk_widget_size_allocate (paned->priv->current_pane->widget, + &child_allocation); + } +} + + +static void moo_paned_map (GtkWidget *widget) +{ + MooPaned *paned = MOO_PANED (widget); + + gdk_window_show (widget->window); + + (* GTK_WIDGET_CLASS (moo_paned_parent_class)->map) (widget); + + if (paned->priv->handle_visible) + { + gdk_window_show (paned->priv->pane_window); + gdk_window_show (paned->priv->handle_window); + gdk_window_raise (paned->priv->pane_window); + } +} + + +static void moo_paned_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + MooPaned *paned = MOO_PANED (container); + GtkBin *bin = GTK_BIN (container); + GSList *l; + + if (bin->child) + callback (bin->child, callback_data); + + if (include_internals) + { + callback (paned->button_box, callback_data); + + for (l = paned->priv->panes; l != NULL; l = l->next) + callback (((Pane*)l->data)->widget, callback_data); + } +} + + +static gboolean moo_paned_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + MooPaned *paned = MOO_PANED (widget); + + if (paned->priv->button_box_visible) + gtk_container_propagate_expose (GTK_CONTAINER (paned), + paned->button_box, event); + + if (GTK_WIDGET_DRAWABLE (GTK_BIN(paned)->child)) + gtk_container_propagate_expose (GTK_CONTAINER (paned), + GTK_BIN(paned)->child, event); + + if (paned->priv->pane_widget_visible) + gtk_container_propagate_expose (GTK_CONTAINER (paned), + paned->priv->current_pane->widget, + event); + + if (paned->priv->handle_visible) + draw_handle (paned); + + return FALSE; +} + + +static void draw_handle (MooPaned *paned) +{ + GtkWidget *widget = GTK_WIDGET (paned); + GtkStateType state; + + GdkRectangle area; + + area.x = 0; + area.y = 0; + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + area.width = paned->priv->handle_size; + area.height = widget->allocation.height; + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + area.width = widget->allocation.width; + area.height = paned->priv->handle_size; + break; + } + + if (gtk_widget_is_focus (widget)) + state = GTK_STATE_SELECTED; + else if (paned->priv->handle_prelit) + state = GTK_STATE_PRELIGHT; + else + state = GTK_WIDGET_STATE (widget); + + gtk_paint_handle (widget->style, + paned->priv->handle_window, + state, + GTK_SHADOW_NONE, + &area, + widget, + "paned", + area.x, area.y, area.width, area.height, + GTK_ORIENTATION_VERTICAL); +} + + +static void button_toggled (GtkToggleButton *button, + MooPaned *paned) +{ + Pane *pane; + + if (!gtk_toggle_button_get_active (button)) + { + if (paned->priv->current_pane && + paned->priv->current_pane->button == GTK_WIDGET (button)) + { + gtk_widget_hide (paned->priv->current_pane->widget); + paned->priv->current_pane = NULL; + paned->priv->pane_widget_visible = FALSE; + paned->priv->pane_widget_size = 0; + gtk_widget_queue_resize (GTK_WIDGET (paned)); + } + + return; + } + + if (paned->priv->current_pane) + { + Pane *old_pane = paned->priv->current_pane; + paned->priv->current_pane = NULL; + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (old_pane->button), FALSE); + gtk_widget_hide (old_pane->widget); + } + + pane = g_object_get_data (G_OBJECT (button), "moo-pane"); + g_return_if_fail (pane != NULL); + + if (GTK_WIDGET_REALIZED (paned)) + { + gtk_widget_set_parent_window (pane->widget, + paned->priv->pane_window); + gtk_widget_queue_resize (GTK_WIDGET (paned)); + } + + paned->priv->current_pane = pane; + gtk_widget_show (pane->widget); + + paned->priv->handle_visible = TRUE; + gtk_widget_style_get (GTK_WIDGET (paned), + "handle_size", &paned->priv->handle_size, + NULL); + paned->priv->pane_widget_visible = TRUE; + if (paned->priv->position > 0) + paned->priv->pane_widget_size = paned->priv->position; +} + + +void moo_paned_set_sticky_pane (MooPaned *paned, + gboolean sticky) +{ + g_return_if_fail (MOO_IS_PANED (paned)); + if (paned->priv->sticky != sticky && GTK_WIDGET_REALIZED (paned)) + gtk_widget_queue_resize (GTK_WIDGET (paned)); + paned->priv->sticky = sticky; + g_object_notify (G_OBJECT (paned), "sticky-pane"); +} + + +void moo_paned_hide_pane (MooPaned *paned) +{ + GtkToggleButton *btn; + g_return_if_fail (MOO_IS_PANED (paned)); + if (paned->priv->current_pane) + { + btn = GTK_TOGGLE_BUTTON (paned->priv->current_pane->button); + gtk_toggle_button_set_active (btn, FALSE); + } +} + + +void moo_paned_open_pane (MooPaned *paned, + guint index) +{ + Pane *pane; + g_return_if_fail (MOO_IS_PANED (paned)); + pane = g_slist_nth_data (paned->priv->panes, index); + g_return_if_fail (pane != NULL); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (pane->button), + TRUE); +} + + +GtkWidget *moo_paned_get_nth_pane (MooPaned *paned, + guint n) +{ + Pane *pane; + g_return_val_if_fail (MOO_IS_PANED (paned), NULL); + pane = g_slist_nth_data (paned->priv->panes, n); + g_return_val_if_fail (pane != NULL, NULL); + return pane->widget; +} + + +static gboolean moo_paned_motion (GtkWidget *widget, + G_GNUC_UNUSED GdkEventMotion *event) +{ + MooPaned *paned = MOO_PANED (widget); + + if (paned->priv->in_drag) + { + int size; + GtkRequisition requisition; + + gtk_widget_get_child_requisition (paned->priv->current_pane->widget, + &requisition); + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + gtk_widget_get_pointer (widget, &size, NULL); + size -= (paned->priv->drag_start + paned->priv->button_box_size); + size = CLAMP (size, requisition.width, + widget->allocation.width - paned->priv->button_box_size - + paned->priv->handle_size); + break; + case GTK_POS_RIGHT: + gtk_widget_get_pointer (widget, &size, NULL); + size = widget->allocation.width - size; + size -= (paned->priv->drag_start + paned->priv->button_box_size); + size = CLAMP (size, requisition.width, + widget->allocation.width - paned->priv->button_box_size - + paned->priv->handle_size); + break; + case GTK_POS_TOP: + gtk_widget_get_pointer (widget, NULL, &size); + size -= (paned->priv->drag_start + paned->priv->button_box_size); + size = CLAMP (size, requisition.height, + widget->allocation.height - paned->priv->button_box_size - + paned->priv->handle_size); + break; + case GTK_POS_BOTTOM: + gtk_widget_get_pointer (widget, NULL, &size); + size = widget->allocation.height - size; + size -= (paned->priv->drag_start + paned->priv->button_box_size); + size = CLAMP (size, requisition.height, + widget->allocation.height - paned->priv->button_box_size - + paned->priv->handle_size); + break; + } + + if (size != paned->priv->pane_widget_size) + moo_paned_set_pane_size (paned, size); + } + + return FALSE; +} + + +static void get_handle_rect (MooPaned *paned, + GdkRectangle *rect) +{ + rect->x = rect->y = 0; + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + rect->width = paned->priv->handle_size; + rect->height = GTK_WIDGET(paned)->allocation.height; + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + rect->height = paned->priv->handle_size; + rect->width = GTK_WIDGET(paned)->allocation.width; + break; + } +} + + +static gboolean moo_paned_enter (GtkWidget *widget, + GdkEventCrossing *event) +{ + MooPaned *paned = MOO_PANED (widget); + GdkRectangle rect; + + if (event->window == paned->priv->handle_window && + !paned->priv->in_drag) + { + paned->priv->handle_prelit = TRUE; + get_handle_rect (paned, &rect); + gdk_window_invalidate_rect (paned->priv->handle_window, + &rect, FALSE); + return TRUE; + } + + return FALSE; +} + + +static gboolean moo_paned_leave (GtkWidget *widget, + GdkEventCrossing *event) +{ + MooPaned *paned = MOO_PANED (widget); + GdkRectangle rect; + + if (event->window == paned->priv->handle_window && + !paned->priv->in_drag) + { + paned->priv->handle_prelit = FALSE; + get_handle_rect (paned, &rect); + gdk_window_invalidate_rect (paned->priv->handle_window, + &rect, FALSE); + return TRUE; + } + + return FALSE; +} + + +static gboolean moo_paned_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + MooPaned *paned = MOO_PANED (widget); + + if (!paned->priv->in_drag && + (event->window == paned->priv->handle_window) && + (event->button == 1) && + paned->priv->pane_widget_visible) + { + paned->priv->in_drag = TRUE; + + /* This is copied from gtkpaned.c */ + gdk_pointer_grab (paned->priv->handle_window, FALSE, + GDK_POINTER_MOTION_HINT_MASK + | GDK_BUTTON1_MOTION_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_ENTER_NOTIFY_MASK + | GDK_LEAVE_NOTIFY_MASK, + NULL, NULL, + event->time); + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + paned->priv->drag_start = event->x; + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + paned->priv->drag_start = event->y; + break; + } + + return TRUE; + } + + return FALSE; +} + + +static gboolean moo_paned_button_release(GtkWidget *widget, + GdkEventButton *event) +{ + MooPaned *paned = MOO_PANED (widget); + + if (paned->priv->in_drag && (event->button == 1)) + { + paned->priv->in_drag = FALSE; + paned->priv->drag_start = -1; + gdk_display_pointer_ungrab (gtk_widget_get_display (widget), + event->time); + return TRUE; + } + + return FALSE; +} + + +void moo_paned_set_pane_size (MooPaned *paned, + int size) +{ + g_return_if_fail (MOO_IS_PANED (paned)); + + if (GTK_WIDGET_REALIZED (paned)) + { + GtkWidget *widget = GTK_WIDGET (paned); + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + size = CLAMP (size, 0, + widget->allocation.width - paned->priv->button_box_size - + paned->priv->handle_size); + break; + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + size = CLAMP (size, 0, + widget->allocation.height - paned->priv->button_box_size - + paned->priv->handle_size); + break; + } + + if (size != paned->priv->position) + { + paned->priv->position = size; + if (paned->priv->pane_widget_visible) + gtk_widget_queue_resize (widget); + } + } + else + { + paned->priv->position = size; + } +} + + +static void button_box_visible_notify (MooPaned *paned) +{ + gboolean visible = GTK_WIDGET_VISIBLE (paned->button_box); + + if (paned->priv->button_box_visible == visible) + return; + + if (paned->priv->panes) + paned->priv->button_box_visible = visible; + + if (GTK_WIDGET_REALIZED (paned)) + gtk_widget_queue_resize (GTK_WIDGET (paned)); +} + + +void moo_paned_add_pane (MooPaned *paned, + GtkWidget *pane_widget, + const char *button_label, + const char *button_stock_id) +{ + GtkWidget *box; + GtkWidget *label = NULL; + GtkWidget *icon = NULL; + + g_return_if_fail (MOO_IS_PANED (paned)); + g_return_if_fail (GTK_IS_WIDGET (pane_widget)); + g_return_if_fail (pane_widget->parent == NULL); + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + box = gtk_vbox_new (FALSE, SPACING_IN_BUTTON); + break; + default: + box = gtk_hbox_new (FALSE, SPACING_IN_BUTTON); + break; + } + + gtk_widget_show (box); + + if (button_label) + { + label = gtk_label_new (button_label); + gtk_widget_show (label); + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + gtk_label_set_angle (GTK_LABEL (label), 90); + break; + case GTK_POS_RIGHT: + gtk_label_set_angle (GTK_LABEL (label), 270); + break; + default: + break; + } + } + + if (button_stock_id) + { + icon = gtk_image_new_from_stock (button_stock_id, + GTK_ICON_SIZE_MENU); + gtk_widget_show (icon); + } + + switch (paned->priv->pane_position) + { + case GTK_POS_LEFT: + if (label) + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + if (icon) + gtk_box_pack_start (GTK_BOX (box), icon, FALSE, FALSE, 0); + break; + default: + if (icon) + gtk_box_pack_start (GTK_BOX (box), icon, FALSE, FALSE, 0); + if (label) + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + break; + } + + moo_paned_insert_pane (paned, pane_widget, box, -1); +} + + +void moo_paned_insert_pane (MooPaned *paned, + GtkWidget *pane_widget, + GtkWidget *button_widget, + int position) +{ + GtkWidget *button; + Pane *pane; + + g_return_if_fail (MOO_IS_PANED (paned)); + g_return_if_fail (GTK_IS_WIDGET (pane_widget)); + g_return_if_fail (GTK_IS_WIDGET (button_widget)); + g_return_if_fail (pane_widget->parent == NULL); + g_return_if_fail (button_widget->parent == NULL); + + button = gtk_toggle_button_new (); + gtk_widget_show (button); + gtk_container_add (GTK_CONTAINER (button), button_widget); + gtk_widget_show (button_widget); + + if (position < 0 || position > (int) moo_paned_n_panes (paned)) + position = moo_paned_n_panes (paned); + + gtk_container_add_with_properties (GTK_CONTAINER (paned->button_box), + button, + "expand", FALSE, + "fill", FALSE, + "pack-type", GTK_PACK_START, + "position", position, + NULL); + + gtk_widget_set_parent (pane_widget, GTK_WIDGET (paned)); + gtk_object_sink (gtk_object_ref (GTK_OBJECT (pane_widget))); + + pane = g_new (Pane, 1); + pane->widget = pane_widget; + pane->button = button; + paned->priv->panes = g_slist_insert (paned->priv->panes, + pane, position); + + g_object_set_data (G_OBJECT (button), "moo-pane", pane); + g_object_set_data (G_OBJECT (pane_widget), "moo-pane", pane); + g_signal_connect (button, "toggled", + G_CALLBACK (button_toggled), paned); + + if (GTK_WIDGET_VISIBLE (paned->button_box)) + paned->priv->button_box_visible = TRUE; + + paned->priv->handle_visible = TRUE; + gtk_widget_style_get (GTK_WIDGET (paned), + "handle_size", &paned->priv->handle_size, + NULL); + + if (GTK_WIDGET_VISIBLE (paned)) + gtk_widget_queue_resize (GTK_WIDGET (paned)); + + if (GTK_WIDGET_REALIZED (paned)) + gtk_widget_set_parent_window (pane_widget, + paned->priv->pane_window); +} + + +static void moo_paned_remove (GtkContainer *container, + GtkWidget *widget) +{ + MooPaned *paned = MOO_PANED (container); + + if (widget == GTK_BIN(paned)->child) + GTK_CONTAINER_CLASS(moo_paned_parent_class)->remove (container, widget); + else + moo_paned_remove_pane (paned, widget); +} + + +static void moo_paned_remove_pane (MooPaned *paned, + GtkWidget *pane_widget) +{ + Pane *pane; + + g_return_if_fail (MOO_IS_PANED (paned)); + g_return_if_fail (GTK_IS_WIDGET (pane_widget)); + + pane = g_object_get_data (G_OBJECT (pane_widget), "moo-pane"); + g_return_if_fail (pane != NULL); + g_return_if_fail (pane->widget == pane_widget); + + if (paned->priv->current_pane && + paned->priv->current_pane->widget == pane_widget) + { + moo_paned_hide_pane (paned); + } + + g_object_set_data (G_OBJECT (pane->button), "moo-pane", NULL); + g_object_set_data (G_OBJECT (pane_widget), "moo-pane", NULL); + g_signal_handlers_disconnect_by_func (pane->button, + (gpointer) button_toggled, + paned); + + gtk_container_remove (GTK_CONTAINER (paned->button_box), pane->button); + paned->priv->panes = g_slist_remove (paned->priv->panes, pane); + + gtk_widget_unparent (pane_widget); + + if (!moo_paned_n_panes (paned)) + { + paned->priv->handle_visible = FALSE; + paned->priv->handle_size = 0; + if (paned->priv->pane_window) + gdk_window_hide (paned->priv->pane_window); + gtk_widget_hide (paned->button_box); + } + + if (GTK_WIDGET_VISIBLE (paned)) + gtk_widget_queue_resize (GTK_WIDGET (paned)); +} + + +guint moo_paned_n_panes (MooPaned *paned) +{ + g_return_val_if_fail (MOO_IS_PANED (paned), 0); + return g_slist_length (paned->priv->panes); +} + + +static void moo_paned_set_focus_child (GtkContainer *container, + GtkWidget *widget) +{ + MooPaned *paned = MOO_PANED (container); + + GTK_CONTAINER_CLASS(moo_paned_parent_class)->set_focus_child (container, widget); + + if (widget == GTK_BIN(paned)->child && + paned->priv->close_on_child_focus && + !paned->priv->sticky) + { + moo_paned_hide_pane (paned); + } +} diff --git a/moo/mooutils/moopaned.h b/moo/mooutils/moopaned.h new file mode 100644 index 00000000..3727a51e --- /dev/null +++ b/moo/mooutils/moopaned.h @@ -0,0 +1,78 @@ +/* + * mooutils/moopaned.h + * + * Copyright (C) 2004-2005 by Yevgen Muntyan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * See COPYING file that comes with this distribution. + */ + +#ifndef MOOUTILS_MOOPANED_H +#define MOOUTILS_MOOPANED_H + +#include + +G_BEGIN_DECLS + + +#define MOO_TYPE_PANED (moo_paned_get_type ()) +#define MOO_PANED(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), MOO_TYPE_PANED, MooPaned)) +#define MOO_PANED_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MOO_TYPE_PANED, MooPanedClass)) +#define MOO_IS_PANED(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), MOO_TYPE_PANED)) +#define MOO_IS_PANED_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MOO_TYPE_PANED)) +#define MOO_PANED_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MOO_TYPE_PANED, MooPanedClass)) + + +typedef struct _MooPaned MooPaned; +typedef struct _MooPanedPrivate MooPanedPrivate; +typedef struct _MooPanedClass MooPanedClass; + +struct _MooPaned +{ + GtkBin bin; + GtkWidget *button_box; + MooPanedPrivate *priv; +}; + +struct _MooPanedClass +{ + GtkBinClass bin_class; +}; + + +GType moo_paned_get_type (void) G_GNUC_CONST; + +GtkWidget *moo_paned_new (GtkPositionType pane_position); + +void moo_paned_add_pane (MooPaned *paned, + GtkWidget *pane_widget, + const char *button_label, + const char *button_stock_id); +void moo_paned_insert_pane (MooPaned *paned, + GtkWidget *pane_widget, + GtkWidget *button_widget, + int position); + +guint moo_paned_n_panes (MooPaned *paned); + +GtkWidget *moo_paned_get_nth_pane (MooPaned *paned, + guint n); + +void moo_paned_set_sticky_pane (MooPaned *paned, + gboolean sticky); + +void moo_paned_set_pane_size (MooPaned *paned, + int size); + +void moo_paned_open_pane (MooPaned *paned, + guint index); +void moo_paned_hide_pane (MooPaned *paned); + + +G_END_DECLS + +#endif /* MOOUTILS_MOOPANED_H */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 06eac13a..9c3c01a3 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -3,14 +3,14 @@ # EXTRA_PROGRAMS = medit mterm markup editor termbuffer -bin_PROGRAMS = +bin_PROGRAMS = noinst_PROGRAMS = EXTRA_DIST = \ pyapp.py.in \ meditui.xml \ editor-ui.xml - + BUILT_SOURCES = editor-ui.h editor-ui.h: editor-ui.xml sh $(srcdir)/../moo/mooutils/xml2h.sh MEDIT_UI $(srcdir)/editor-ui.xml > editor-ui.h @@ -27,7 +27,7 @@ if BUILD_MOOTERM noinst_PROGRAMS += mterm termbuffer endif if BUILD_MOOUTILS -noinst_PROGRAMS += markup +noinst_PROGRAMS += markup testpaned testfileview endif @@ -104,3 +104,18 @@ markup_LDFLAGS += -mwindows endif MINGW_BUILD markup_SOURCES = markup.c + + +testpaned_LDFLAGS = +testpaned_LDADD = $(MOO_LIBS) ../moo/libmoo.la +if MINGW_BUILD +testpaned_LDFLAGS += -mwindows +endif MINGW_BUILD +testpaned_SOURCES = testpaned.c + +testfileview_LDFLAGS = +testfileview_LDADD = $(MOO_LIBS) ../moo/libmoo.la +if MINGW_BUILD +testfileview_LDFLAGS += -mwindows +endif MINGW_BUILD +testfileview_SOURCES = testfileview.c diff --git a/tests/testfileview.c b/tests/testfileview.c new file mode 100644 index 00000000..d4849287 --- /dev/null +++ b/tests/testfileview.c @@ -0,0 +1,25 @@ +#include "mooutils/moofileview.h" + + +int main (int argc, char *argv[]) +{ + GtkWidget *window, *tree; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + g_signal_connect (window, "destroy", + G_CALLBACK (gtk_main_quit), NULL); + gtk_window_set_default_size (GTK_WINDOW (window), -1, 500); + + tree = moo_file_view_new (); + gtk_container_add (GTK_CONTAINER (window), tree); + + moo_file_view_chdir (MOO_FILE_VIEW (tree), + g_get_home_dir (), NULL);; + + gtk_widget_show_all (window); + + gtk_main (); + return 0; +} diff --git a/tests/testpaned.c b/tests/testpaned.c new file mode 100644 index 00000000..275b0c72 --- /dev/null +++ b/tests/testpaned.c @@ -0,0 +1,116 @@ +#include "mooutils/moopaned.h" + + +static int WINDOWS = 0; + +static void window_destroyed (void) +{ + if (!--WINDOWS) gtk_main_quit (); +} + + +static void sticky_button_toggled (GtkToggleButton *button, + MooPaned *paned) +{ + gboolean active = gtk_toggle_button_get_active (button); + moo_paned_set_sticky_pane (paned, active); +} + + +static void create_window_with_paned (GtkPositionType pane_position) +{ + GtkWidget *window, *paned, *textview, *button, *label, *swin; + GtkTextBuffer *buffer; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size (GTK_WINDOW (window), 400, 300); + g_signal_connect (window, "destroy", + G_CALLBACK (window_destroyed), NULL); + WINDOWS++; + + paned = moo_paned_new (pane_position); + gtk_widget_show (paned); + gtk_container_add (GTK_CONTAINER (window), paned); + + textview = gtk_text_view_new (); + gtk_widget_show (textview); + swin = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_widget_show (swin); + gtk_container_add (GTK_CONTAINER (paned), swin); + gtk_container_add (GTK_CONTAINER (swin), textview); + + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (textview), GTK_WRAP_WORD); + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview)); + gtk_text_buffer_insert_at_cursor (buffer, "Click a button. Click a button. " + "Click a button. Click a button. Click a button. Click a button. " + "Click a button. Click a button. Click a button. Click a button. " + "Click a button. Click a button. Click a button. Click a button. " + "Click a button. Click a button. Click a button. Click a button. " + "Click a button. Click a button. Click a button. Click a button. " + "Click a button. Click a button. Click a button. Click a button. " + "Click a button. Click a button. Click a button. Click a button. " + "Click a button. Click a button. Click a button. Click a button. " + "Click a button. Click a button. Click a button. Click a button. " + "Click a button. Click a button. Click a button. Click a button. " + "Click a button. Click a button. Click a button. Click a button. ", + -1); + + textview = gtk_text_view_new (); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (textview), GTK_WRAP_WORD); + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview)); + gtk_text_buffer_insert_at_cursor (buffer, "Hi there. Hi there. " + "Hi there. Hi there. Hi there. Hi there. Hi there. ", -1); + moo_paned_add_pane (MOO_PANED (paned), + textview, + "TextView", + GTK_STOCK_OK); + moo_paned_add_pane (MOO_PANED (paned), + gtk_label_new ("This is a label"), + "Label", + GTK_STOCK_CANCEL); + + button = gtk_toggle_button_new (); + gtk_widget_show (button); + g_signal_connect (button, "toggled", + G_CALLBACK (sticky_button_toggled), + paned); + gtk_box_pack_end (GTK_BOX (MOO_PANED(paned)->button_box), + button, FALSE, FALSE, 0); + + label = gtk_label_new ("Sticky"); + switch (pane_position) + { + case GTK_POS_LEFT: + gtk_label_set_angle (GTK_LABEL (label), 90); + break; + case GTK_POS_RIGHT: + gtk_label_set_angle (GTK_LABEL (label), 270); + break; + default: + break; + } + + gtk_widget_show (label); + gtk_container_add (GTK_CONTAINER (button), label); + + gtk_widget_show_all (window); +} + + +int main (int argc, char *argv[]) +{ + gtk_init (&argc, &argv); + +// gdk_window_set_debug_updates (TRUE); + + create_window_with_paned (GTK_POS_RIGHT); + create_window_with_paned (GTK_POS_LEFT); + create_window_with_paned (GTK_POS_TOP); + create_window_with_paned (GTK_POS_BOTTOM); + + gtk_main (); + return 0; +}