975 lines
30 KiB
C
975 lines
30 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* pikapanedbox.c
|
|
* Copyright (C) 2001-2005 Michael Natterer <mitch@gimp.org>
|
|
* Copyright (C) 2009-2011 Martin Nordholts <martinn@src.gnome.org>
|
|
*
|
|
* 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 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <gegl.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "libpikacolor/pikacolor.h"
|
|
#include "libpikawidgets/pikawidgets.h"
|
|
|
|
#include "widgets-types.h"
|
|
|
|
#include "core/pika.h"
|
|
#include "core/pikacontext.h"
|
|
|
|
#include "pikadialogfactory.h"
|
|
#include "pikadnd.h"
|
|
#include "pikadockable.h"
|
|
#include "pikadockbook.h"
|
|
#include "pikamenudock.h"
|
|
#include "pikapanedbox.h"
|
|
#include "pikatoolbox.h"
|
|
#include "pikawidgets-utils.h"
|
|
|
|
#include "pika-log.h"
|
|
|
|
|
|
/**
|
|
* Defines the size of the area that dockables can be dropped on in
|
|
* order to be inserted and get space on their own (rather than
|
|
* inserted among others and sharing space)
|
|
*/
|
|
#define DROP_AREA_SIZE 6
|
|
|
|
#define DROP_HIGHLIGHT_MIN_SIZE 32
|
|
#define DROP_HIGHLIGHT_COLOR "#215d9c"
|
|
#define DROP_HIGHLIGHT_OPACITY_ACTIVE 0.8
|
|
#define DROP_HIGHLIGHT_OPACITY_INACTIVE 0.4
|
|
|
|
#define INSERT_INDEX_UNUSED G_MININT
|
|
|
|
|
|
typedef struct
|
|
{
|
|
gboolean active;
|
|
GeglRectangle area;
|
|
gdouble opacity;
|
|
} PikaPanedBoxHighlight;
|
|
|
|
struct _PikaPanedBoxPrivate
|
|
{
|
|
/* Widgets that are separated by panes */
|
|
GList *widgets;
|
|
|
|
/* Is the DND highlight shown */
|
|
PikaPanedBoxHighlight dnd_highlights[3];
|
|
gint dnd_n_highlights;
|
|
GdkDragContext *dnd_context;
|
|
gint dnd_paned_position;
|
|
gint dnd_idle_id;
|
|
|
|
/* The insert index to use on drop */
|
|
gint insert_index;
|
|
|
|
/* Callback on drop */
|
|
PikaPanedBoxDroppedFunc dropped_cb;
|
|
gpointer dropped_cb_data;
|
|
|
|
/* A drag handler offered to handle drag events */
|
|
PikaPanedBox *drag_handler;
|
|
};
|
|
|
|
|
|
static void pika_paned_box_dispose (GObject *object);
|
|
|
|
static void pika_paned_box_drag_leave (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
guint time);
|
|
static gboolean pika_paned_box_drag_motion (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
guint time);
|
|
static gboolean pika_paned_box_drag_drop (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
guint time);
|
|
static void pika_paned_box_drag_data_received (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
GtkSelectionData *data,
|
|
guint info,
|
|
guint time);
|
|
static void pika_paned_box_set_widget_drag_handler (GtkWidget *widget,
|
|
PikaPanedBox *handler);
|
|
static gint pika_paned_box_get_drop_area_size (PikaPanedBox *paned_box);
|
|
static void pika_paned_box_hide_drop_indicator (PikaPanedBox *paned_box,
|
|
gint index);
|
|
|
|
static void pika_paned_box_drag_callback (GdkDragContext *context,
|
|
gboolean begin,
|
|
PikaPanedBox *paned_box);
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (PikaPanedBox, pika_paned_box, GTK_TYPE_BOX)
|
|
|
|
#define parent_class pika_paned_box_parent_class
|
|
|
|
static const GtkTargetEntry dialog_target_table[] = { PIKA_TARGET_NOTEBOOK_TAB };
|
|
|
|
static GSList *drop_hints = NULL;
|
|
|
|
|
|
static void
|
|
pika_paned_box_class_init (PikaPanedBoxClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
|
|
object_class->dispose = pika_paned_box_dispose;
|
|
|
|
widget_class->drag_leave = pika_paned_box_drag_leave;
|
|
widget_class->drag_motion = pika_paned_box_drag_motion;
|
|
widget_class->drag_drop = pika_paned_box_drag_drop;
|
|
widget_class->drag_data_received = pika_paned_box_drag_data_received;
|
|
}
|
|
|
|
static void
|
|
pika_paned_box_init (PikaPanedBox *paned_box)
|
|
{
|
|
paned_box->p = pika_paned_box_get_instance_private (paned_box);
|
|
|
|
/* Setup DND */
|
|
gtk_drag_dest_set (GTK_WIDGET (paned_box),
|
|
0,
|
|
dialog_target_table, G_N_ELEMENTS (dialog_target_table),
|
|
GDK_ACTION_MOVE);
|
|
|
|
pika_dockbook_add_drag_callback (
|
|
(PikaDockbookDragCallback) pika_paned_box_drag_callback,
|
|
paned_box);
|
|
}
|
|
|
|
static void
|
|
pika_paned_box_dispose (GObject *object)
|
|
{
|
|
PikaPanedBox *paned_box = PIKA_PANED_BOX (object);
|
|
gsize i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (paned_box->p->dnd_highlights); i++)
|
|
pika_paned_box_hide_drop_indicator (paned_box, i);
|
|
|
|
if (paned_box->p->dnd_idle_id)
|
|
{
|
|
g_source_remove (paned_box->p->dnd_idle_id);
|
|
|
|
paned_box->p->dnd_idle_id = 0;
|
|
}
|
|
|
|
while (paned_box->p->widgets)
|
|
{
|
|
GtkWidget *widget = paned_box->p->widgets->data;
|
|
|
|
g_object_ref (widget);
|
|
pika_paned_box_remove_widget (paned_box, widget);
|
|
gtk_widget_destroy (widget);
|
|
g_object_unref (widget);
|
|
}
|
|
|
|
pika_dockbook_remove_drag_callback (
|
|
(PikaDockbookDragCallback) pika_paned_box_drag_callback,
|
|
paned_box);
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
pika_paned_box_set_widget_drag_handler (GtkWidget *widget,
|
|
PikaPanedBox *drag_handler)
|
|
{
|
|
/* Hook us in for drag events. We could abstract this properly and
|
|
* put pika_paned_box_will_handle_drag() in an interface for
|
|
* example, but it doesn't feel worth it at this point
|
|
*
|
|
* Note that we don't have 'else if's because a widget can be both a
|
|
* dock and a toolbox for example, in which case we want to set a
|
|
* drag handler in two ways
|
|
*
|
|
* We so need to introduce some abstractions here...
|
|
*/
|
|
|
|
if (PIKA_IS_DOCKBOOK (widget))
|
|
{
|
|
pika_dockbook_set_drag_handler (PIKA_DOCKBOOK (widget),
|
|
drag_handler);
|
|
}
|
|
|
|
if (PIKA_IS_DOCK (widget))
|
|
{
|
|
PikaPanedBox *dock_paned_box = NULL;
|
|
dock_paned_box = PIKA_PANED_BOX (pika_dock_get_vbox (PIKA_DOCK (widget)));
|
|
pika_paned_box_set_drag_handler (dock_paned_box, drag_handler);
|
|
}
|
|
|
|
if (PIKA_IS_TOOLBOX (widget))
|
|
{
|
|
PikaToolbox *toolbox = PIKA_TOOLBOX (widget);
|
|
pika_toolbox_set_drag_handler (toolbox, drag_handler);
|
|
}
|
|
}
|
|
|
|
static gint
|
|
pika_paned_box_get_drop_area_size (PikaPanedBox *paned_box)
|
|
{
|
|
gint drop_area_size = 0;
|
|
|
|
if (! paned_box->p->widgets)
|
|
{
|
|
GtkAllocation allocation;
|
|
GtkOrientation orientation;
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (paned_box), &allocation);
|
|
orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (paned_box));
|
|
|
|
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
drop_area_size = allocation.width;
|
|
else if (orientation == GTK_ORIENTATION_VERTICAL)
|
|
drop_area_size = allocation.height;
|
|
}
|
|
|
|
drop_area_size = MAX (drop_area_size, DROP_AREA_SIZE);
|
|
|
|
return drop_area_size;
|
|
}
|
|
|
|
static gboolean
|
|
pika_paned_box_get_handle_drag (PikaPanedBox *paned_box,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
guint time,
|
|
gint *insert_index,
|
|
GeglRectangle *area)
|
|
{
|
|
gint index = INSERT_INDEX_UNUSED;
|
|
GtkAllocation allocation = { 0, };
|
|
gint area_x = 0;
|
|
gint area_y = 0;
|
|
gint area_w = 0;
|
|
gint area_h = 0;
|
|
GtkOrientation orientation = 0;
|
|
gint drop_area_size = pika_paned_box_get_drop_area_size (paned_box);
|
|
|
|
if (pika_paned_box_will_handle_drag (paned_box->p->drag_handler,
|
|
GTK_WIDGET (paned_box),
|
|
context,
|
|
x, y,
|
|
time))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (gtk_drag_dest_find_target (GTK_WIDGET (paned_box), context, NULL) ==
|
|
GDK_NONE)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (paned_box), &allocation);
|
|
|
|
/* See if we're at the edge of the dock If there are no dockables,
|
|
* the entire paned box is a drop area
|
|
*/
|
|
orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (paned_box));
|
|
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
{
|
|
area_y = 0;
|
|
area_h = allocation.height;
|
|
|
|
/* If there are no widgets, the drop area is as big as the paned
|
|
* box
|
|
*/
|
|
if (! paned_box->p->widgets)
|
|
area_w = allocation.width;
|
|
else
|
|
area_w = drop_area_size;
|
|
|
|
if (x < drop_area_size)
|
|
{
|
|
index = 0;
|
|
area_x = 0;
|
|
}
|
|
if (x > allocation.width - drop_area_size)
|
|
{
|
|
index = -1;
|
|
area_x = allocation.width - drop_area_size;
|
|
}
|
|
}
|
|
else /* if (orientation = GTK_ORIENTATION_VERTICAL) */
|
|
{
|
|
area_x = 0;
|
|
area_w = allocation.width;
|
|
|
|
/* If there are no widgets, the drop area is as big as the paned
|
|
* box
|
|
*/
|
|
if (! paned_box->p->widgets)
|
|
area_h = allocation.height;
|
|
else
|
|
area_h = drop_area_size;
|
|
|
|
if (y < drop_area_size)
|
|
{
|
|
index = 0;
|
|
area_y = 0;
|
|
}
|
|
if (y > allocation.height - drop_area_size)
|
|
{
|
|
index = -1;
|
|
area_y = allocation.height - drop_area_size;
|
|
}
|
|
}
|
|
|
|
if (area)
|
|
{
|
|
area->x = allocation.x + area_x;
|
|
area->y = allocation.y + area_y;
|
|
area->width = area_w;
|
|
area->height = area_h;
|
|
}
|
|
|
|
if (insert_index)
|
|
*insert_index = index;
|
|
|
|
return index != INSERT_INDEX_UNUSED;
|
|
}
|
|
|
|
static gboolean
|
|
pika_paned_box_drop_indicator_draw (GtkWidget *widget,
|
|
cairo_t *cr,
|
|
gpointer data)
|
|
{
|
|
PikaPanedBox *paned_box = PIKA_PANED_BOX (widget);
|
|
PikaRGB color;
|
|
gsize i;
|
|
|
|
pika_rgb_parse_hex (&color, DROP_HIGHLIGHT_COLOR, -1);
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (paned_box->p->dnd_highlights); i++)
|
|
{
|
|
const PikaPanedBoxHighlight *highlight = &paned_box->p->dnd_highlights[i];
|
|
|
|
if (! highlight->active)
|
|
continue;
|
|
|
|
cairo_set_source_rgba (cr, color.r, color.g, color.b, highlight->opacity);
|
|
|
|
cairo_rectangle (cr,
|
|
highlight->area.x,
|
|
highlight->area.y,
|
|
highlight->area.width,
|
|
highlight->area.height);
|
|
|
|
cairo_fill (cr);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
pika_paned_box_position_drop_indicator (PikaPanedBox *paned_box,
|
|
gint index,
|
|
const GeglRectangle *area,
|
|
gdouble opacity)
|
|
{
|
|
GtkWidget *widget = GTK_WIDGET (paned_box);
|
|
|
|
paned_box->p->dnd_highlights[index].area = *area;
|
|
paned_box->p->dnd_highlights[index].opacity = opacity;
|
|
|
|
if (! paned_box->p->dnd_highlights[index].active)
|
|
{
|
|
if (paned_box->p->dnd_n_highlights == 0)
|
|
{
|
|
g_signal_connect_after (
|
|
widget, "draw",
|
|
G_CALLBACK (pika_paned_box_drop_indicator_draw),
|
|
NULL);
|
|
}
|
|
|
|
paned_box->p->dnd_highlights[index].active = TRUE;
|
|
|
|
paned_box->p->dnd_n_highlights++;
|
|
}
|
|
|
|
gtk_widget_queue_draw (widget);
|
|
}
|
|
|
|
static void
|
|
pika_paned_box_hide_drop_indicator (PikaPanedBox *paned_box,
|
|
gint index)
|
|
{
|
|
GtkWidget *widget = GTK_WIDGET (paned_box);
|
|
|
|
if (paned_box->p->dnd_highlights[index].active)
|
|
{
|
|
paned_box->p->dnd_highlights[index].active = FALSE;
|
|
|
|
paned_box->p->dnd_n_highlights--;
|
|
|
|
if (paned_box->p->dnd_n_highlights == 0)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (
|
|
widget,
|
|
pika_paned_box_drop_indicator_draw,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
gtk_widget_queue_draw (widget);
|
|
}
|
|
|
|
static void
|
|
pika_paned_box_drag_leave (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
guint time)
|
|
{
|
|
pika_paned_box_hide_drop_indicator (PIKA_PANED_BOX (widget), 0);
|
|
}
|
|
|
|
static gboolean
|
|
pika_paned_box_drag_motion (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
guint time)
|
|
{
|
|
PikaPanedBox *paned_box = PIKA_PANED_BOX (widget);
|
|
gint insert_index;
|
|
GeglRectangle area;
|
|
gboolean handle;
|
|
|
|
handle = pika_paned_box_get_handle_drag (paned_box, context, x, y, time,
|
|
&insert_index, &area);
|
|
|
|
/* If we are at the edge, show a highlight to communicate that a
|
|
* drop will create a new dock column
|
|
*/
|
|
if (handle)
|
|
{
|
|
pika_paned_box_position_drop_indicator (paned_box,
|
|
0,
|
|
&area,
|
|
DROP_HIGHLIGHT_OPACITY_ACTIVE);
|
|
}
|
|
else
|
|
{
|
|
pika_paned_box_hide_drop_indicator (paned_box, 0);
|
|
}
|
|
|
|
/* Save the insert index for drag-drop */
|
|
paned_box->p->insert_index = insert_index;
|
|
|
|
gdk_drag_status (context, handle ? GDK_ACTION_MOVE : 0, time);
|
|
|
|
/* Return TRUE so drag_leave() is called */
|
|
return handle;
|
|
}
|
|
|
|
static gboolean
|
|
pika_paned_box_drag_drop (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
guint time)
|
|
{
|
|
PikaPanedBox *paned_box = PIKA_PANED_BOX (widget);
|
|
GdkAtom target;
|
|
|
|
if (pika_paned_box_will_handle_drag (paned_box->p->drag_handler,
|
|
widget,
|
|
context,
|
|
x, y,
|
|
time))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
target = gtk_drag_dest_find_target (widget, context, NULL);
|
|
|
|
if (target == GDK_NONE)
|
|
return FALSE;
|
|
|
|
gtk_drag_get_data (widget, context, target, time);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
pika_paned_box_drag_data_received (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
GtkSelectionData *data,
|
|
guint info,
|
|
guint time)
|
|
{
|
|
PikaPanedBox *paned_box = PIKA_PANED_BOX (widget);
|
|
GtkWidget *notebook = gtk_drag_get_source_widget (context);
|
|
GtkWidget **child = (gpointer) gtk_selection_data_get_data (data);
|
|
gboolean dropped = FALSE;
|
|
|
|
if (paned_box->p->dropped_cb)
|
|
{
|
|
dropped = paned_box->p->dropped_cb (notebook,
|
|
*child,
|
|
paned_box->p->insert_index,
|
|
paned_box->p->dropped_cb_data);
|
|
}
|
|
|
|
gtk_drag_finish (context, dropped, TRUE, time);
|
|
}
|
|
|
|
static gboolean
|
|
pika_paned_box_drag_callback_idle (PikaPanedBox *paned_box)
|
|
{
|
|
GtkAllocation allocation;
|
|
GtkOrientation orientation;
|
|
GeglRectangle area;
|
|
|
|
paned_box->p->dnd_idle_id = 0;
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (paned_box), &allocation);
|
|
orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (paned_box));
|
|
|
|
#define ADD_AREA(index, left, top) \
|
|
if (pika_paned_box_get_handle_drag ( \
|
|
paned_box, \
|
|
paned_box->p->dnd_context, \
|
|
(left), (top), \
|
|
0, \
|
|
NULL, &area)) \
|
|
{ \
|
|
pika_paned_box_position_drop_indicator ( \
|
|
paned_box, \
|
|
index, \
|
|
&area, \
|
|
DROP_HIGHLIGHT_OPACITY_INACTIVE); \
|
|
}
|
|
|
|
if (! paned_box->p->widgets)
|
|
{
|
|
ADD_AREA (1, allocation.width / 2, allocation.height / 2)
|
|
}
|
|
else if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
{
|
|
ADD_AREA (1, 0, allocation.height / 2)
|
|
ADD_AREA (2, allocation.width - 1, allocation.height / 2)
|
|
}
|
|
else
|
|
{
|
|
ADD_AREA (1, allocation.width / 2, 0)
|
|
ADD_AREA (2, allocation.width / 2, allocation.height - 1)
|
|
}
|
|
|
|
#undef ADD_AREA
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
pika_paned_box_drag_callback (GdkDragContext *context,
|
|
gboolean begin,
|
|
PikaPanedBox *paned_box)
|
|
{
|
|
GtkWidget *paned;
|
|
gint position;
|
|
|
|
if (! gtk_widget_get_sensitive (GTK_WIDGET (paned_box)))
|
|
return;
|
|
|
|
paned = gtk_widget_get_ancestor (GTK_WIDGET (paned_box),
|
|
GTK_TYPE_PANED);
|
|
|
|
/* apparently, we can be called multiple times when beginning a drag
|
|
* (possibly a gtk bug); make sure not to leak the idle.
|
|
*
|
|
* see issue #4895.
|
|
*/
|
|
if (begin && ! paned_box->p->dnd_context)
|
|
{
|
|
paned_box->p->dnd_context = context;
|
|
|
|
if (paned)
|
|
{
|
|
GtkAllocation allocation;
|
|
|
|
gtk_widget_get_allocation (paned, &allocation);
|
|
|
|
position = gtk_paned_get_position (GTK_PANED (paned));
|
|
|
|
paned_box->p->dnd_paned_position = position;
|
|
|
|
if (position < 0)
|
|
{
|
|
position = 0;
|
|
}
|
|
else if (gtk_widget_is_ancestor (
|
|
GTK_WIDGET (paned_box),
|
|
gtk_paned_get_child2 (GTK_PANED (paned))))
|
|
{
|
|
position = allocation.width - position;
|
|
}
|
|
|
|
if (position < DROP_HIGHLIGHT_MIN_SIZE)
|
|
{
|
|
position = DROP_HIGHLIGHT_MIN_SIZE;
|
|
|
|
if (gtk_widget_is_ancestor (
|
|
GTK_WIDGET (paned_box),
|
|
gtk_paned_get_child2 (GTK_PANED (paned))))
|
|
{
|
|
position = allocation.width - position;
|
|
}
|
|
|
|
gtk_paned_set_position (GTK_PANED (paned), position);
|
|
}
|
|
}
|
|
|
|
paned_box->p->dnd_idle_id = g_idle_add (
|
|
(GSourceFunc) pika_paned_box_drag_callback_idle,
|
|
paned_box);
|
|
}
|
|
else if (! begin && paned_box->p->dnd_context)
|
|
{
|
|
if (paned_box->p->dnd_idle_id)
|
|
{
|
|
g_source_remove (paned_box->p->dnd_idle_id);
|
|
|
|
paned_box->p->dnd_idle_id = 0;
|
|
}
|
|
|
|
paned_box->p->dnd_context = NULL;
|
|
|
|
pika_paned_box_hide_drop_indicator (paned_box, 1);
|
|
pika_paned_box_hide_drop_indicator (paned_box, 2);
|
|
|
|
if (paned)
|
|
{
|
|
gtk_paned_set_position (GTK_PANED (paned),
|
|
paned_box->p->dnd_paned_position);
|
|
}
|
|
}
|
|
}
|
|
|
|
GtkWidget *
|
|
pika_paned_box_new (gboolean homogeneous,
|
|
gint spacing,
|
|
GtkOrientation orientation)
|
|
{
|
|
return g_object_new (PIKA_TYPE_PANED_BOX,
|
|
"homogeneous", homogeneous,
|
|
"spacing", 0,
|
|
"orientation", orientation,
|
|
NULL);
|
|
}
|
|
|
|
void
|
|
pika_paned_box_set_dropped_cb (PikaPanedBox *paned_box,
|
|
PikaPanedBoxDroppedFunc dropped_cb,
|
|
gpointer dropped_cb_data)
|
|
{
|
|
g_return_if_fail (PIKA_IS_PANED_BOX (paned_box));
|
|
|
|
paned_box->p->dropped_cb = dropped_cb;
|
|
paned_box->p->dropped_cb_data = dropped_cb_data;
|
|
}
|
|
|
|
/**
|
|
* pika_paned_box_add_widget:
|
|
* @paned_box: A #PikaPanedBox
|
|
* @widget: The #GtkWidget to add
|
|
* @index: Where to add the @widget
|
|
*
|
|
* Add a #GtkWidget to the #PikaPanedBox in a hierarchy of #GtkPaned:s
|
|
* so the space can be manually distributed between the widgets.
|
|
**/
|
|
void
|
|
pika_paned_box_add_widget (PikaPanedBox *paned_box,
|
|
GtkWidget *widget,
|
|
gint index)
|
|
{
|
|
gint old_length = 0;
|
|
|
|
g_return_if_fail (PIKA_IS_PANED_BOX (paned_box));
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
|
|
PIKA_LOG (DND, "Adding GtkWidget %p to PikaPanedBox %p", widget, paned_box);
|
|
|
|
/* Calculate length */
|
|
old_length = g_list_length (paned_box->p->widgets);
|
|
|
|
/* If index is invalid append at the end */
|
|
if (index >= old_length || index < 0)
|
|
{
|
|
index = old_length;
|
|
}
|
|
|
|
/* Insert into the list */
|
|
paned_box->p->widgets = g_list_insert (paned_box->p->widgets, widget, index);
|
|
|
|
/* Hook us in for drag events. We could abstract this but it doesn't
|
|
* seem worth it at this point
|
|
*/
|
|
pika_paned_box_set_widget_drag_handler (widget, paned_box);
|
|
|
|
/* Insert into the GtkPaned hierarchy */
|
|
if (old_length == 0)
|
|
{
|
|
/* A widget is added, hide the instructions */
|
|
//gtk_widget_hide (paned_box->p->instructions);
|
|
drop_hints = g_slist_remove (drop_hints, paned_box);
|
|
|
|
gtk_box_pack_start (GTK_BOX (paned_box), widget, TRUE, TRUE, 0);
|
|
}
|
|
else
|
|
{
|
|
GtkWidget *old_widget;
|
|
GtkWidget *parent;
|
|
GtkWidget *paned;
|
|
GtkOrientation orientation;
|
|
|
|
/* Figure out what widget to detach */
|
|
if (index == 0)
|
|
{
|
|
old_widget = g_list_nth_data (paned_box->p->widgets, index + 1);
|
|
}
|
|
else
|
|
{
|
|
old_widget = g_list_nth_data (paned_box->p->widgets, index - 1);
|
|
}
|
|
|
|
parent = gtk_widget_get_parent (old_widget);
|
|
|
|
if (old_length > 1 && index > 0)
|
|
{
|
|
GtkWidget *grandparent = gtk_widget_get_parent (parent);
|
|
|
|
old_widget = parent;
|
|
parent = grandparent;
|
|
}
|
|
|
|
/* Detach the widget and build up a new hierarchy */
|
|
g_object_ref (old_widget);
|
|
gtk_container_remove (GTK_CONTAINER (parent), old_widget);
|
|
|
|
/* GtkPaned is abstract :( */
|
|
orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (paned_box));
|
|
paned = gtk_paned_new (orientation);
|
|
gtk_paned_set_wide_handle (GTK_PANED (paned), TRUE);
|
|
|
|
if (GTK_IS_PANED (parent))
|
|
{
|
|
gtk_paned_pack1 (GTK_PANED (parent), paned, TRUE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
gtk_box_pack_start (GTK_BOX (parent), paned, TRUE, TRUE, 0);
|
|
}
|
|
gtk_widget_show (paned);
|
|
|
|
if (index == 0)
|
|
{
|
|
gtk_paned_pack1 (GTK_PANED (paned), widget,
|
|
TRUE, FALSE);
|
|
gtk_paned_pack2 (GTK_PANED (paned), old_widget,
|
|
TRUE, FALSE);
|
|
}
|
|
else
|
|
{
|
|
gtk_paned_pack1 (GTK_PANED (paned), old_widget,
|
|
TRUE, FALSE);
|
|
gtk_paned_pack2 (GTK_PANED (paned), widget,
|
|
TRUE, FALSE);
|
|
}
|
|
|
|
g_object_unref (old_widget);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_paned_box_remove_widget:
|
|
* @paned_box: A #PikaPanedBox
|
|
* @widget: The #GtkWidget to remove
|
|
*
|
|
* Remove a #GtkWidget from a #PikaPanedBox added with
|
|
* pika_widgets_add_paned_widget().
|
|
**/
|
|
void
|
|
pika_paned_box_remove_widget (PikaPanedBox *paned_box,
|
|
GtkWidget *widget)
|
|
{
|
|
gint old_length = 0;
|
|
gint index = 0;
|
|
GtkWidget *other_widget = NULL;
|
|
GtkWidget *parent = NULL;
|
|
GtkWidget *grandparent = NULL;
|
|
|
|
g_return_if_fail (PIKA_IS_PANED_BOX (paned_box));
|
|
g_return_if_fail (GTK_IS_WIDGET (widget));
|
|
|
|
PIKA_LOG (DND, "Removing GtkWidget %p from PikaPanedBox %p", widget, paned_box);
|
|
|
|
/* Calculate length and index */
|
|
old_length = g_list_length (paned_box->p->widgets);
|
|
index = g_list_index (paned_box->p->widgets, widget);
|
|
|
|
/* Remove from list */
|
|
paned_box->p->widgets = g_list_remove (paned_box->p->widgets, widget);
|
|
|
|
/* Reset the drag events hook */
|
|
pika_paned_box_set_widget_drag_handler (widget, NULL);
|
|
|
|
/* Remove from widget hierarchy */
|
|
if (old_length == 1)
|
|
{
|
|
/* The widget might already be parent-less if we are in
|
|
* destruction, .e.g when closing a dock window.
|
|
*/
|
|
if (gtk_widget_get_parent (widget) != NULL)
|
|
gtk_container_remove (GTK_CONTAINER (paned_box), widget);
|
|
|
|
/* The last widget is removed, show the instructions */
|
|
//gtk_widget_show (paned_box->p->instructions);
|
|
drop_hints = g_slist_prepend (drop_hints, paned_box);
|
|
}
|
|
else
|
|
{
|
|
g_object_ref (widget);
|
|
|
|
parent = gtk_widget_get_parent (GTK_WIDGET (widget));
|
|
grandparent = gtk_widget_get_parent (parent);
|
|
|
|
if (index == 0)
|
|
other_widget = gtk_paned_get_child2 (GTK_PANED (parent));
|
|
else
|
|
other_widget = gtk_paned_get_child1 (GTK_PANED (parent));
|
|
|
|
g_object_ref (other_widget);
|
|
|
|
gtk_container_remove (GTK_CONTAINER (parent), other_widget);
|
|
gtk_container_remove (GTK_CONTAINER (parent), GTK_WIDGET (widget));
|
|
|
|
gtk_container_remove (GTK_CONTAINER (grandparent), parent);
|
|
|
|
if (GTK_IS_PANED (grandparent))
|
|
gtk_paned_pack1 (GTK_PANED (grandparent), other_widget, TRUE, FALSE);
|
|
else
|
|
gtk_box_pack_start (GTK_BOX (paned_box), other_widget, TRUE, TRUE, 0);
|
|
|
|
g_object_unref (other_widget);
|
|
|
|
g_object_unref (widget);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pika_paned_box_will_handle_drag:
|
|
* @paned_box: A #PikaPanedBox
|
|
* @widget: The widget that got the drag event
|
|
* @context: Context from drag event
|
|
* @x: x from drag event
|
|
* @y: y from drag event
|
|
* @time: time from drag event
|
|
*
|
|
* Returns: %TRUE if the drag event on @widget will be handled by
|
|
* @paned_box.
|
|
**/
|
|
gboolean
|
|
pika_paned_box_will_handle_drag (PikaPanedBox *paned_box,
|
|
GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
gint time)
|
|
{
|
|
gint paned_box_x = 0;
|
|
gint paned_box_y = 0;
|
|
GtkAllocation allocation = { 0, };
|
|
GtkOrientation orientation = 0;
|
|
gboolean will_handle = FALSE;
|
|
gint drop_area_size = 0;
|
|
|
|
g_return_val_if_fail (paned_box == NULL ||
|
|
PIKA_IS_PANED_BOX (paned_box), FALSE);
|
|
|
|
/* Check for NULL to allow cleaner client code */
|
|
if (paned_box == NULL)
|
|
return FALSE;
|
|
|
|
/* Our handler might handle it */
|
|
if (pika_paned_box_will_handle_drag (paned_box->p->drag_handler,
|
|
widget,
|
|
context,
|
|
x, y,
|
|
time))
|
|
{
|
|
/* Return TRUE so the client will pass on the drag event */
|
|
return TRUE;
|
|
}
|
|
|
|
/* If we don't have a common ancenstor we will not handle it */
|
|
if (! gtk_widget_translate_coordinates (widget,
|
|
GTK_WIDGET (paned_box),
|
|
x, y,
|
|
&paned_box_x, &paned_box_y))
|
|
{
|
|
/* Return FALSE so the client can take care of the drag event */
|
|
return FALSE;
|
|
}
|
|
|
|
/* We now have paned_box coordinates, see if the paned_box will
|
|
* handle the event
|
|
*/
|
|
gtk_widget_get_allocation (GTK_WIDGET (paned_box), &allocation);
|
|
orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (paned_box));
|
|
drop_area_size = pika_paned_box_get_drop_area_size (paned_box);
|
|
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
{
|
|
will_handle = (paned_box_x < drop_area_size ||
|
|
paned_box_x > allocation.width - drop_area_size);
|
|
}
|
|
else /*if (orientation = GTK_ORIENTATION_VERTICAL)*/
|
|
{
|
|
will_handle = (paned_box_y < drop_area_size ||
|
|
paned_box_y > allocation.height - drop_area_size);
|
|
}
|
|
|
|
return will_handle;
|
|
}
|
|
|
|
void
|
|
pika_paned_box_set_drag_handler (PikaPanedBox *paned_box,
|
|
PikaPanedBox *drag_handler)
|
|
{
|
|
g_return_if_fail (PIKA_IS_PANED_BOX (paned_box));
|
|
|
|
paned_box->p->drag_handler = drag_handler;
|
|
}
|