/* * moopane.c * * Copyright (C) 2004-2010 by Yevgen Muntyan * * This file is part of medit. medit is free software; you can * redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the * Free Software Foundation; either version 2.1 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public * License along with medit. If not, see . */ /** * class:MooPane: (parent GtkObject) (moo.lua 0) (moo.private 1) **/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "marshals.h" #include "moopaned.h" #include "moo-pixbufs.h" #include #include #include #define SPACING_IN_BUTTON 4 #define OPEN_PANE_TIMEOUT 200 #include "mooutils-misc.h" #include "moocompat.h" #include "moohelp.h" #include "mooutils-gobject.h" #include "mooi18n.h" #include "moocpp/gobjtypes.h" using namespace moo; struct _MooPane { GtkObject base; char *id; MooPaned *parent; GtkWidget *child; GtkWidget *child_holder; MooPaneLabel *label; GtkWidget *frame; GtkWidget *handle; GtkWidget *small_handle; GtkWidget *button; GtkWidget *label_widget; GtkWidget *icon_widget; GtkWidget *sticky_button; GtkWidget *detach_button; GtkWidget *close_button; GtkWidget *frame_label_embedded; GtkWidget *frame_label_window; char *frame_label_text; gboolean frame_label_markup; /* XXX weak pointer */ gpointer focus_child; GtkWidget *window; GtkWidget *keep_on_top_button; GtkWidget *window_child_holder; MooPaneParams *params; guint open_timeout; guint button_highlight : 1; guint drag_dest_enabled : 1; guint detachable : 1; guint removable : 1; guint params_changed_blocked : 1; }; struct _MooPaneClass { GtkObjectClass base_class; gboolean (*remove) (MooPane *pane); }; G_DEFINE_TYPE (MooPane, moo_pane, GTK_TYPE_OBJECT) enum { PROP_0, PROP_ID, PROP_LABEL, PROP_PARAMS, PROP_DETACHABLE, PROP_REMOVABLE }; enum { REMOVE, NUM_SIGNALS }; static guint signals[NUM_SIGNALS]; static void set_pane_window_icon_and_title (MooPane *pane) { if (pane->window && pane->label) { if (pane->label->icon_pixbuf) gtk_window_set_icon (GTK_WINDOW (pane->window), pane->label->icon_pixbuf); else if (pane->label->icon_stock_id) _moo_window_set_icon_from_stock (GTK_WINDOW (pane->window), pane->label->icon_stock_id); if (pane->label->window_title) gtk_window_set_title (GTK_WINDOW (pane->window), pane->label->window_title); else gtk_window_set_title (GTK_WINDOW (pane->window), pane->label->label); } } static void update_label_widgets (MooPane *pane) { if (pane->label && pane->label_widget) { gtk_label_set_text (GTK_LABEL (pane->label_widget), pane->label->label); g_object_set (pane->label_widget, "visible", pane->label->label != NULL, NULL); } if (pane->label && pane->icon_widget) { if (pane->label->icon_pixbuf) gtk_image_set_from_pixbuf (GTK_IMAGE (pane->icon_widget), pane->label->icon_pixbuf); else if (pane->label->icon_stock_id) gtk_image_set_from_stock (GTK_IMAGE (pane->icon_widget), pane->label->icon_stock_id, GTK_ICON_SIZE_MENU); g_object_set (pane->icon_widget, "visible", pane->label->icon_pixbuf || pane->label->icon_stock_id, NULL); } set_pane_window_icon_and_title (pane); } /** * moo_pane_set_label: **/ void moo_pane_set_label (MooPane *pane, MooPaneLabel *label) { MooPaneLabel *tmp; g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (label != NULL); tmp = pane->label; pane->label = moo_pane_label_copy (label); moo_pane_label_free (tmp); update_label_widgets (pane); g_object_notify (G_OBJECT (pane), "label"); } /** * moo_pane_get_params: **/ MooPaneParams * moo_pane_get_params (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return moo_pane_params_copy (pane->params); } /** * moo_pane_get_label: **/ MooPaneLabel * moo_pane_get_label (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return moo_pane_label_copy (pane->label); } /** * moo_pane_set_params: **/ void moo_pane_set_params (MooPane *pane, MooPaneParams *params) { MooPaneParams *old_params; g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (params != NULL); old_params = pane->params; pane->params = moo_pane_params_copy (params); if (old_params->detached != params->detached) { pane->params->detached = old_params->detached; if (old_params->detached) moo_paned_attach_pane (pane->parent, pane); else moo_paned_detach_pane (pane->parent, pane); } moo_pane_params_free (old_params); g_object_notify (G_OBJECT (pane), "params"); } /** * moo_pane_set_detachable: **/ void moo_pane_set_detachable (MooPane *pane, gboolean detachable) { g_return_if_fail (MOO_IS_PANE (pane)); if (detachable == pane->detachable) return; pane->detachable = detachable != 0; if (pane->params->detached && !detachable) moo_paned_attach_pane (pane->parent, pane); if (pane->detach_button) g_object_set (pane->detach_button, "visible", pane->detachable, NULL); g_object_notify (G_OBJECT (pane), "detachable"); } /** * moo_pane_set_removable: **/ void moo_pane_set_removable (MooPane *pane, gboolean removable) { g_return_if_fail (MOO_IS_PANE (pane)); if (removable == pane->removable) return; pane->removable = removable != 0; if (pane->close_button) g_object_set (pane->close_button, "visible", pane->removable, NULL); g_object_notify (G_OBJECT (pane), "removable"); } /** * moo_pane_get_detachable: **/ gboolean moo_pane_get_detachable (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), FALSE); return pane->detachable; } /** * moo_pane_get_removable: **/ gboolean moo_pane_get_removable (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), FALSE); return pane->removable; } static void moo_pane_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MooPane *pane = MOO_PANE (object); switch (prop_id) { case PROP_LABEL: moo_pane_set_label (pane, reinterpret_cast (g_value_get_boxed (value))); break; case PROP_PARAMS: moo_pane_set_params (pane, reinterpret_cast (g_value_get_boxed (value))); break; case PROP_DETACHABLE: moo_pane_set_detachable (pane, g_value_get_boolean (value)); break; case PROP_REMOVABLE: moo_pane_set_removable (pane, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void moo_pane_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MooPane *pane = MOO_PANE (object); switch (prop_id) { case PROP_ID: g_value_set_string (value, pane->id); break; case PROP_LABEL: g_value_set_boxed (value, pane->label); break; case PROP_PARAMS: g_value_set_boxed (value, pane->params); break; case PROP_DETACHABLE: g_value_set_boolean (value, pane->detachable != 0); break; case PROP_REMOVABLE: g_value_set_boolean (value, pane->removable != 0); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void moo_pane_init (MooPane *pane) { pane->detachable = TRUE; pane->removable = TRUE; pane->params = moo_pane_params_new (NULL, FALSE, FALSE, FALSE); pane->id = NULL; pane->label = NULL; pane->child = NULL; pane->child_holder = NULL; pane->frame = NULL; pane->handle = NULL; pane->small_handle = NULL; pane->button = NULL; pane->label_widget = NULL; pane->icon_widget = NULL; pane->sticky_button = NULL; pane->detach_button = NULL; pane->close_button = NULL; pane->focus_child = NULL; pane->window = NULL; pane->keep_on_top_button = NULL; pane->window_child_holder = NULL; } static void moo_pane_finalize (GObject *object) { MooPane *pane = MOO_PANE (object); g_free (pane->id); moo_pane_label_free (pane->label); moo_pane_params_free (pane->params); G_OBJECT_CLASS (moo_pane_parent_class)->finalize (object); } static void moo_pane_dispose (GObject *object) { MooPane *pane = MOO_PANE (object); if (pane->child) { GtkWidget *tmp = pane->child; pane->child = NULL; g_object_unref (tmp); } if (pane->frame) { gtk_widget_unparent (pane->frame); pane->frame = NULL; } if (pane->window) { gtk_widget_destroy (pane->window); pane->window = NULL; } if (pane->open_timeout) { g_source_remove (pane->open_timeout); pane->open_timeout = 0; } G_OBJECT_CLASS (moo_pane_parent_class)->dispose (object); } static void moo_pane_class_init (MooPaneClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->set_property = moo_pane_set_property; gobject_class->get_property = moo_pane_get_property; gobject_class->finalize = moo_pane_finalize; gobject_class->dispose = moo_pane_dispose; g_object_class_install_property (gobject_class, PROP_ID, g_param_spec_string ("id", "id", "id", NULL, G_PARAM_READABLE)); g_object_class_install_property (gobject_class, PROP_LABEL, g_param_spec_boxed ("label", "label", "label", MOO_TYPE_PANE_LABEL, (GParamFlags) G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_PARAMS, g_param_spec_boxed ("params", "params", "params", MOO_TYPE_PANE_PARAMS, (GParamFlags) G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_DETACHABLE, g_param_spec_boolean ("detachable", "detachable", "detachable", TRUE, (GParamFlags) G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_REMOVABLE, g_param_spec_boolean ("removable", "removable", "removable", TRUE, (GParamFlags) G_PARAM_READWRITE)); signals[REMOVE] = g_signal_new ("remove", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (MooPaneClass, remove), g_signal_accumulator_true_handled, NULL, _moo_marshal_BOOL__VOID, G_TYPE_BOOLEAN, 0); } static void close_button_clicked (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); if (pane->parent) _moo_pane_try_remove (pane); } static void hide_button_clicked (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); if (pane->parent) moo_paned_hide_pane (pane->parent); } static void attach_button_clicked (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); if (pane->parent) _moo_paned_attach_pane (pane->parent, pane); } static void detach_button_clicked (MooPane *pane) { moo_paned_detach_pane (pane->parent, pane); } static void sticky_button_toggled (GtkToggleButton *button, MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); moo_paned_set_sticky_pane (pane->parent, gtk_toggle_button_get_active (button)); } static void update_sticky_button (MooPane *pane) { if (pane->parent) { gboolean sticky, active; g_object_get (pane->parent, "sticky-pane", &sticky, NULL); g_object_get (pane->sticky_button, "active", &active, NULL); if (active != sticky) g_object_set (pane->sticky_button, "active", sticky, NULL); } } /** * moo_pane_set_frame_markup: * * @pane: * @markup: (type const-utf8) (allow-none): **/ void moo_pane_set_frame_markup (MooPane *pane, const char *text) { char *tmp; g_return_if_fail (MOO_IS_PANE (pane)); if (!text) { moo_pane_set_frame_text (pane, NULL); return; } tmp = pane->frame_label_text; pane->frame_label_text = g_strdup (text); pane->frame_label_markup = TRUE; g_free (tmp); if (pane->frame_label_embedded) gtk_label_set_markup (GTK_LABEL (pane->frame_label_embedded), text); if (pane->frame_label_window) gtk_label_set_markup (GTK_LABEL (pane->frame_label_window), text); } /** * moo_pane_set_frame_text: * * @pane: * @text: (type const-utf8) (allow-none): **/ void moo_pane_set_frame_text (MooPane *pane, const char *text) { char *tmp; g_return_if_fail (MOO_IS_PANE (pane)); tmp = pane->frame_label_text; pane->frame_label_text = g_strdup (text); pane->frame_label_markup = FALSE; g_free (tmp); if (pane->frame_label_embedded) gtk_label_set_text (GTK_LABEL (pane->frame_label_embedded), text); if (pane->frame_label_window) gtk_label_set_text (GTK_LABEL (pane->frame_label_window), text); } static GtkWidget * create_button (MooPane *pane, GtkWidget *toolbar, const char *tip, gboolean toggle, int padding, MooSmallIcon icon) { GtkWidget *button; GtkWidget *icon_widget; if (toggle) button = gtk_toggle_button_new (); else button = gtk_button_new (); g_object_set_data (G_OBJECT (button), "moo-pane", pane); gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE); gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); _moo_widget_set_tooltip (button, tip); icon_widget = _moo_create_small_icon (icon); gtk_container_add (GTK_CONTAINER (button), icon_widget); gtk_box_pack_end (GTK_BOX (toolbar), button, FALSE, FALSE, padding); gtk_widget_show_all (button); return button; } static GtkWidget * create_frame_widget (MooPane *pane, MooPanePosition position, gboolean embedded) { GtkWidget *vbox, *toolbar, *separator, *handle, *table, *child_holder; GtkWidget *handle_hbox, *frame_label; vbox = gtk_vbox_new (FALSE, 0); gtk_widget_show (vbox); toolbar = gtk_hbox_new (FALSE, 0); handle = gtk_event_box_new (); gtk_widget_show (handle); gtk_box_pack_start (GTK_BOX (toolbar), handle, TRUE, TRUE, 3); pane->handle = handle; handle_hbox = gtk_hbox_new (FALSE, 0); gtk_widget_show (handle_hbox); gtk_container_add (GTK_CONTAINER (pane->handle), handle_hbox); frame_label = gtk_label_new (NULL); gtk_widget_show (frame_label); gtk_box_pack_start (GTK_BOX (handle_hbox), frame_label, TRUE, TRUE, 0); gtk_misc_set_alignment (GTK_MISC (frame_label), .0, .5); gtk_misc_set_padding (GTK_MISC (frame_label), 6, 0); gtk_label_set_ellipsize (GTK_LABEL (frame_label), PANGO_ELLIPSIZE_END); if (pane->frame_label_markup) gtk_label_set_markup (GTK_LABEL (frame_label), pane->frame_label_text); else gtk_label_set_text (GTK_LABEL (frame_label), pane->frame_label_text); if (embedded) pane->frame_label_embedded = frame_label; else pane->frame_label_window = frame_label; pane->small_handle = gtk_event_box_new (); gtk_widget_show (pane->small_handle); gtk_box_pack_start (GTK_BOX (handle_hbox), pane->small_handle, TRUE, TRUE, 0); if (embedded) { GtkWidget *hide_button; pane->close_button = create_button (pane, toolbar, _("Remove pane"), FALSE, 3, MOO_SMALL_ICON_CLOSE); g_object_set_data (G_OBJECT (pane->close_button), "moo-pane", pane); g_signal_connect_swapped (pane->close_button, "clicked", G_CALLBACK (close_button_clicked), pane); if (!pane->removable) gtk_widget_hide (pane->close_button); hide_button = create_button (pane, toolbar, _("Hide pane"), FALSE, 0, MOO_SMALL_ICON_HIDE); pane->sticky_button = create_button (pane, toolbar, _("Sticky"), TRUE, 0, MOO_SMALL_ICON_STICKY); pane->detach_button = create_button (pane, toolbar, _("Detach pane"), FALSE, 0, MOO_SMALL_ICON_DETACH); g_signal_connect_swapped (hide_button, "clicked", G_CALLBACK (hide_button_clicked), pane); g_signal_connect_swapped (pane->detach_button, "clicked", G_CALLBACK (detach_button_clicked), pane); } else { GtkWidget *attach_button; attach_button = create_button (pane, toolbar, _("Attach"), FALSE, 0, MOO_SMALL_ICON_ATTACH); pane->keep_on_top_button = create_button (pane, toolbar, _("Keep on top"), TRUE, 0, MOO_SMALL_ICON_KEEP_ON_TOP); g_object_set_data (G_OBJECT (attach_button), "moo-pane", pane); g_signal_connect_swapped (attach_button, "clicked", G_CALLBACK (attach_button_clicked), pane); } gtk_widget_show (toolbar); gtk_box_pack_start (GTK_BOX (vbox), toolbar, FALSE, FALSE, 0); separator = gtk_hseparator_new (); gtk_widget_show (separator); gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, FALSE, 0); child_holder = gtk_vbox_new (FALSE, 0); gtk_widget_show (child_holder); gtk_box_pack_start (GTK_BOX (vbox), child_holder, TRUE, TRUE, 0); if (embedded) pane->child_holder = child_holder; else pane->window_child_holder = child_holder; table = gtk_table_new (2, 2, FALSE); switch (position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: separator = gtk_vseparator_new (); break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: separator = gtk_hseparator_new (); break; } gtk_widget_show (separator); switch (position) { case MOO_PANE_POS_LEFT: gtk_table_attach (GTK_TABLE (table), separator, 0, 1, 0, 1, GtkAttachOptions (0), GTK_FILL, 0, 0); gtk_table_attach (GTK_TABLE (table), vbox, 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); break; case MOO_PANE_POS_TOP: gtk_table_attach (GTK_TABLE (table), separator, 0, 1, 0, 1, GtkAttachOptions (0), GTK_FILL, 0, 0); gtk_table_attach (GTK_TABLE (table), vbox, 0, 1, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); break; case MOO_PANE_POS_RIGHT: gtk_table_attach (GTK_TABLE (table), separator, 1, 2, 0, 1, GtkAttachOptions (0), GTK_FILL, 0, 0); gtk_table_attach (GTK_TABLE (table), vbox, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); break; case MOO_PANE_POS_BOTTOM: gtk_table_attach (GTK_TABLE (table), separator, 0, 1, 1, 2, GtkAttachOptions (0), GTK_FILL, 0, 0); gtk_table_attach (GTK_TABLE (table), vbox, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); break; } return table; } static GtkWidget * create_label_widget (MooPanePosition position, GtkWidget **label_widget, GtkWidget **icon_widget) { GtkWidget *box = NULL; g_return_val_if_fail (position < 4, NULL); *label_widget = gtk_label_new (NULL); switch (position) { case MOO_PANE_POS_LEFT: gtk_label_set_angle (GTK_LABEL (*label_widget), 90); break; case MOO_PANE_POS_RIGHT: gtk_label_set_angle (GTK_LABEL (*label_widget), 270); break; default: break; } *icon_widget = gtk_image_new (); switch (position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: box = gtk_vbox_new (FALSE, SPACING_IN_BUTTON); break; default: box = gtk_hbox_new (FALSE, SPACING_IN_BUTTON); break; } switch (position) { case MOO_PANE_POS_LEFT: gtk_box_pack_start (GTK_BOX (box), *label_widget, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box), *icon_widget, FALSE, FALSE, 0); break; default: gtk_box_pack_start (GTK_BOX (box), *icon_widget, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box), *label_widget, FALSE, FALSE, 0); break; } gtk_widget_show (box); return box; } static void paned_enable_detaching_notify (MooPane *pane) { gboolean enable; g_object_get (pane->parent, "enable-detaching", &enable, NULL); g_object_set (pane->detach_button, "visible", enable && pane->detachable, NULL); } static void paned_sticky_pane_notify (MooPane *pane) { update_sticky_button (pane); } static void button_drag_leave (GtkWidget *button, G_GNUC_UNUSED GdkDragContext *context, G_GNUC_UNUSED guint time, MooPane *pane) { if (pane->open_timeout) g_source_remove (pane->open_timeout); pane->open_timeout = 0; if (pane->button_highlight) gtk_drag_unhighlight (button); pane->button_highlight = FALSE; } static gboolean drag_open_pane (MooPane *pane) { moo_pane_open (pane); if (pane->button_highlight) gtk_drag_unhighlight (pane->button); pane->button_highlight = FALSE; pane->open_timeout = 0; return FALSE; } static gboolean button_drag_motion (GtkWidget *button, GdkDragContext *context, G_GNUC_UNUSED int x, G_GNUC_UNUSED int y, guint time, MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), FALSE); if (moo_paned_is_open (pane->parent) && moo_paned_get_open_pane (pane->parent) == pane) { goto out; } if (pane->params->detached) goto out; if (!pane->button_highlight) { gtk_drag_highlight (button); pane->button_highlight = TRUE; } if (!pane->open_timeout) pane->open_timeout = g_timeout_add (OPEN_PANE_TIMEOUT, (GSourceFunc) drag_open_pane, pane); gdk_drag_status (context, GdkDragAction (0), time); return TRUE; out: if (pane->button_highlight) gtk_drag_unhighlight (button); pane->button_highlight = FALSE; if (pane->open_timeout) g_source_remove (pane->open_timeout); pane->open_timeout = 0; gdk_drag_status (context, GdkDragAction (0), time); return TRUE; } static void setup_button_dnd (MooPane *pane) { gtk_drag_dest_set (pane->button, GtkDestDefaults (0), NULL, 0, GDK_ACTION_COPY | GDK_ACTION_MOVE); g_signal_connect (pane->button, "drag-motion", G_CALLBACK (button_drag_motion), pane); g_signal_connect (pane->button, "drag-leave", G_CALLBACK (button_drag_leave), pane); } /** * moo_pane_set_drag_dest: **/ void moo_pane_set_drag_dest (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); moo_pane_unset_drag_dest (pane); pane->drag_dest_enabled = TRUE; if (pane->button) setup_button_dnd (pane); } /** * moo_pane_unset_drag_dest: **/ void moo_pane_unset_drag_dest (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); if (pane->drag_dest_enabled && pane->button) { gtk_drag_dest_unset (pane->button); g_signal_handlers_disconnect_by_func (pane->button, (gpointer) button_drag_motion, pane); g_signal_handlers_disconnect_by_func (pane->button, (gpointer) button_drag_leave, pane); } if (pane->open_timeout) g_source_remove (pane->open_timeout); pane->open_timeout = 0; if (pane->button_highlight) { gtk_drag_unhighlight (pane->button); pane->button_highlight = FALSE; } } static void create_widgets (MooPane *pane, MooPanePosition position, GdkWindow *pane_window) { GtkWidget *label; pane->frame = create_frame_widget (pane, position, TRUE); update_sticky_button (pane); gtk_widget_set_parent_window (pane->frame, pane_window); gtk_widget_set_parent (pane->frame, GTK_WIDGET (pane->parent)); gtk_box_pack_start (GTK_BOX (pane->child_holder), pane->child, TRUE, TRUE, 0); pane->button = gtk_toggle_button_new (); gtk_widget_show (pane->button); gtk_button_set_focus_on_click (GTK_BUTTON (pane->button), FALSE); label = create_label_widget (position, &pane->label_widget, &pane->icon_widget); gtk_container_add (GTK_CONTAINER (pane->button), label); gtk_widget_show (label); update_label_widgets (pane); if (pane->drag_dest_enabled) setup_button_dnd (pane); g_object_set_data (G_OBJECT (pane->button), "moo-pane", pane); g_object_set_data (G_OBJECT (pane->child), "moo-pane", pane); g_object_set_data (G_OBJECT (pane->frame), "moo-pane", pane); g_object_set_data (G_OBJECT (pane->handle), "moo-pane", pane); g_signal_connect (pane->sticky_button, "toggled", G_CALLBACK (sticky_button_toggled), pane); } MooPane * _moo_pane_new (GtkWidget *child, MooPaneLabel *label) { MooPane *pane; g_return_val_if_fail (GTK_IS_WIDGET (child), NULL); pane = MOO_PANE (g_object_new (MOO_TYPE_PANE, nullptr)); pane->child = GTK_WIDGET (g_object_ref (child)); gtk_widget_show (pane->child); g_object_set_data (G_OBJECT (pane->child), "moo-pane", pane); if (label) moo_pane_set_label (pane, label); return pane; } /** * moo_pane_get_id: * * Returns: (type const-utf8) **/ const char * moo_pane_get_id (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return pane->id; } void _moo_pane_set_id (MooPane *pane, const char *id) { char *tmp; g_return_if_fail (MOO_IS_PANE (pane)); tmp = pane->id; pane->id = g_strdup (id); g_free (tmp); } void _moo_pane_set_parent (MooPane *pane, gpointer parent, GdkWindow *pane_window) { g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (MOO_IS_PANED (parent)); g_return_if_fail (pane->parent == NULL || pane->parent == parent); g_return_if_fail (pane->child != NULL || pane->parent == parent); if (pane->parent == parent) { gtk_widget_set_parent_window (pane->frame, pane_window); } else { pane->parent = MOO_PANED (parent); create_widgets (pane, _moo_paned_get_position (pane->parent), pane_window); g_signal_connect_swapped (parent, "notify::enable-detaching", G_CALLBACK (paned_enable_detaching_notify), pane); g_signal_connect_swapped (parent, "notify::sticky-pane", G_CALLBACK (paned_sticky_pane_notify), pane); } } void _moo_pane_size_request (MooPane *pane, GtkRequisition *req) { g_return_if_fail (MOO_IS_PANE (pane) && pane->frame != NULL); gtk_widget_size_request (pane->frame, req); } void _moo_pane_size_allocate (MooPane *pane, GtkAllocation *allocation) { g_return_if_fail (MOO_IS_PANE (pane) && pane->frame != NULL); gtk_widget_size_allocate (pane->frame, allocation); } void _moo_pane_get_size_request (MooPane *pane, GtkRequisition *req) { g_return_if_fail (MOO_IS_PANE (pane) && pane->frame != NULL); gtk_widget_get_child_requisition (pane->frame, req); } GtkWidget * _moo_pane_get_frame (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return pane->frame; } GtkWidget * _moo_pane_get_focus_child (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return object_ref_cast_opt (pane->focus_child); } GtkWidget * _moo_pane_get_button (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return pane->button; } void _moo_pane_get_handle (MooPane *pane, GtkWidget **big, GtkWidget **small_) { g_return_if_fail (MOO_IS_PANE (pane)); *big = pane->handle; *small_ = pane->small_handle; } GtkWidget * _moo_pane_get_window (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return pane->window; } /** * moo_pane_get_child: **/ GtkWidget * moo_pane_get_child (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return pane->child; } gpointer _moo_pane_get_parent (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return pane->parent; } void _moo_pane_params_changed (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); if (!pane->params_changed_blocked) g_object_notify (G_OBJECT (pane), "params"); } void _moo_pane_freeze_params (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); pane->params_changed_blocked = TRUE; } void _moo_pane_thaw_params (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); pane->params_changed_blocked = FALSE; } gboolean _moo_pane_get_detached (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), FALSE); return pane->params->detached; } void _moo_pane_unparent (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); if (pane->parent) { g_signal_handlers_disconnect_by_func (pane->parent, (gpointer) paned_enable_detaching_notify, pane); g_signal_handlers_disconnect_by_func (pane->parent, (gpointer) paned_sticky_pane_notify, pane); pane->parent = NULL; gtk_container_remove (GTK_CONTAINER (pane->child_holder), pane->child); gtk_widget_unparent (pane->frame); pane->child_holder = NULL; pane->frame = NULL; pane->handle = NULL; pane->small_handle = NULL; pane->button = NULL; pane->label_widget = NULL; pane->icon_widget = NULL; pane->sticky_button = NULL; pane->detach_button = NULL; pane->close_button = NULL; if (pane->window) gtk_widget_destroy (pane->window); pane->window = NULL; pane->keep_on_top_button = NULL; pane->window_child_holder = NULL; pane->focus_child = NULL; } } static GtkWidget * find_focus (GtkWidget *widget) { GtkWidget *focus_child, *window; if (!widget) return NULL; window = gtk_widget_get_toplevel (widget); if (!GTK_IS_WINDOW (window)) return NULL; focus_child = gtk_window_get_focus (GTK_WINDOW (window)); if (focus_child && gtk_widget_is_ancestor (focus_child, widget)) return focus_child; else return NULL; } void _moo_pane_update_focus_child (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); if (pane->focus_child) g_object_remove_weak_pointer (G_OBJECT (pane->focus_child), &pane->focus_child); pane->focus_child = find_focus (pane->child); if (pane->focus_child) g_object_add_weak_pointer (G_OBJECT (pane->focus_child), &pane->focus_child); } static gboolean pane_window_delete_event (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), FALSE); moo_paned_attach_pane (pane->parent, pane); return TRUE; } static void keep_on_top_button_toggled (GtkToggleButton *button, MooPane *pane) { gboolean active; g_return_if_fail (MOO_IS_PANE (pane)); active = gtk_toggle_button_get_active (button); pane->params->keep_on_top = active; if (pane->params->keep_on_top) { GtkWidget *parent = gtk_widget_get_toplevel (GTK_WIDGET (pane->parent)); if (GTK_IS_WINDOW (parent)) gtk_window_set_transient_for (GTK_WINDOW (pane->window), GTK_WINDOW (parent)); } else { gtk_window_set_transient_for (GTK_WINDOW (pane->window), NULL); } _moo_pane_params_changed (pane); } static gboolean pane_window_configure (GtkWidget *window, GdkEventConfigure *event, MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), FALSE); g_return_val_if_fail (pane->window == window, FALSE); pane->params->window_position.x = event->x; pane->params->window_position.y = event->y; pane->params->window_position.width = event->width; pane->params->window_position.height = event->height; _moo_pane_params_changed (pane); return FALSE; } static void create_pane_window (MooPane *pane) { int width = -1; int height = -1; GtkWidget *frame; GtkWindow *window; if (pane->window) return; pane->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); window = GTK_WINDOW (pane->window); moo_help_connect_keys (pane->window); set_pane_window_icon_and_title (pane); gtk_window_set_type_hint (GTK_WINDOW (pane->window), GDK_WINDOW_TYPE_HINT_UTILITY); switch (_moo_paned_get_position (pane->parent)) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: width = moo_paned_get_pane_size (pane->parent); height = GTK_WIDGET(pane->parent)->allocation.height; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: height = moo_paned_get_pane_size (pane->parent); width = GTK_WIDGET(pane->parent)->allocation.width; break; } gtk_window_set_default_size (window, width, height); g_signal_connect_swapped (window, "delete-event", G_CALLBACK (pane_window_delete_event), pane); frame = create_frame_widget (pane, _moo_paned_get_position (pane->parent), FALSE); gtk_widget_show (frame); gtk_container_add (GTK_CONTAINER (pane->window), frame); g_object_set_data (G_OBJECT (pane->window), "moo-pane", pane); g_object_set_data (G_OBJECT (pane->keep_on_top_button), "moo-pane", pane); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (pane->keep_on_top_button), pane->params->keep_on_top); g_signal_connect (pane->keep_on_top_button, "toggled", G_CALLBACK (keep_on_top_button_toggled), pane); g_signal_connect (pane->window, "configure-event", G_CALLBACK (pane_window_configure), pane); } /* FIXME use gtk_widget_reparent(), it does work now */ static void reparent (GtkWidget *widget, GtkWidget *old_container, GtkWidget *new_container) { g_object_ref (widget); gtk_container_remove (GTK_CONTAINER (old_container), widget); gtk_container_add (GTK_CONTAINER (new_container), widget); g_object_unref (widget); } void _moo_pane_detach (MooPane *pane) { gboolean visible; g_return_if_fail (MOO_IS_PANE (pane)); if (pane->params->detached) return; pane->params->detached = TRUE; create_pane_window (pane); reparent (pane->child, pane->child_holder, pane->window_child_holder); if (pane->params->keep_on_top) { GtkWidget *parent = gtk_widget_get_toplevel (GTK_WIDGET (pane->parent)); if (GTK_IS_WINDOW (parent)) gtk_window_set_transient_for (GTK_WINDOW (pane->window), GTK_WINDOW (parent)); } else { gtk_window_set_transient_for (GTK_WINDOW (pane->window), NULL); } if (pane->focus_child) gtk_widget_grab_focus (GTK_WIDGET (pane->focus_child)); else gtk_widget_child_focus (pane->child, GTK_DIR_TAB_FORWARD); g_object_get (pane->window, "visible", &visible, NULL); if (!visible && pane->params->window_position.width > 0 && pane->params->window_position.height > 0) { gtk_window_move (GTK_WINDOW (pane->window), pane->params->window_position.x, pane->params->window_position.y); gtk_window_set_default_size (GTK_WINDOW (pane->window), pane->params->window_position.width, pane->params->window_position.height); } gtk_window_present (GTK_WINDOW (pane->window)); _moo_pane_params_changed (pane); } void _moo_pane_attach (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); if (!pane->params->detached) return; pane->params->detached = FALSE; if (pane->focus_child) g_object_remove_weak_pointer (G_OBJECT (pane->focus_child), &pane->focus_child); pane->focus_child = find_focus (pane->child); if (pane->focus_child) g_object_add_weak_pointer (G_OBJECT (pane->focus_child), &pane->focus_child); reparent (pane->child, pane->window_child_holder, pane->child_holder); gtk_widget_hide (pane->window); _moo_pane_params_changed (pane); } void _moo_pane_try_remove (MooPane *pane) { gboolean ret; g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (pane->parent != NULL); g_object_ref (pane); g_signal_emit (pane, signals[REMOVE], 0, &ret); if (!ret && pane->parent && pane->child) moo_paned_remove_pane (pane->parent, pane->child); g_object_unref (pane); } /** * moo_pane_open: **/ void moo_pane_open (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (pane->parent != NULL); moo_paned_open_pane (pane->parent, pane); } /** * moo_pane_present: **/ void moo_pane_present (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (pane->parent != NULL); moo_paned_present_pane (pane->parent, pane); } /** * moo_pane_attach: **/ void moo_pane_attach (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (pane->parent != NULL); moo_paned_attach_pane (pane->parent, pane); } /** * moo_pane_detach: **/ void moo_pane_detach (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (pane->parent != NULL); moo_paned_detach_pane (pane->parent, pane); } /** * moo_pane_get_index: **/ int moo_pane_get_index (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), -1); if (pane->parent) return moo_paned_get_pane_num (pane->parent, pane->child); else return -1; } typedef enum { ICON_PIXBUFS, ICON_ARROW_UP, ICON_ARROW_DOWN, ICON_ARROW_LEFT, ICON_ARROW_RIGHT } IconType; typedef struct { GtkWidget base; GdkPixbuf **pixbufs; const guchar *data; IconType type; } MooIconWidget; typedef struct { GtkWidgetClass base_class; } MooIconWidgetClass; GType _moo_icon_widget_get_type (void) G_GNUC_CONST; #define MOO_TYPE_ICON_WIDGET (_moo_icon_widget_get_type ()) #define MOO_ICON_WIDGET(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), MOO_TYPE_ICON_WIDGET, MooIconWidget)) G_DEFINE_TYPE (MooIconWidget, _moo_icon_widget, GTK_TYPE_WIDGET) static void _moo_icon_widget_init (MooIconWidget *icon) { GTK_WIDGET_SET_NO_WINDOW (icon); icon->pixbufs = NULL; icon->data = NULL; icon->type = ICON_PIXBUFS; } static void free_pixbufs (MooIconWidget *icon) { if (icon->pixbufs) { int i; for (i = 0; i < 5 /* magic */; ++i) g_object_unref (icon->pixbufs[i]); g_free (icon->pixbufs); icon->pixbufs = NULL; } } static void moo_icon_widget_style_set (GtkWidget *widget, GtkStyle *old_style) { GTK_WIDGET_CLASS (_moo_icon_widget_parent_class)->style_set (widget, old_style); free_pixbufs ((MooIconWidget*) widget); } static void moo_icon_widget_dispose (GObject *object) { free_pixbufs ((MooIconWidget*) object); G_OBJECT_CLASS (_moo_icon_widget_parent_class)->dispose (object); } static GdkPixbuf * get_pixbuf (MooIconWidget *icon) { if (!icon->pixbufs) { int state; GtkWidget *widget = GTK_WIDGET (icon); g_return_val_if_fail (icon->data != NULL, NULL); icon->pixbufs = g_new0 (GdkPixbuf*, 5 /* magic */); for (state = 0; state < 5 /* magic */; ++state) { GdkPixbuf *pixbuf; guchar *pixels, *p; int width, height, rowstride, n_channels; GdkColor *color; int x, y; pixbuf = gdk_pixbuf_new_from_inline (-1, icon->data, TRUE, NULL); g_return_val_if_fail (pixbuf != NULL, NULL); icon->pixbufs[state] = pixbuf; pixels = gdk_pixbuf_get_pixels (pixbuf); width = gdk_pixbuf_get_width (pixbuf); height = gdk_pixbuf_get_height (pixbuf); n_channels = gdk_pixbuf_get_n_channels (pixbuf); g_assert (n_channels == 4); rowstride = gdk_pixbuf_get_rowstride (pixbuf); color = &widget->style->fg[state]; for (x = 0; x < width; ++x) { for (y = 0; y < height; ++y) { p = pixels + y * rowstride + x * n_channels; if (p[3] != 0) { p[0] = color->red >> 8; p[1] = color->green >> 8; p[2] = color->blue >> 8; } } } } } return icon->pixbufs[GTK_WIDGET_STATE (icon)]; } static void draw_pixbuf (GtkWidget *widget, GdkEventExpose *event) { GdkPixbuf *pixbuf; int pixbuf_width, pixbuf_height; int x, y; pixbuf = get_pixbuf ((MooIconWidget*) widget); g_return_if_fail (pixbuf != NULL); pixbuf_width = gdk_pixbuf_get_width (pixbuf); pixbuf_height = gdk_pixbuf_get_height (pixbuf); x = widget->allocation.x + (widget->allocation.width - pixbuf_width) / 2; y = widget->allocation.y + (widget->allocation.height - pixbuf_height) / 2; gdk_draw_pixbuf (event->window, widget->style->black_gc, pixbuf, 0, 0, x, y, pixbuf_width, pixbuf_height, GDK_RGB_DITHER_NORMAL, 0, 0); } static void draw_arrow (GtkWidget *widget, GdkEventExpose *event) { GtkArrowType arrow_type; int x, y, width, height; switch (((MooIconWidget*)widget)->type) { case ICON_ARROW_UP: arrow_type = GTK_ARROW_UP; break; case ICON_ARROW_DOWN: arrow_type = GTK_ARROW_DOWN; break; case ICON_ARROW_LEFT: arrow_type = GTK_ARROW_LEFT; break; case ICON_ARROW_RIGHT: arrow_type = GTK_ARROW_RIGHT; break; default: g_return_if_reached (); } width = 3 * widget->allocation.width / 4; height = 3 * widget->allocation.height / 4; x = widget->allocation.x + width / 6; y = widget->allocation.y + height / 6; gtk_paint_arrow (widget->style, event->window, GtkStateType (GTK_WIDGET_STATE (widget)), GTK_SHADOW_IN, &event->area, widget, NULL, arrow_type, TRUE, x, y, width, height); } static gboolean moo_icon_widget_expose_event (GtkWidget *widget, GdkEventExpose *event) { MooIconWidget *icon = (MooIconWidget*) widget; switch (icon->type) { case ICON_PIXBUFS: draw_pixbuf (widget, event); break; case ICON_ARROW_UP: case ICON_ARROW_DOWN: case ICON_ARROW_LEFT: case ICON_ARROW_RIGHT: draw_arrow (widget, event); break; } return FALSE; } static void _moo_icon_widget_class_init (MooIconWidgetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->dispose = moo_icon_widget_dispose; widget_class->style_set = moo_icon_widget_style_set; widget_class->expose_event = moo_icon_widget_expose_event; } GtkWidget * _moo_create_small_icon (MooSmallIcon icon) { MooIconWidget *icon_widget; const guchar *data = NULL; switch (icon) { case MOO_SMALL_ICON_HIDE: data = MOO_HIDE_ICON; break; case MOO_SMALL_ICON_STICKY: data = MOO_STICKY_ICON; break; case MOO_SMALL_ICON_CLOSE: data = MOO_CLOSE_ICON; break; case MOO_SMALL_ICON_DETACH: data = MOO_DETACH_ICON; break; case MOO_SMALL_ICON_ATTACH: data = MOO_ATTACH_ICON; break; case MOO_SMALL_ICON_KEEP_ON_TOP: data = MOO_KEEP_ON_TOP_ICON; break; } g_return_val_if_fail (data != NULL, NULL); icon_widget = MOO_ICON_WIDGET (g_object_new (MOO_TYPE_ICON_WIDGET, nullptr)); icon_widget->data = data; gtk_widget_set_size_request (GTK_WIDGET (icon_widget), 7, 7 /* magic */); return GTK_WIDGET (icon_widget); } GtkWidget * _moo_create_arrow_icon (GtkArrowType arrow_type) { MooIconWidget *icon_widget; IconType icon_type; switch (arrow_type) { case GTK_ARROW_UP: icon_type = ICON_ARROW_UP; break; case GTK_ARROW_DOWN: icon_type = ICON_ARROW_DOWN; break; case GTK_ARROW_LEFT: icon_type = ICON_ARROW_LEFT; break; case GTK_ARROW_RIGHT: icon_type = ICON_ARROW_RIGHT; break; default: g_return_val_if_reached (NULL); } icon_widget = MOO_ICON_WIDGET (g_object_new (MOO_TYPE_ICON_WIDGET, nullptr)); icon_widget->type = icon_type; return GTK_WIDGET (icon_widget); }