diff options
Diffstat (limited to 'src/gtksctree.c')
-rw-r--r-- | src/gtksctree.c | 573 |
1 files changed, 573 insertions, 0 deletions
diff --git a/src/gtksctree.c b/src/gtksctree.c new file mode 100644 index 00000000..eddb52fc --- /dev/null +++ b/src/gtksctree.c @@ -0,0 +1,573 @@ +/* + * This program is based on gtkflist.c + */ + +#include "gtksctree.h" +#include "sylpheed-marshal.h" + +enum { + ROW_POPUP_MENU, + EMPTY_POPUP_MENU, + OPEN_ROW, + START_DRAG, + LAST_SIGNAL +}; + + +static void gtk_sctree_class_init (GtkSCTreeClass *class); +static void gtk_sctree_init (GtkSCTree *sctree); + +static gint gtk_sctree_button_press (GtkWidget *widget, GdkEventButton *event); +static gint gtk_sctree_button_release (GtkWidget *widget, GdkEventButton *event); +static gint gtk_sctree_motion (GtkWidget *widget, GdkEventMotion *event); +static void gtk_sctree_drag_begin (GtkWidget *widget, GdkDragContext *context); +static void gtk_sctree_drag_end (GtkWidget *widget, GdkDragContext *context); +static void gtk_sctree_drag_data_get (GtkWidget *widget, GdkDragContext *context, + GtkSelectionData *data, guint info, guint time); +static void gtk_sctree_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time); +static gboolean gtk_sctree_drag_motion (GtkWidget *widget, GdkDragContext *context, + gint x, gint y, guint time); +static gboolean gtk_sctree_drag_drop (GtkWidget *widget, GdkDragContext *context, + gint x, gint y, guint time); +static void gtk_sctree_drag_data_received (GtkWidget *widget, GdkDragContext *context, + gint x, gint y, GtkSelectionData *data, + guint info, guint time); + +static void gtk_sctree_clear (GtkCList *clist); +static void gtk_sctree_collapse (GtkCTree *ctree, GtkCTreeNode *node); + +static GtkCTreeClass *parent_class; + +static guint sctree_signals[LAST_SIGNAL]; + + +/** + * gtk_sctree_get_type: + * @void: + * + * Creates the GtkSCTree class and its type information + * + * Return value: The type ID for GtkSCTreeClass + **/ +GType +gtk_sctree_get_type (void) +{ + static GType sctree_type = 0; + + if (!sctree_type) { + GTypeInfo sctree_info = { + sizeof (GtkSCTreeClass), + + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + + (GClassInitFunc) gtk_sctree_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + + sizeof (GtkSCTree), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_sctree_init, + }; + + sctree_type = g_type_register_static (GTK_TYPE_CTREE, "GtkSCTree", &sctree_info, (GTypeFlags)0); + } + + return sctree_type; +} + +/* Standard class initialization function */ +static void +gtk_sctree_class_init (GtkSCTreeClass *klass) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkCListClass *clist_class; + GtkCTreeClass *ctree_class; + + object_class = (GtkObjectClass *) klass; + widget_class = (GtkWidgetClass *) klass; + clist_class = (GtkCListClass *) klass; + ctree_class = (GtkCTreeClass *) klass; + + parent_class = gtk_type_class (gtk_ctree_get_type ()); + + sctree_signals[ROW_POPUP_MENU] = + g_signal_new ("row_popup_menu", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkSCTreeClass, row_popup_menu), + NULL, NULL, + sylpheed_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + GDK_TYPE_EVENT); + sctree_signals[EMPTY_POPUP_MENU] = + g_signal_new ("empty_popup_menu", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkSCTreeClass, empty_popup_menu), + NULL, NULL, + sylpheed_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + GDK_TYPE_EVENT); + sctree_signals[OPEN_ROW] = + g_signal_new ("open_row", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkSCTreeClass, open_row), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + sctree_signals[START_DRAG] = + g_signal_new ("start_drag", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkSCTreeClass, start_drag), + NULL, NULL, + sylpheed_marshal_VOID__INT_POINTER, + G_TYPE_NONE, 2, + G_TYPE_INT, + GDK_TYPE_EVENT); + + /* gtk_object_class_add_signals (object_class, sctree_signals, LAST_SIGNAL); */ + + clist_class->clear = gtk_sctree_clear; + ctree_class->tree_collapse = gtk_sctree_collapse; + + widget_class->button_press_event = gtk_sctree_button_press; + widget_class->button_release_event = gtk_sctree_button_release; + widget_class->motion_notify_event = gtk_sctree_motion; + widget_class->drag_begin = gtk_sctree_drag_begin; + widget_class->drag_end = gtk_sctree_drag_end; + widget_class->drag_data_get = gtk_sctree_drag_data_get; + widget_class->drag_leave = gtk_sctree_drag_leave; + widget_class->drag_motion = gtk_sctree_drag_motion; + widget_class->drag_drop = gtk_sctree_drag_drop; + widget_class->drag_data_received = gtk_sctree_drag_data_received; +} + +/* Standard object initialization function */ +static void +gtk_sctree_init (GtkSCTree *sctree) +{ + sctree->anchor_row = NULL; + + /* GtkCTree does not specify pointer motion by default */ + gtk_widget_add_events (GTK_WIDGET (sctree), GDK_POINTER_MOTION_MASK); + gtk_widget_add_events (GTK_WIDGET (sctree), GDK_POINTER_MOTION_MASK); +} + +/* Get information the specified row is selected. */ + +static gboolean +row_is_selected(GtkSCTree *sctree, gint row) +{ + GtkCListRow *clist_row; + clist_row = g_list_nth (GTK_CLIST(sctree)->row_list, row)->data; + return clist_row ? clist_row->state == GTK_STATE_SELECTED : FALSE; +} + +/* Selects the rows between the anchor to the specified row, inclusive. */ +static void +select_range (GtkSCTree *sctree, gint row) +{ + gint prev_row; + gint min, max; + gint i; + + if (sctree->anchor_row == NULL) { + prev_row = row; + sctree->anchor_row = gtk_ctree_node_nth(GTK_CTREE(sctree), row); + } else + prev_row = g_list_position(GTK_CLIST(sctree)->row_list, + (GList *)sctree->anchor_row); + + if (row < prev_row) { + min = row; + max = prev_row; + } else { + min = prev_row; + max = row; + } + for (i = min; i <= max; i++) + gtk_clist_select_row (GTK_CLIST (sctree), i, -1); +} + +/* Handles row selection according to the specified modifier state */ +static void +select_row (GtkSCTree *sctree, gint row, gint col, guint state) +{ + gboolean range, additive; + g_return_if_fail (sctree != NULL); + g_return_if_fail (GTK_IS_SCTREE (sctree)); + + range = ((state & GDK_SHIFT_MASK) != 0) && + (GTK_CLIST(sctree)->selection_mode != GTK_SELECTION_SINGLE) && + (GTK_CLIST(sctree)->selection_mode != GTK_SELECTION_BROWSE); + additive = ((state & GDK_CONTROL_MASK) != 0) && + (GTK_CLIST(sctree)->selection_mode != GTK_SELECTION_SINGLE) && + (GTK_CLIST(sctree)->selection_mode != GTK_SELECTION_BROWSE); + + gtk_clist_freeze (GTK_CLIST (sctree)); + GTK_CLIST(sctree)->focus_row = row; + GTK_CLIST_GET_CLASS(sctree)->refresh(GTK_CLIST(sctree)); + if (!additive) + gtk_clist_unselect_all (GTK_CLIST (sctree)); + + if (!range) { + GtkCTreeNode *node; + + node = gtk_ctree_node_nth (GTK_CTREE(sctree), row); + + /*No need to manage overlapped list*/ + if (additive) { + if (row_is_selected(sctree, row)) + gtk_clist_unselect_row (GTK_CLIST (sctree), row, col); + else + g_signal_emit_by_name + (G_OBJECT (sctree), + "tree_select_row", node, col); + } else { + g_signal_emit_by_name + (G_OBJECT (sctree), + "tree_select_row", node, col); + } + sctree->anchor_row = node; + } else + select_range (sctree, row); + gtk_clist_thaw (GTK_CLIST (sctree)); +} + +/* Our handler for button_press events. We override all of GtkCList's broken + * behavior. + */ +static gint +gtk_sctree_button_press (GtkWidget *widget, GdkEventButton *event) +{ + GtkSCTree *sctree; + GtkCList *clist; + gboolean on_row; + gint row; + gint col; + gint retval; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_SCTREE (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + sctree = GTK_SCTREE (widget); + clist = GTK_CLIST (widget); + retval = FALSE; + + if (event->window != clist->clist_window) + return (* GTK_WIDGET_CLASS (parent_class)->button_press_event) (widget, event); + + on_row = gtk_clist_get_selection_info (clist, event->x, event->y, &row, &col); + + if (on_row && !GTK_WIDGET_HAS_FOCUS(widget)) + gtk_widget_grab_focus (widget); + + if (gtk_ctree_is_hot_spot (GTK_CTREE(sctree), event->x, event->y)) { + gtk_ctree_toggle_expansion + (GTK_CTREE(sctree), + gtk_ctree_node_nth(GTK_CTREE(sctree), row)); + return TRUE; + } + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button == 1 || event->button == 2) { + if (event->button == 2) + event->state &= ~(GDK_SHIFT_MASK | GDK_CONTROL_MASK); + if (on_row) { + /* Save the mouse info for DnD */ + sctree->dnd_press_button = event->button; + sctree->dnd_press_x = event->x; + sctree->dnd_press_y = event->y; + + /* Handle selection */ + if ((row_is_selected (sctree, row) + && !(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) + || ((event->state & GDK_CONTROL_MASK) + && !(event->state & GDK_SHIFT_MASK))) { + sctree->dnd_select_pending = TRUE; + sctree->dnd_select_pending_state = event->state; + sctree->dnd_select_pending_row = row; + } else + select_row (sctree, row, col, event->state); + } else + gtk_clist_unselect_all (clist); + + retval = TRUE; + } else if (event->button == 3) { + /* Emit *_popup_menu signal*/ + if (on_row) { + if (!row_is_selected(sctree,row)) + select_row (sctree, row, col, 0); + g_signal_emit (G_OBJECT (sctree), + sctree_signals[ROW_POPUP_MENU], + 0, event); + } else { + gtk_clist_unselect_all(clist); + g_signal_emit (G_OBJECT (sctree), + sctree_signals[EMPTY_POPUP_MENU], + 0, event); + } + retval = TRUE; + } + + break; + + case GDK_2BUTTON_PRESS: + if (event->button != 1) + break; + + sctree->dnd_select_pending = FALSE; + sctree->dnd_select_pending_state = 0; + + if (on_row) + g_signal_emit (G_OBJECT (sctree), + sctree_signals[OPEN_ROW], 0); + + retval = TRUE; + break; + + default: + break; + } + + return retval; +} + +/* Our handler for button_release events. We override all of GtkCList's broken + * behavior. + */ +static gint +gtk_sctree_button_release (GtkWidget *widget, GdkEventButton *event) +{ + GtkSCTree *sctree; + GtkCList *clist; + gint on_row; + gint row, col; + gint retval; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_SCTREE (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + sctree = GTK_SCTREE (widget); + clist = GTK_CLIST (widget); + retval = FALSE; + + if (event->window != clist->clist_window) + return (* GTK_WIDGET_CLASS (parent_class)->button_release_event) (widget, event); + + on_row = gtk_clist_get_selection_info (clist, event->x, event->y, &row, &col); + + if (!(event->button == 1 || event->button == 2)) + return FALSE; + + sctree->dnd_press_button = 0; + sctree->dnd_press_x = 0; + sctree->dnd_press_y = 0; + + if (on_row) { + if (sctree->dnd_select_pending) { + select_row (sctree, row, col, sctree->dnd_select_pending_state); + sctree->dnd_select_pending = FALSE; + sctree->dnd_select_pending_state = 0; + } + + retval = TRUE; + } + + return retval; +} + +/* Our handler for motion_notify events. We override all of GtkCList's broken + * behavior. + */ +static gint +gtk_sctree_motion (GtkWidget *widget, GdkEventMotion *event) +{ + GtkSCTree *sctree; + GtkCList *clist; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_SCTREE (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + sctree = GTK_SCTREE (widget); + clist = GTK_CLIST (widget); + + if (event->window != clist->clist_window) + return (* GTK_WIDGET_CLASS (parent_class)->motion_notify_event) (widget, event); + + if (!((sctree->dnd_press_button == 1 && (event->state & GDK_BUTTON1_MASK)) + || (sctree->dnd_press_button == 2 && (event->state & GDK_BUTTON2_MASK)))) + return FALSE; + + /* This is the same threshold value that is used in gtkdnd.c */ + + if (MAX (ABS (sctree->dnd_press_x - event->x), + ABS (sctree->dnd_press_y - event->y)) <= 3) + return FALSE; + + /* Handle any pending selections */ + + if (sctree->dnd_select_pending) { + if (!row_is_selected(sctree,sctree->dnd_select_pending_row)) + select_row (sctree, + sctree->dnd_select_pending_row, + -1, + sctree->dnd_select_pending_state); + + sctree->dnd_select_pending = FALSE; + sctree->dnd_select_pending_state = 0; + } + + g_signal_emit (G_OBJECT (sctree), + sctree_signals[START_DRAG], + 0, + sctree->dnd_press_button, + event); + return TRUE; +} + +/* We override the drag_begin signal to do nothing */ +static void +gtk_sctree_drag_begin (GtkWidget *widget, GdkDragContext *context) +{ + /* nothing */ +} + +/* We override the drag_end signal to do nothing */ +static void +gtk_sctree_drag_end (GtkWidget *widget, GdkDragContext *context) +{ + /* nothing */ +} + +/* We override the drag_data_get signal to do nothing */ +static void +gtk_sctree_drag_data_get (GtkWidget *widget, GdkDragContext *context, + GtkSelectionData *data, guint info, guint time) +{ + /* nothing */ +} + +/* We override the drag_leave signal to do nothing */ +static void +gtk_sctree_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time) +{ + /* nothing */ +} + +/* We override the drag_motion signal to do nothing */ +static gboolean +gtk_sctree_drag_motion (GtkWidget *widget, GdkDragContext *context, + gint x, gint y, guint time) +{ + return FALSE; +} + +/* We override the drag_drop signal to do nothing */ +static gboolean +gtk_sctree_drag_drop (GtkWidget *widget, GdkDragContext *context, + gint x, gint y, guint time) +{ + return FALSE; +} + +/* We override the drag_data_received signal to do nothing */ +static void +gtk_sctree_drag_data_received (GtkWidget *widget, GdkDragContext *context, + gint x, gint y, GtkSelectionData *data, + guint info, guint time) +{ + /* nothing */ +} + +/* Our handler for the clear signal of the clist. We have to reset the anchor + * to null. + */ +static void +gtk_sctree_clear (GtkCList *clist) +{ + GtkSCTree *sctree; + + g_return_if_fail (clist != NULL); + g_return_if_fail (GTK_IS_SCTREE (clist)); + + sctree = GTK_SCTREE (clist); + sctree->anchor_row = NULL; + + if (((GtkCListClass *)parent_class)->clear) + (* ((GtkCListClass *)parent_class)->clear) (clist); +} + +/* Our handler for the change_focus_row_expansion signal of the ctree. + We have to set the anchor to parent visible node. + */ +static void +gtk_sctree_collapse (GtkCTree *ctree, GtkCTreeNode *node) +{ + g_return_if_fail (ctree != NULL); + g_return_if_fail (GTK_IS_SCTREE (ctree)); + + (* parent_class->tree_collapse) (ctree, node); + GTK_SCTREE(ctree)->anchor_row = + gtk_ctree_node_nth(ctree, GTK_CLIST(ctree)->focus_row); +} + +GtkWidget *gtk_sctree_new_with_titles (gint columns, gint tree_column, + gchar *titles[]) +{ +#if 0 + GtkSCTree* sctree; + + sctree = gtk_type_new (gtk_sctree_get_type ()); + gtk_ctree_construct (GTK_CTREE (sctree), columns, tree_column, titles); + gtk_clist_set_selection_mode(GTK_CLIST(sctree), GTK_SELECTION_EXTENDED); + + return GTK_WIDGET (sctree); +#else + GtkWidget *widget; + + g_return_val_if_fail (columns > 0, NULL); + g_return_val_if_fail (tree_column >= 0 && tree_column < columns, NULL); + + widget = gtk_widget_new (TYPE_GTK_SCTREE, + "n_columns", columns, + "tree_column", tree_column, + NULL); + if (titles) { + GtkCList *clist = GTK_CLIST (widget); + guint i; + + for (i = 0; i < columns; i++) + gtk_clist_set_column_title (clist, i, titles[i]); + gtk_clist_column_titles_show (clist); + } + + return widget; +#endif +} + +void gtk_sctree_select (GtkSCTree *sctree, GtkCTreeNode *node) +{ + select_row(sctree, + g_list_position(GTK_CLIST(sctree)->row_list, (GList *)node), + -1, 0); +} + +void gtk_sctree_unselect_all (GtkSCTree *sctree) +{ + gtk_clist_unselect_all(GTK_CLIST(sctree)); + sctree->anchor_row = NULL; +} + +void gtk_sctree_set_anchor_row (GtkSCTree *sctree, GtkCTreeNode *node) +{ + sctree->anchor_row = node; +} |