From: Tristan Van Berkom Date: Thu, 11 Nov 2010 09:13:54 +0000 (+0900) Subject: Added event handling to GtkCellAreaBox X-Git-Url: http://pileus.org/git/?a=commitdiff_plain;h=33db66e728357752c8d0ee90b324a5bfab469c72;p=~andy%2Fgtk Added event handling to GtkCellAreaBox Now GtkCellAreaBox handles the click event to activate renderers and checks if the area is in a sibling of a focus renderer, possibly activating the proper focus sibling renderer. Also GtkCellArea gains a "focus-changed" signal to allow it to change the currently focused row according to the button events. --- diff --git a/gtk/gtkcellarea.c b/gtk/gtkcellarea.c index e34c16307..0e6a2eed9 100644 --- a/gtk/gtkcellarea.c +++ b/gtk/gtkcellarea.c @@ -201,6 +201,7 @@ enum { SIGNAL_EDITING_CANCELED, SIGNAL_EDITING_DONE, SIGNAL_REMOVE_EDITABLE, + SIGNAL_FOCUS_CHANGED, LAST_SIGNAL }; @@ -326,6 +327,17 @@ gtk_cell_area_class_init (GtkCellAreaClass *class) GTK_TYPE_CELL_RENDERER, GTK_TYPE_CELL_EDITABLE); + cell_area_signals[SIGNAL_FOCUS_CHANGED] = + g_signal_new (I_("focus-changed"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, /* No class closure here */ + NULL, NULL, + _gtk_marshal_VOID__OBJECT_STRING, + G_TYPE_NONE, 2, + GTK_TYPE_CELL_RENDERER, + G_TYPE_STRING); + /* Properties */ g_object_class_install_property (object_class, PROP_CELL_MARGIN_LEFT, @@ -1861,6 +1873,13 @@ gtk_cell_area_set_focus_cell (GtkCellArea *area, g_object_notify (G_OBJECT (area), "focus-cell"); } + + /* Signal that the current focus renderer for this path changed + * (it may be that the focus cell did not change, but the row + * may have changed so we need to signal it) */ + g_signal_emit (area, cell_area_signals[SIGNAL_FOCUS_CHANGED], 0, + priv->focus_cell, priv->current_path); + } /** @@ -1988,6 +2007,40 @@ gtk_cell_area_get_focus_siblings (GtkCellArea *area, return g_hash_table_lookup (priv->focus_siblings, renderer); } +GtkCellRenderer * +gtk_cell_area_get_focus_from_sibling (GtkCellArea *area, + GtkCellRenderer *renderer) +{ + GtkCellRenderer *ret_renderer = NULL; + GList *renderers, *l; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + g_return_val_if_fail (GTK_IS_CELL_RENDERER (renderer), NULL); + + renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area)); + + for (l = renderers; l; l = l->next) + { + GtkCellRenderer *a_renderer = l->data; + const GList *list; + + for (list = gtk_cell_area_get_focus_siblings (area, a_renderer); + list; list = list->next) + { + GtkCellRenderer *sibling_renderer = list->data; + + if (sibling_renderer == renderer) + { + ret_renderer = a_renderer; + break; + } + } + } + g_list_free (renderers); + + return ret_renderer; +} + /************************************************************* * API: Cell Activation/Editing * *************************************************************/ diff --git a/gtk/gtkcellarea.h b/gtk/gtkcellarea.h index c1203541e..685d695ec 100644 --- a/gtk/gtkcellarea.h +++ b/gtk/gtkcellarea.h @@ -302,7 +302,8 @@ gboolean gtk_cell_area_is_focus_sibling (GtkCellArea GtkCellRenderer *sibling); G_CONST_RETURN GList *gtk_cell_area_get_focus_siblings (GtkCellArea *area, GtkCellRenderer *renderer); - +GtkCellRenderer *gtk_cell_area_get_focus_from_sibling (GtkCellArea *area, + GtkCellRenderer *renderer); /* Cell Activation/Editing */ void gtk_cell_area_set_edited_cell (GtkCellArea *area, diff --git a/gtk/gtkcellareabox.c b/gtk/gtkcellareabox.c index 45355aebe..0b0f23c62 100644 --- a/gtk/gtkcellareabox.c +++ b/gtk/gtkcellareabox.c @@ -892,9 +892,102 @@ gtk_cell_area_box_event (GtkCellArea *area, /* Also detect mouse events, for mouse events we need to allocate the renderers * and find which renderer needs to be activated. */ + if (event->type == GDK_BUTTON_PRESS) + { + GdkEventButton *button_event = (GdkEventButton *)event; + + if (button_event->button == 1) + { + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxPrivate *priv = box->priv; + GtkCellAreaBoxIter *box_iter = GTK_CELL_AREA_BOX_ITER (iter); + GSList *allocated_cells, *l; + GdkRectangle cell_background, inner_area; + GtkAllocation allocation; + gint event_x, event_y; + + gtk_widget_get_allocation (widget, &allocation); + + /* We may need some semantics to tell us the offset of the event + * window we are handling events for (i.e. GtkTreeView has a bin_window) */ + event_x = button_event->x; + event_y = button_event->y; + + cell_background = *cell_area; + + /* Get a list of cells with allocation sizes decided regardless + * of alignments and pack order etc. */ + allocated_cells = get_allocated_cells (box, box_iter, widget); + + for (l = allocated_cells; l; l = l->next) + { + AllocatedCell *cell = l->data; + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + cell_background.x = cell_area->x + cell->position; + cell_background.width = cell->size; + } + else + { + cell_background.y = cell_area->y + cell->position; + cell_background.height = cell->size; + } + + /* Remove margins from the background area to produce the cell area + */ + gtk_cell_area_inner_cell_area (area, &cell_background, &inner_area); + + if (event_x >= inner_area.x && event_x <= inner_area.x + inner_area.width && + event_y >= inner_area.y && event_y <= inner_area.y + inner_area.height) + { + GtkCellRenderer *event_renderer = NULL; + if (gtk_cell_renderer_can_focus (cell->renderer)) + event_renderer = cell->renderer; + else + { + GtkCellRenderer *focus_renderer; + + /* A renderer can have focus siblings but that renderer might not be + * focusable for every row... so we go on to check can_focus here. */ + focus_renderer = gtk_cell_area_get_focus_from_sibling (area, cell->renderer); + + if (focus_renderer && gtk_cell_renderer_can_focus (focus_renderer)) + event_renderer = focus_renderer; + } - return 0; + if (event_renderer) + { + if (gtk_cell_area_get_edited_cell (area)) + { + /* XXX Was it really canceled in this case ? */ + gtk_cell_area_stop_editing (area, TRUE); + gtk_cell_area_set_focus_cell (area, event_renderer); + retval = TRUE; + } + else + { + /* If we are activating via a focus sibling, we need to fix the + * cell area */ + if (event_renderer != cell->renderer) + gtk_cell_area_inner_cell_area (area, cell_area, &cell_background); + + gtk_cell_area_set_focus_cell (area, event_renderer); + + retval = gtk_cell_area_activate_cell (area, widget, event_renderer, + event, &cell_background, flags); + } + break; + } + } + } + g_slist_foreach (allocated_cells, (GFunc)allocated_cell_free, NULL); + g_slist_free (allocated_cells); + } + } + + return retval; } static void @@ -948,6 +1041,9 @@ gtk_cell_area_box_render (GtkCellArea *area, */ gtk_cell_area_inner_cell_area (area, &cell_background, &inner_area); + /* XXX TODO Here after getting the inner area of the cell background, + * add portions of the background area to the cell background */ + if (focus_cell && (cell->renderer == focus_cell || gtk_cell_area_is_focus_sibling (area, focus_cell, cell->renderer))) diff --git a/tests/cellareascaffold.c b/tests/cellareascaffold.c index 6d40be9ed..3e3787ab4 100644 --- a/tests/cellareascaffold.c +++ b/tests/cellareascaffold.c @@ -57,8 +57,13 @@ static void cell_area_scaffold_get_preferred_width_for_height (GtkWidget gint for_size, gint *minimum_size, gint *natural_size); +static void cell_area_scaffold_map (GtkWidget *widget); +static void cell_area_scaffold_unmap (GtkWidget *widget); static gint cell_area_scaffold_focus (GtkWidget *widget, GtkDirectionType direction); +static gboolean cell_area_scaffold_button_press (GtkWidget *widget, + GdkEventButton *event); + /* CellAreaScaffoldClass */ static void cell_area_scaffold_activate (CellAreaScaffold *scaffold); @@ -67,6 +72,10 @@ static void cell_area_scaffold_activate (CellAreaScaf static void size_changed_cb (GtkCellAreaIter *iter, GParamSpec *pspec, CellAreaScaffold *scaffold); +static void focus_changed_cb (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *path, + CellAreaScaffold *scaffold); static void row_changed_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, @@ -109,6 +118,7 @@ struct _CellAreaScaffoldPrivate { /* Focus handling */ gint focus_row; + gulong focus_changed_id; /* Check when the underlying area changes the size and * we need to queue a redraw */ @@ -161,6 +171,10 @@ cell_area_scaffold_init (CellAreaScaffold *scaffold) gtk_widget_set_has_window (GTK_WIDGET (scaffold), FALSE); gtk_widget_set_can_focus (GTK_WIDGET (scaffold), TRUE); + priv->focus_changed_id = + g_signal_connect (priv->area, "focus-changed", + G_CALLBACK (focus_changed_cb), scaffold); + priv->size_changed_id = g_signal_connect (priv->iter, "notify", G_CALLBACK (size_changed_cb), scaffold); @@ -187,7 +201,10 @@ cell_area_scaffold_class_init (CellAreaScaffoldClass *class) widget_class->get_preferred_height_for_width = cell_area_scaffold_get_preferred_height_for_width; widget_class->get_preferred_height = cell_area_scaffold_get_preferred_height; widget_class->get_preferred_width_for_height = cell_area_scaffold_get_preferred_width_for_height; + widget_class->map = cell_area_scaffold_map; + widget_class->unmap = cell_area_scaffold_unmap; widget_class->focus = cell_area_scaffold_focus; + widget_class->button_press_event = cell_area_scaffold_button_press; class->activate = cell_area_scaffold_activate; @@ -246,8 +263,11 @@ cell_area_scaffold_dispose (GObject *object) if (priv->area) { /* Disconnect signals */ + g_signal_handler_disconnect (priv->area, priv->focus_changed_id); + g_object_unref (priv->area); priv->area = NULL; + priv->focus_changed_id = 0; } G_OBJECT_CLASS (cell_area_scaffold_parent_class)->dispose (object); @@ -334,8 +354,7 @@ cell_area_scaffold_realize (GtkWidget *widget) gtk_widget_set_window (widget, window); g_object_ref (window); - priv->event_window = gdk_window_new (window, - &attributes, attributes_mask); + priv->event_window = gdk_window_new (window, &attributes, attributes_mask); gdk_window_set_user_data (priv->event_window, widget); gtk_widget_style_attach (widget); @@ -510,9 +529,6 @@ cell_area_scaffold_size_allocate (GtkWidget *widget, CellAreaScaffoldPrivate *priv = scaffold->priv; GtkOrientation orientation; - if (!priv->model) - return; - gtk_widget_set_allocation (widget, allocation); if (gtk_widget_get_realized (widget)) @@ -522,6 +538,9 @@ cell_area_scaffold_size_allocate (GtkWidget *widget, allocation->width, allocation->height); + if (!priv->model) + return; + orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area)); /* Cache the per-row sizes and allocate the iter */ @@ -700,6 +719,31 @@ cell_area_scaffold_get_preferred_width_for_height (GtkWidget *widget, } } +static void +cell_area_scaffold_map (GtkWidget *widget) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget); + CellAreaScaffoldPrivate *priv = scaffold->priv; + + GTK_WIDGET_CLASS (cell_area_scaffold_parent_class)->map (widget); + + if (priv->event_window) + gdk_window_show (priv->event_window); +} + +static void +cell_area_scaffold_unmap (GtkWidget *widget) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget); + CellAreaScaffoldPrivate *priv = scaffold->priv; + + GTK_WIDGET_CLASS (cell_area_scaffold_parent_class)->unmap (widget); + + if (priv->event_window) + gdk_window_hide (priv->event_window); +} + + static gint cell_area_scaffold_focus (GtkWidget *widget, GtkDirectionType direction) @@ -710,6 +754,7 @@ cell_area_scaffold_focus (GtkWidget *widget, gboolean valid; gint focus_row; GtkOrientation orientation; + gboolean changed = FALSE; /* Grab focus on ourself if we dont already have focus */ if (!gtk_widget_has_focus (widget)) @@ -719,6 +764,8 @@ cell_area_scaffold_focus (GtkWidget *widget, orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area)); focus_row = priv->focus_row; + + g_signal_handler_block (priv->area, priv->focus_changed_id); valid = gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, priv->focus_row); while (valid) @@ -733,7 +780,8 @@ cell_area_scaffold_focus (GtkWidget *widget, /* XXX A smarter implementation would only invalidate the rectangles where * focus was removed from and new focus was placed */ gtk_widget_queue_draw (widget); - return TRUE; + changed = TRUE; + break; } else { @@ -802,11 +850,84 @@ cell_area_scaffold_focus (GtkWidget *widget, } } + g_signal_handler_unblock (priv->area, priv->focus_changed_id); + /* XXX A smarter implementation would only invalidate the rectangles where * focus was removed from and new focus was placed */ gtk_widget_queue_draw (widget); - return FALSE; + return changed; +} + +static gboolean +cell_area_scaffold_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget); + CellAreaScaffoldPrivate *priv = scaffold->priv; + GtkTreeIter iter; + gboolean valid; + GtkOrientation orientation; + gint i = 0; + GdkRectangle event_area; + GtkAllocation allocation; + gboolean handled = FALSE; + + /* Move focus from cell to cell and row to row */ + orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area)); + + gtk_widget_get_allocation (widget, &allocation); + + event_area.x = 0; + event_area.y = 0; + event_area.width = allocation.width; + event_area.height = allocation.height; + + valid = gtk_tree_model_get_iter_first (priv->model, &iter); + while (valid) + { + RowData *data = &g_array_index (priv->row_data, RowData, i); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + event_area.height = data->size; + + if (event->y >= allocation.y + event_area.y && + event->y <= allocation.y + event_area.y + event_area.height) + { + /* XXX A real implementation would assemble GtkCellRendererState flags here */ + gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE); + handled = gtk_cell_area_event (priv->area, priv->iter, GTK_WIDGET (scaffold), + (GdkEvent *)event, &event_area, 0); + break; + } + + event_area.y += data->size; + event_area.y += ROW_SPACING; + } + else + { + event_area.width = data->size; + + if (event->x >= allocation.x + event_area.x && + event->x <= allocation.x + event_area.x + event_area.width) + { + /* XXX A real implementation would assemble GtkCellRendererState flags here */ + gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE); + handled = gtk_cell_area_event (priv->area, priv->iter, GTK_WIDGET (scaffold), + (GdkEvent *)event, &event_area, 0); + break; + } + + event_area.x += data->size; + event_area.x += ROW_SPACING; + } + + i++; + valid = gtk_tree_model_iter_next (priv->model, &iter); + } + + return handled; } /********************************************************* @@ -875,6 +996,42 @@ size_changed_cb (GtkCellAreaIter *iter, gtk_widget_queue_resize (GTK_WIDGET (scaffold)); } +static void +focus_changed_cb (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *path, + CellAreaScaffold *scaffold) +{ + CellAreaScaffoldPrivate *priv = scaffold->priv; + GtkWidget *widget = GTK_WIDGET (scaffold); + GtkTreePath *treepath; + gboolean found = FALSE; + gint *indices; + + if (!priv->model) + return; + + /* We can be signaled that a renderer lost focus, here + * we dont care */ + if (!renderer) + return; + + treepath = gtk_tree_path_new_from_string (path); + indices = gtk_tree_path_get_indices (treepath); + + priv->focus_row = indices[0]; + + gtk_tree_path_free (treepath); + + g_print ("Focus changed signal, new focus row %d\n", priv->focus_row); + + /* Make sure we have focus now */ + if (!gtk_widget_has_focus (widget)) + gtk_widget_grab_focus (widget); + + gtk_widget_queue_draw (widget); +} + static void rebuild_and_flush_internals (CellAreaScaffold *scaffold) {