diff --git a/gtk/gtkcellarea.c b/gtk/gtkcellarea.c index fc30fb88c3..ccc1b71bc9 100644 --- a/gtk/gtkcellarea.c +++ b/gtk/gtkcellarea.c @@ -130,10 +130,21 @@ typedef struct { struct _GtkCellAreaPrivate { + /* The GtkCellArea bookkeeps any connected + * attributes in this hash table. + */ GHashTable *cell_info; + /* The cell border decides how much space to reserve + * around each cell for the background_area + */ GtkBorder cell_border; + /* Current path is saved as a side-effect + * of gtk_cell_area_apply_attributes() */ + gchar *current_path; + + GtkCellRenderer *edited_cell; GtkCellRenderer *focus_cell; guint can_focus : 1; @@ -144,11 +155,14 @@ enum { PROP_CELL_MARGIN_LEFT, PROP_CELL_MARGIN_RIGHT, PROP_CELL_MARGIN_TOP, - PROP_CELL_MARGIN_BOTTOM + PROP_CELL_MARGIN_BOTTOM, + PROP_FOCUS_CELL, + PROP_EDITED_CELL }; enum { SIGNAL_FOCUS_LEAVE, + SIGNAL_EDITING_STARTED, LAST_SIGNAL }; @@ -229,6 +243,17 @@ gtk_cell_area_class_init (GtkCellAreaClass *class) G_TYPE_NONE, 2, GTK_TYPE_DIRECTION_TYPE, G_TYPE_STRING); + cell_area_signals[SIGNAL_EDITING_STARTED] = + g_signal_new (I_("editing-started"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, /* No class closure here */ + NULL, NULL, + _gtk_marshal_VOID__OBJECT_OBJECT_STRING, + G_TYPE_NONE, 3, + GTK_TYPE_CELL_RENDERER, + GTK_TYPE_CELL_EDITABLE, + G_TYPE_STRING); /* Properties */ g_object_class_install_property (object_class, @@ -275,6 +300,24 @@ gtk_cell_area_class_init (GtkCellAreaClass *class) 0, GTK_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_FOCUS_CELL, + g_param_spec_object + ("focus-cell", + P_("Focus Cell"), + P_("The cell which currently has focus"), + GTK_TYPE_CELL_RENDERER, + GTK_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_EDITED_CELL, + g_param_spec_object + ("edited-cell", + P_("Edited Cell"), + P_("The cell which is currently being edited"), + GTK_TYPE_CELL_RENDERER, + GTK_PARAM_READWRITE)); + /* Pool for Cell Properties */ if (!cell_property_pool) cell_property_pool = g_param_spec_pool_new (FALSE); @@ -366,6 +409,8 @@ gtk_cell_area_finalize (GObject *object) */ g_hash_table_destroy (priv->cell_info); + g_free (priv->current_path); + G_OBJECT_CLASS (gtk_cell_area_parent_class)->finalize (object); } @@ -379,8 +424,9 @@ gtk_cell_area_dispose (GObject *object) */ gtk_cell_layout_clear (GTK_CELL_LAYOUT (object)); - /* Remove any ref to a focused cell */ + /* Remove any ref to a focused/edited cell */ gtk_cell_area_set_focus_cell (GTK_CELL_AREA (object), NULL); + gtk_cell_area_set_edited_cell (GTK_CELL_AREA (object), NULL); G_OBJECT_CLASS (gtk_cell_area_parent_class)->dispose (object); } @@ -407,6 +453,9 @@ gtk_cell_area_set_property (GObject *object, case PROP_CELL_MARGIN_BOTTOM: gtk_cell_area_set_cell_margin_bottom (area, g_value_get_int (value)); break; + case PROP_FOCUS_CELL: + gtk_cell_area_set_focus_cell (area, (GtkCellRenderer *)g_value_get_object (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -436,6 +485,9 @@ gtk_cell_area_get_property (GObject *object, case PROP_CELL_MARGIN_BOTTOM: g_value_set_int (value, priv->cell_border.bottom); break; + case PROP_FOCUS_CELL: + g_value_set_object (value, priv->focus_cell); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -453,16 +505,73 @@ gtk_cell_area_real_event (GtkCellArea *area, const GdkRectangle *cell_area, GtkCellRendererState flags) { - if (event->type == GDK_KEY_PRESS) + if (event->type == GDK_KEY_PRESS && (flags & GTK_CELL_RENDERER_FOCUSED) != 0) { + GdkEventKey *key_event = (GdkEventKey *)event; GtkCellAreaPrivate *priv = area->priv; - if (priv->focus_cell) + if (priv->focus_cell && + (key_event->keyval == GDK_KEY_space || + key_event->keyval == GDK_KEY_KP_Space || + key_event->keyval == GDK_KEY_Return || + key_event->keyval == GDK_KEY_ISO_Enter || + key_event->keyval == GDK_KEY_KP_Enter)) { - /* Activate of Edit the currently focused cell */ + /* Activate or Edit the currently focused cell */ + GtkCellRendererMode mode; + GdkRectangle background_area; + GdkRectangle inner_area; + /* Get the allocation of the focused cell. + */ + gtk_cell_area_get_cell_allocation (area, iter, widget, priv->focus_cell, + cell_area, &background_area); - return TRUE; + /* Remove margins from the background area to produce the cell area. + */ + gtk_cell_area_inner_cell_area (area, &background_area, &inner_area); + + /* XXX Need to do some extra right-to-left casing either here + * or inside the above called apis. + */ + + g_object_get (priv->focus_cell, "mode", &mode, NULL); + + if (mode == GTK_CELL_RENDERER_MODE_ACTIVATABLE) + { + if (gtk_cell_renderer_activate (priv->focus_cell, + event, widget, + priv->current_path, + &background_area, + &inner_area, + flags)) + return TRUE; + } + else if (mode == GTK_CELL_RENDERER_MODE_EDITABLE) + { + GtkCellEditable *editable_widget; + + editable_widget = + gtk_cell_renderer_start_editing (priv->focus_cell, + event, widget, + priv->current_path, + &background_area, + &inner_area, + flags); + + if (editable_widget != NULL) + { + g_return_val_if_fail (GTK_IS_CELL_EDITABLE (editable_widget), FALSE); + + gtk_cell_area_set_edited_cell (area, priv->focus_cell); + + /* Signal that editing started so that callers can get + * a handle on the editable_widget */ + gtk_cell_area_editing_started (area, priv->focus_cell, editable_widget); + + return TRUE; + } + } } } @@ -662,6 +771,14 @@ gtk_cell_area_get_cells (GtkCellLayout *cell_layout) /************************************************************* * API * *************************************************************/ + +/** + * gtk_cell_area_add: + * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer to add to @area + * + * Adds @renderer to @area with the default child cell properties. + */ void gtk_cell_area_add (GtkCellArea *area, GtkCellRenderer *renderer) @@ -680,6 +797,13 @@ gtk_cell_area_add (GtkCellArea *area, g_type_name (G_TYPE_FROM_INSTANCE (area))); } +/** + * gtk_cell_area_remove: + * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer to add to @area + * + * Removes @renderer from @area. + */ void gtk_cell_area_remove (GtkCellArea *area, GtkCellRenderer *renderer) @@ -703,6 +827,14 @@ gtk_cell_area_remove (GtkCellArea *area, g_type_name (G_TYPE_FROM_INSTANCE (area))); } +/** + * gtk_cell_area_forall + * @area: a #GtkCellArea + * @callback: the #GtkCellCallback to call + * @callback_data: user provided data pointer + * + * Calls @callback for every #GtkCellRenderer in @area. + */ void gtk_cell_area_forall (GtkCellArea *area, GtkCellCallback callback, @@ -722,6 +854,45 @@ gtk_cell_area_forall (GtkCellArea *area, g_type_name (G_TYPE_FROM_INSTANCE (area))); } +/** + * gtk_cell_area_get_cell_allocation: + * @area: a #GtkCellArea + * @iter: the #GtkCellAreaIter used to hold sizes for @area. + * @widget: the #GtkWidget that @area is rendering on + * @renderer: the #GtkCellRenderer to get the allocation for + * @cell_area: the whole allocated area for @area in @widget + * for this row + * @allocation: where to store the allocation for @renderer + * + * Derives the allocation of @renderer inside @area if @area + * were to be renderered in @cell_area. + */ +void +gtk_cell_area_get_cell_allocation (GtkCellArea *area, + GtkCellAreaIter *iter, + GtkWidget *widget, + GtkCellRenderer *renderer, + const GdkRectangle *cell_area, + GdkRectangle *allocation) +{ + GtkCellAreaClass *class; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_AREA_ITER (iter)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + g_return_if_fail (cell_area != NULL); + g_return_if_fail (allocation != NULL); + + class = GTK_CELL_AREA_GET_CLASS (area); + + if (class->get_cell_allocation) + class->get_cell_allocation (area, iter, widget, renderer, cell_area, allocation); + else + g_warning ("GtkCellAreaClass::get_cell_allocation not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); +} + gint gtk_cell_area_event (GtkCellArea *area, GtkCellAreaIter *iter, @@ -893,6 +1064,17 @@ gtk_cell_area_get_preferred_width_for_height (GtkCellArea *area, /************************************************************* * API: Attributes * *************************************************************/ + +/** + * gtk_cell_area_attribute_connect: + * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer to connect an attribute for + * @attribute: the attribute name + * @column: the #GtkTreeModel column to fetch attribute values from + * + * Connects an @attribute to apply values from @column for the + * #GtkTreeModel in use. + */ void gtk_cell_area_attribute_connect (GtkCellArea *area, GtkCellRenderer *renderer, @@ -949,6 +1131,16 @@ gtk_cell_area_attribute_connect (GtkCellArea *area, info->attributes = g_slist_prepend (info->attributes, cell_attribute); } +/** + * gtk_cell_area_attribute_disconnect: + * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer to disconnect an attribute for + * @attribute: the attribute name + * + * Disconnects @attribute for the @renderer in @area so that + * attribute will no longer be updated with values from the + * model. + */ void gtk_cell_area_attribute_disconnect (GtkCellArea *area, GtkCellRenderer *renderer, @@ -1025,6 +1217,18 @@ apply_cell_attributes (GtkCellRenderer *renderer, g_object_thaw_notify (G_OBJECT (renderer)); } +/** + * gtk_cell_area_apply_attributes + * @area: a #GtkCellArea + * @tree_model: a #GtkTreeModel to pull values from + * @iter: the #GtkTreeIter in @tree_model to apply values for + * @is_expander: whether @iter has children + * @is_expanded: whether @iter is expanded in the view and + * children are visible + * + * Applies any connected attributes to the renderers in + * @area by pulling the values from @tree_model. + */ void gtk_cell_area_apply_attributes (GtkCellArea *area, GtkTreeModel *tree_model, @@ -1034,6 +1238,7 @@ gtk_cell_area_apply_attributes (GtkCellArea *area, { GtkCellAreaPrivate *priv; AttributeData data; + GtkTreePath *path; g_return_if_fail (GTK_IS_CELL_AREA (area)); g_return_if_fail (GTK_IS_TREE_MODEL (tree_model)); @@ -1051,8 +1256,37 @@ gtk_cell_area_apply_attributes (GtkCellArea *area, /* Go over any cells that have attributes or custom GtkCellLayoutDataFuncs and * apply the data from the treemodel */ g_hash_table_foreach (priv->cell_info, (GHFunc)apply_cell_attributes, &data); + + /* Update the currently applied path */ + g_free (priv->current_path); + path = gtk_tree_model_get_path (tree_model, iter); + priv->current_path = gtk_tree_path_to_string (path); + gtk_tree_path_free (path); } +/** + * gtk_cell_area_get_current_path_string: + * @area: a #GtkCellArea + * + * Gets the current #GtkTreePath string for the currently + * applied #GtkTreeIter, this is implicitly updated when + * gtk_cell_area_apply_attributes() is called and can be + * used to interact with renderers from #GtkCellArea + * subclasses. + */ +const gchar * +gtk_cell_area_get_current_path_string (GtkCellArea *area) +{ + GtkCellAreaPrivate *priv; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + + priv = area->priv; + + return priv->current_path; +} + + /************************************************************* * API: Cell Properties * *************************************************************/ @@ -1566,6 +1800,8 @@ gtk_cell_area_set_focus_cell (GtkCellArea *area, if (priv->focus_cell) g_object_ref (priv->focus_cell); + + g_object_notify (G_OBJECT (area), "focus-cell"); } } @@ -1589,6 +1825,43 @@ gtk_cell_area_get_focus_cell (GtkCellArea *area) return priv->focus_cell; } +void +gtk_cell_area_set_edited_cell (GtkCellArea *area, + GtkCellRenderer *renderer) +{ + GtkCellAreaPrivate *priv; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (renderer == NULL || GTK_IS_CELL_RENDERER (renderer)); + + priv = area->priv; + + if (priv->edited_cell != renderer) + { + if (priv->edited_cell) + g_object_unref (priv->edited_cell); + + priv->edited_cell = renderer; + + if (priv->edited_cell) + g_object_ref (priv->edited_cell); + + g_object_notify (G_OBJECT (area), "edited-cell"); + } +} + +GtkCellRenderer * +gtk_cell_area_get_edited_cell (GtkCellArea *area) +{ + GtkCellAreaPrivate *priv; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + + priv = area->priv; + + return priv->edited_cell; +} + /************************************************************* * API: Margins * *************************************************************/ @@ -1697,6 +1970,21 @@ gtk_cell_area_set_cell_margin_bottom (GtkCellArea *area, } /* For convenience in area implementations */ +void +gtk_cell_area_editing_started (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *editable) +{ + GtkCellAreaPrivate *priv; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + + priv = area->priv; + + g_signal_emit (area, cell_area_signals[SIGNAL_EDITING_STARTED], 0, + renderer, editable, priv->current_path); +} + void gtk_cell_area_inner_cell_area (GtkCellArea *area, GdkRectangle *background_area, diff --git a/gtk/gtkcellarea.h b/gtk/gtkcellarea.h index 9c095538bd..0e806df688 100644 --- a/gtk/gtkcellarea.h +++ b/gtk/gtkcellarea.h @@ -79,6 +79,12 @@ struct _GtkCellAreaClass void (* forall) (GtkCellArea *area, GtkCellCallback callback, gpointer callback_data); + void (* get_cell_allocation) (GtkCellArea *area, + GtkCellAreaIter *iter, + GtkWidget *widget, + GtkCellRenderer *renderer, + const GdkRectangle *cell_area, + GdkRectangle *allocation); gint (* event) (GtkCellArea *area, GtkCellAreaIter *iter, GtkWidget *widget, @@ -156,6 +162,12 @@ void gtk_cell_area_remove (GtkCellArea void gtk_cell_area_forall (GtkCellArea *area, GtkCellCallback callback, gpointer callback_data); +void gtk_cell_area_get_cell_allocation (GtkCellArea *area, + GtkCellAreaIter *iter, + GtkWidget *widget, + GtkCellRenderer *renderer, + const GdkRectangle *cell_area, + GdkRectangle *allocation); gint gtk_cell_area_event (GtkCellArea *area, GtkCellAreaIter *iter, GtkWidget *widget, @@ -194,6 +206,8 @@ void gtk_cell_area_get_preferred_width_for_height (GtkCellArea gint height, gint *minimum_width, gint *natural_width); +G_CONST_RETURN gchar *gtk_cell_area_get_current_path_string (GtkCellArea *area); + /* Attributes */ void gtk_cell_area_apply_attributes (GtkCellArea *area, @@ -263,6 +277,9 @@ gboolean gtk_cell_area_get_can_focus (GtkCellArea void gtk_cell_area_set_focus_cell (GtkCellArea *area, GtkCellRenderer *renderer); GtkCellRenderer *gtk_cell_area_get_focus_cell (GtkCellArea *area); +void gtk_cell_area_set_edited_cell (GtkCellArea *area, + GtkCellRenderer *renderer); +GtkCellRenderer *gtk_cell_area_get_edited_cell (GtkCellArea *area); @@ -282,6 +299,11 @@ void gtk_cell_area_set_cell_margin_bottom (GtkCellArea /* Functions for area implementations */ +/* Signal that editing started on the area (fires the "editing-started" signal) */ +void gtk_cell_area_editing_started (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *editable); + /* Distinguish the inner cell area from the whole requested area including margins */ void gtk_cell_area_inner_cell_area (GtkCellArea *area, GdkRectangle *cell_area, diff --git a/gtk/gtkcellareabox.c b/gtk/gtkcellareabox.c index ad2f6ee2ef..a2f73bd4fe 100644 --- a/gtk/gtkcellareabox.c +++ b/gtk/gtkcellareabox.c @@ -50,6 +50,12 @@ static void gtk_cell_area_box_remove (GtkCellArea static void gtk_cell_area_box_forall (GtkCellArea *area, GtkCellCallback callback, gpointer callback_data); +static void gtk_cell_area_box_get_cell_allocation (GtkCellArea *area, + GtkCellAreaIter *iter, + GtkWidget *widget, + GtkCellRenderer *renderer, + const GdkRectangle *cell_area, + GdkRectangle *allocation); static gint gtk_cell_area_box_event (GtkCellArea *area, GtkCellAreaIter *iter, GtkWidget *widget, @@ -225,13 +231,14 @@ gtk_cell_area_box_class_init (GtkCellAreaBoxClass *class) object_class->get_property = gtk_cell_area_box_get_property; /* GtkCellAreaClass */ - area_class->add = gtk_cell_area_box_add; - area_class->remove = gtk_cell_area_box_remove; - area_class->forall = gtk_cell_area_box_forall; - area_class->event = gtk_cell_area_box_event; - area_class->render = gtk_cell_area_box_render; - area_class->set_cell_property = gtk_cell_area_box_set_cell_property; - area_class->get_cell_property = gtk_cell_area_box_get_cell_property; + area_class->add = gtk_cell_area_box_add; + area_class->remove = gtk_cell_area_box_remove; + area_class->forall = gtk_cell_area_box_forall; + area_class->get_cell_allocation = gtk_cell_area_box_get_cell_allocation; + area_class->event = gtk_cell_area_box_event; + area_class->render = gtk_cell_area_box_render; + area_class->set_cell_property = gtk_cell_area_box_set_cell_property; + area_class->get_cell_property = gtk_cell_area_box_get_cell_property; area_class->create_iter = gtk_cell_area_box_create_iter; area_class->get_request_mode = gtk_cell_area_box_get_request_mode; @@ -811,6 +818,50 @@ gtk_cell_area_box_forall (GtkCellArea *area, } } +static void +gtk_cell_area_box_get_cell_allocation (GtkCellArea *area, + GtkCellAreaIter *iter, + GtkWidget *widget, + GtkCellRenderer *renderer, + const GdkRectangle *cell_area, + GdkRectangle *allocation) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxPrivate *priv = box->priv; + GtkCellAreaBoxIter *box_iter = GTK_CELL_AREA_BOX_ITER (iter); + GSList *allocated_cells, *l; + + *allocation = *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 (cell->renderer == renderer) + { + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + allocation->x = cell_area->x + cell->position; + allocation->width = cell->size; + } + else + { + allocation->y = cell_area->y + cell->position; + allocation->height = cell->size; + } + + break; + } + } + + g_slist_foreach (allocated_cells, (GFunc)allocated_cell_free, NULL); + g_slist_free (allocated_cells); +} + static gint gtk_cell_area_box_event (GtkCellArea *area, GtkCellAreaIter *iter, @@ -819,6 +870,27 @@ gtk_cell_area_box_event (GtkCellArea *area, const GdkRectangle *cell_area, GtkCellRendererState flags) { + gint retval; + + /* First let the parent class handle activation of cells via keystrokes */ + retval = + GTK_CELL_AREA_CLASS (gtk_cell_area_box_parent_class)->event (area, iter, widget, + event, cell_area, flags); + + if (retval) + return retval; + + /* Now detect keystrokes that move focus directionally inside the area + * or signal that focus should leave the area in a given direction. + * + * To navigate focus we only need to loop through the groups and + * observe the orientation and push focus along to the next cell + * or signal that focus should leave the area. + */ + + /* Also detect mouse events, for mouse events we need to allocate the renderers + * and find which renderer needs to be activated. + */ return 0; diff --git a/gtk/gtkmarshalers.list b/gtk/gtkmarshalers.list index 88e870f052..9332f3a172 100644 --- a/gtk/gtkmarshalers.list +++ b/gtk/gtkmarshalers.list @@ -90,6 +90,7 @@ VOID:OBJECT,STRING,STRING VOID:OBJECT,UINT VOID:OBJECT,UINT,FLAGS VOID:OBJECT,STRING +VOID:OBJECT,OBJECT,STRING VOID:OBJECT,OBJECT,OBJECT VOID:POINTER VOID:POINTER,INT