diff options
43 files changed, 13662 insertions, 3208 deletions
diff --git a/docs/reference/gtk/gtk-docs.sgml b/docs/reference/gtk/gtk-docs.sgml index 3671b672e9..39b9624c0a 100644 --- a/docs/reference/gtk/gtk-docs.sgml +++ b/docs/reference/gtk/gtk-docs.sgml @@ -151,6 +151,9 @@ <xi:include href="xml/gtktreemodelsort.xml" /> <xi:include href="xml/gtktreemodelfilter.xml" /> <xi:include href="xml/gtkcelllayout.xml" /> + <xi:include href="xml/gtkcellarea.xml" /> + <xi:include href="xml/gtkcellareabox.xml" /> + <xi:include href="xml/gtkcellareacontext.xml" /> <xi:include href="xml/gtkcellrenderer.xml" /> <xi:include href="xml/gtkcelleditable.xml" /> <xi:include href="xml/gtkcellrendereraccel.xml" /> diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt index 88a6d4d90d..6fefb960ad 100644 --- a/docs/reference/gtk/gtk3-sections.txt +++ b/docs/reference/gtk/gtk3-sections.txt @@ -1119,6 +1119,7 @@ gtk_entry_buffer_get_type GtkEntryCompletion GtkEntryCompletionMatchFunc gtk_entry_completion_new +gtk_entry_completion_new_with_area gtk_entry_completion_get_entry gtk_entry_completion_set_model gtk_entry_completion_get_model @@ -4145,6 +4146,7 @@ GtkTreeViewColumnSizing GtkTreeCellDataFunc GtkTreeViewColumn gtk_tree_view_column_new +gtk_tree_view_column_new_with_area gtk_tree_view_column_new_with_attributes gtk_tree_view_column_pack_start gtk_tree_view_column_pack_end @@ -4364,6 +4366,7 @@ GtkCellLayoutIface GtkCellLayoutDataFunc gtk_cell_layout_pack_start gtk_cell_layout_pack_end +gtk_cell_layout_get_area gtk_cell_layout_get_cells gtk_cell_layout_reorder gtk_cell_layout_clear @@ -4381,12 +4384,127 @@ gtk_cell_layout_get_type </SECTION> <SECTION> +<FILE>gtkcellarea</FILE> +<TITLE>GtkCellArea</TITLE> +GtkCellArea +GtkCellAreaClass +GtkCellCallback +GtkCellAllocCallback +GTK_CELL_AREA_WARN_INVALID_CELL_PROPERTY_ID +gtk_cell_area_add +gtk_cell_area_remove +gtk_cell_area_has_renderer +gtk_cell_area_foreach +gtk_cell_area_foreach_alloc +gtk_cell_area_event +gtk_cell_area_render +gtk_cell_area_set_style_detail +gtk_cell_area_get_style_detail +gtk_cell_area_get_cell_allocation +gtk_cell_area_get_cell_at_position +gtk_cell_area_create_context +gtk_cell_area_copy_context +gtk_cell_area_get_request_mode +gtk_cell_area_get_preferred_width +gtk_cell_area_get_preferred_height_for_width +gtk_cell_area_get_preferred_height +gtk_cell_area_get_preferred_width_for_height +gtk_cell_area_get_current_path_string +gtk_cell_area_apply_attributes +gtk_cell_area_attribute_connect +gtk_cell_area_attribute_disconnect +gtk_cell_area_class_install_cell_property +gtk_cell_area_class_find_cell_property +gtk_cell_area_class_list_cell_properties +gtk_cell_area_add_with_properties +gtk_cell_area_cell_set +gtk_cell_area_cell_get +gtk_cell_area_cell_set_valist +gtk_cell_area_cell_get_valist +gtk_cell_area_cell_set_property +gtk_cell_area_cell_get_property +gtk_cell_area_is_activatable +gtk_cell_area_activate +gtk_cell_area_focus +gtk_cell_area_set_focus_cell +gtk_cell_area_get_focus_cell +gtk_cell_area_add_focus_sibling +gtk_cell_area_remove_focus_sibling +gtk_cell_area_is_focus_sibling +gtk_cell_area_get_focus_siblings +gtk_cell_area_get_focus_from_sibling +gtk_cell_area_get_edited_cell +gtk_cell_area_get_edit_widget +gtk_cell_area_activate_cell +gtk_cell_area_stop_editing +gtk_cell_area_inner_cell_area +gtk_cell_area_request_renderer +<SUBSECTION Standard> +GTK_CELL_AREA +GTK_IS_CELL_AREA +GTK_TYPE_CELL_AREA +gtk_cell_area_get_type +GTK_CELL_AREA_CLASS +GTK_IS_CELL_AREA_CLASS +GTK_CELL_AREA_GET_CLASS +GtkCellAreaPrivate +</SECTION> + +<SECTION> +<FILE>gtkcellareacontext</FILE> +<TITLE>GtkCellAreaContext</TITLE> +GtkCellAreaContextClass +GtkCellAreaContext +gtk_cell_area_context_get_area +gtk_cell_area_context_allocate +gtk_cell_area_context_reset +gtk_cell_area_context_get_preferred_width +gtk_cell_area_context_get_preferred_height +gtk_cell_area_context_get_preferred_height_for_width +gtk_cell_area_context_get_preferred_width_for_height +gtk_cell_area_context_get_allocation +gtk_cell_area_context_push_preferred_width +gtk_cell_area_context_push_preferred_height +<SUBSECTION Standard> +GTK_CELL_AREA_CONTEXT +GTK_IS_CELL_AREA_CONTEXT +GTK_TYPE_CELL_AREA_CONTEXT +gtk_cell_area_context_get_type +GTK_CELL_AREA_CONTEXT_CLASS +GTK_IS_CELL_AREA_CONTEXT_CLASS +GTK_CELL_AREA_CONTEXT_GET_CLASS +GtkCellAreaContextPrivate +</SECTION> + +<SECTION> +<FILE>gtkcellareabox</FILE> +<TITLE>GtkCellAreaBox</TITLE> +GtkCellAreaBox +GtkCellAreaBoxClass +gtk_cell_area_box_new +gtk_cell_area_box_pack_start +gtk_cell_area_box_pack_end +gtk_cell_area_box_get_spacing +gtk_cell_area_box_set_spacing +<SUBSECTION Standard> +GTK_CELL_AREA_BOX +GTK_IS_CELL_AREA_BOX +GTK_TYPE_CELL_AREA_BOX +gtk_cell_area_box_get_type +GTK_CELL_AREA_BOX_CLASS +GTK_IS_CELL_AREA_BOX_CLASS +GTK_CELL_AREA_BOX_GET_CLASS +GtkCellAreaBoxPrivate +</SECTION> + +<SECTION> <FILE>gtkcellrenderer</FILE> <TITLE>GtkCellRenderer</TITLE> GtkCellRendererState GtkCellRendererMode GtkCellRenderer GtkCellRendererClass +gtk_cell_renderer_get_aligned_area gtk_cell_renderer_get_size gtk_cell_renderer_render gtk_cell_renderer_activate @@ -4402,6 +4520,7 @@ gtk_cell_renderer_get_alignment gtk_cell_renderer_set_alignment gtk_cell_renderer_get_padding gtk_cell_renderer_set_padding +gtk_cell_renderer_is_activatable <SUBSECTION Width-for-height> gtk_cell_renderer_get_preferred_height diff --git a/docs/reference/gtk/gtk3.types b/docs/reference/gtk/gtk3.types index c385a37c48..fe0ff45f6b 100644 --- a/docs/reference/gtk/gtk3.types +++ b/docs/reference/gtk/gtk3.types @@ -26,6 +26,9 @@ gtk_buildable_get_type gtk_button_box_get_type gtk_button_get_type gtk_calendar_get_type +gtk_cell_area_get_type +gtk_cell_area_box_get_type +gtk_cell_area_context_get_type gtk_cell_editable_get_type gtk_cell_layout_get_type gtk_cell_renderer_accel_get_type diff --git a/docs/reference/gtk/tmpl/gtkcelllayout.sgml b/docs/reference/gtk/tmpl/gtkcelllayout.sgml deleted file mode 100644 index 166d2cb996..0000000000 --- a/docs/reference/gtk/tmpl/gtkcelllayout.sgml +++ /dev/null @@ -1,186 +0,0 @@ -<!-- ##### SECTION Title ##### --> -GtkCellLayout - -<!-- ##### SECTION Short_Description ##### --> -An interface for packing cells - -<!-- ##### SECTION Long_Description ##### --> -<para> -#GtkCellLayout is an interface to be implemented by all objects which -want to provide a #GtkTreeViewColumn-like API for packing cells, setting -attributes and data funcs. -</para> - -<para> -One of the notable features provided by implementations of GtkCellLayout -are <emphasis>attributes</emphasis>. Attributes let you set the properties -in flexible ways. They can just be set to constant values like regular -properties. But they can also be mapped to a column of the underlying -tree model with gtk_cell_layout_set_attributes(), which means that the value -of the attribute can change from cell to cell as they are rendered by the -cell renderer. Finally, it is possible to specify a function with -gtk_cell_layout_set_cell_data_func() that is called to determine the value -of the attribute for each cell that is rendered. -</para> - -<refsect2 id="GtkCellLayout-BUILDER-UI"> -<title>GtkCellLayouts as GtkBuildable</title> -<para> -Implementations of GtkCellLayout which also implement the GtkBuildable -interface (#GtkCellView, #GtkIconView, #GtkComboBox, #GtkComboBoxEntry, -#GtkEntryCompletion, #GtkTreeViewColumn) accept GtkCellRenderer objects -as <child> elements in UI definitions. They support a custom -<attributes> element for their children, which can contain -multiple <attribute> elements. Each <attribute> element has -a name attribute which specifies a property of the cell renderer; the -content of the element is the attribute value. -</para> -<example> -<title>A UI definition fragment specifying attributes</title> -<programlisting><![CDATA[ -<object class="GtkCellView"> - <child> - <object class="GtkCellRendererText"/> - <attributes> - <attribute name="text">0</attribute> - </attributes> - </child>" -</object> -]]></programlisting> -</example> -</refsect2> - -<!-- ##### SECTION See_Also ##### --> -<para> - -</para> - -<!-- ##### SECTION Stability_Level ##### --> - - -<!-- ##### SECTION Image ##### --> - - -<!-- ##### STRUCT GtkCellLayout ##### --> -<para> - -</para> - - -<!-- ##### STRUCT GtkCellLayoutIface ##### --> -<para> - -</para> - -@g_iface: -@pack_start: -@pack_end: -@clear: -@add_attribute: -@set_cell_data_func: -@clear_attributes: -@reorder: -@get_cells: - -<!-- ##### USER_FUNCTION GtkCellLayoutDataFunc ##### --> -<para> -A function which should set the value of @cell_layout's cell renderer(s) -as appropriate. -</para> - -@cell_layout: a #GtkCellLayout -@cell: the cell renderer whose value is to be set -@tree_model: the model -@iter: a #GtkTreeIter indicating the row to set the value for -@data: user data passed to gtk_cell_layout_set_cell_data_func() - - -<!-- ##### FUNCTION gtk_cell_layout_pack_start ##### --> -<para> - -</para> - -@cell_layout: -@cell: -@expand: - - -<!-- ##### FUNCTION gtk_cell_layout_pack_end ##### --> -<para> - -</para> - -@cell_layout: -@cell: -@expand: - - -<!-- ##### FUNCTION gtk_cell_layout_get_cells ##### --> -<para> - -</para> - -@cell_layout: -@Returns: - - -<!-- ##### FUNCTION gtk_cell_layout_reorder ##### --> -<para> - -</para> - -@cell_layout: -@cell: -@position: - - -<!-- ##### FUNCTION gtk_cell_layout_clear ##### --> -<para> - -</para> - -@cell_layout: - - -<!-- ##### FUNCTION gtk_cell_layout_set_attributes ##### --> -<para> - -</para> - -@cell_layout: -@cell: -@Varargs: - - -<!-- ##### FUNCTION gtk_cell_layout_add_attribute ##### --> -<para> - -</para> - -@cell_layout: -@cell: -@attribute: -@column: - - -<!-- ##### FUNCTION gtk_cell_layout_set_cell_data_func ##### --> -<para> - -</para> - -@cell_layout: -@cell: -@func: -@func_data: -@destroy: - - -<!-- ##### FUNCTION gtk_cell_layout_clear_attributes ##### --> -<para> - -</para> - -@cell_layout: -@cell: - - diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 9fa5059c27..cbf8b3d89e 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -173,6 +173,10 @@ gtk_public_h_sources = \ gtkbuildable.h \ gtkbutton.h \ gtkcalendar.h \ + gtkcellarea.h \ + gtkcellareabox.h \ + gtkcellareaboxcontext.h \ + gtkcellareacontext.h \ gtkcelleditable.h \ gtkcelllayout.h \ gtkcellrenderer.h \ @@ -470,6 +474,10 @@ gtk_base_c_sources = \ gtkbuilderparser.c \ gtkbutton.c \ gtkcalendar.c \ + gtkcellarea.c \ + gtkcellareabox.c \ + gtkcellareaboxcontext.c \ + gtkcellareacontext.c \ gtkcelleditable.c \ gtkcelllayout.c \ gtkcellrenderer.c \ @@ -57,6 +57,9 @@ #include <gtk/gtkbuilder.h> #include <gtk/gtkbutton.h> #include <gtk/gtkcalendar.h> +#include <gtk/gtkcellarea.h> +#include <gtk/gtkcellareabox.h> +#include <gtk/gtkcellareacontext.h> #include <gtk/gtkcelleditable.h> #include <gtk/gtkcelllayout.h> #include <gtk/gtkcellrenderer.h> diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols index bb206fa36d..d121000676 100644 --- a/gtk/gtk.symbols +++ b/gtk/gtk.symbols @@ -359,6 +359,70 @@ gtk_calendar_set_detail_height_rows gtk_calendar_set_detail_width_chars gtk_calendar_set_display_options gtk_calendar_unmark_day +gtk_cell_area_activate +gtk_cell_area_activate_cell +gtk_cell_area_add +gtk_cell_area_add_focus_sibling +gtk_cell_area_add_with_properties +gtk_cell_area_apply_attributes +gtk_cell_area_attribute_connect +gtk_cell_area_attribute_disconnect +gtk_cell_area_box_get_spacing +gtk_cell_area_box_get_type G_GNUC_CONST +gtk_cell_area_box_new +gtk_cell_area_box_pack_end +gtk_cell_area_box_pack_start +gtk_cell_area_box_set_spacing +gtk_cell_area_cell_get +gtk_cell_area_cell_get_property +gtk_cell_area_cell_get_valist +gtk_cell_area_cell_set +gtk_cell_area_cell_set_property +gtk_cell_area_cell_set_valist +gtk_cell_area_class_find_cell_property +gtk_cell_area_class_install_cell_property +gtk_cell_area_class_list_cell_properties +gtk_cell_area_context_allocate +gtk_cell_area_context_get_allocation +gtk_cell_area_context_get_area +gtk_cell_area_context_get_preferred_height +gtk_cell_area_context_get_preferred_height_for_width +gtk_cell_area_context_get_preferred_width +gtk_cell_area_context_get_preferred_width_for_height +gtk_cell_area_context_get_type G_GNUC_CONST +gtk_cell_area_context_push_preferred_width +gtk_cell_area_context_push_preferred_height +gtk_cell_area_context_reset +gtk_cell_area_copy_context +gtk_cell_area_create_context +gtk_cell_area_event +gtk_cell_area_foreach +gtk_cell_area_focus +gtk_cell_area_get_edited_cell +gtk_cell_area_get_edit_widget +gtk_cell_area_get_focus_cell +gtk_cell_area_get_focus_from_sibling +gtk_cell_area_get_focus_siblings +gtk_cell_area_get_cell_allocation +gtk_cell_area_get_current_path_string +gtk_cell_area_get_preferred_height +gtk_cell_area_get_preferred_height_for_width +gtk_cell_area_get_preferred_width +gtk_cell_area_get_preferred_width_for_height +gtk_cell_area_get_request_mode +gtk_cell_area_get_style_detail +gtk_cell_area_get_type G_GNUC_CONST +gtk_cell_area_has_renderer +gtk_cell_area_inner_cell_area +gtk_cell_area_is_activatable +gtk_cell_area_is_focus_sibling +gtk_cell_area_remove +gtk_cell_area_remove_focus_sibling +gtk_cell_area_render +gtk_cell_area_request_renderer +gtk_cell_area_set_focus_cell +gtk_cell_area_set_style_detail +gtk_cell_area_stop_editing gtk_cell_editable_editing_done gtk_cell_editable_get_type G_GNUC_CONST gtk_cell_editable_remove_widget @@ -366,6 +430,7 @@ gtk_cell_editable_start_editing gtk_cell_layout_add_attribute gtk_cell_layout_clear gtk_cell_layout_clear_attributes +gtk_cell_layout_get_area gtk_cell_layout_get_cells gtk_cell_layout_get_type G_GNUC_CONST gtk_cell_layout_pack_end @@ -379,6 +444,7 @@ gtk_cell_renderer_accel_new gtk_cell_renderer_activate gtk_cell_renderer_combo_get_type G_GNUC_CONST gtk_cell_renderer_combo_new +gtk_cell_renderer_get_aligned_area gtk_cell_renderer_get_alignment gtk_cell_renderer_get_fixed_size gtk_cell_renderer_get_padding @@ -392,6 +458,7 @@ gtk_cell_renderer_get_sensitive gtk_cell_renderer_get_size gtk_cell_renderer_get_type G_GNUC_CONST gtk_cell_renderer_get_visible +gtk_cell_renderer_is_activatable gtk_cell_renderer_mode_get_type G_GNUC_CONST gtk_cell_renderer_pixbuf_get_type G_GNUC_CONST gtk_cell_renderer_pixbuf_new @@ -731,6 +798,7 @@ gtk_entry_completion_insert_action_markup gtk_entry_completion_insert_action_text gtk_entry_completion_insert_prefix gtk_entry_completion_new +gtk_entry_completion_new_with_area gtk_entry_completion_set_inline_completion gtk_entry_completion_set_inline_selection gtk_entry_completion_set_match_func @@ -3131,6 +3199,7 @@ gtk_tree_view_column_get_visible gtk_tree_view_column_get_widget gtk_tree_view_column_get_width gtk_tree_view_column_new +gtk_tree_view_column_new_with_area gtk_tree_view_column_new_with_attributes G_GNUC_NULL_TERMINATED gtk_tree_view_column_pack_end gtk_tree_view_column_pack_start diff --git a/gtk/gtkcellarea.c b/gtk/gtkcellarea.c new file mode 100644 index 0000000000..fcf550ef82 --- /dev/null +++ b/gtk/gtkcellarea.c @@ -0,0 +1,3618 @@ +/* gtkcellarea.c + * + * Copyright (C) 2010 Openismus GmbH + * + * Authors: + * Tristan Van Berkom <tristanvb@openismus.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:gtkcellarea + * @Short_Description: An abstract class for laying out #GtkCellRenderers + * @Title: GtkCellArea + * + * The #GtkCellArea is an abstract class for #GtkCellLayout widgets (also referred + * to as "layouting widgets") to interface with an arbitrary number of #GtkCellRenderers + * and interact with the user for a given #GtkTreeModel row. + * + * The cell area handles events, focus navigation, drawing and wraps geometrical + * size requests and allocations for a given row of data. + * + * Usually users dont have to interact with the #GtkCellArea directly unless they + * are implementing a cell layouting widget themselves. + * + * <refsect2 id="cell-area-geometry-management"> + * <title>Requesting area sizes</title> + * <para> + * As outlined in <link linkend="geometry-management">GtkWidget's + * geometry management section</link>, GTK+ uses a height-for-width + * geometry managemen system to compute the sizes of widgets and user + * interfaces. #GtkCellArea uses the same semantics to calculate the + * size of an area for an arbitrary number of #GtkTreeModel rows. + * + * When requesting the size of a cell area one needs to calculate + * the size for a handful of rows, this will be done differently by + * different layouting widgets. For instance a #GtkTreeViewColumn + * always lines up the areas from top to bottom while a #GtkIconView + * on the other hand might enforce that all areas received the same + * width and wrap the areas around, requesting height for more cell + * areas when allocated less width. + * + * It's also important for areas to maintain some cell + * alignments with areas rendered for adjacent rows (cells can + * appear "columnized" inside an area even when the size of + * cells are different in each row). For this reason the #GtkCellArea + * uses a #GtkCellAreaContext object to store the alignments + * and sizes along the way (as well as the overall largest minimum + * and natural size for all the rows which have been calculated + * with the said context). + * + * The #GtkCellAreaContext is an opaque object specific to the + * #GtkCellArea which created it (see gtk_cell_area_create_context()). + * The owning cell layouting widget can create as many contexts as + * it wishes to calculate sizes of rows which should receive the + * same size in at least one orientation (horizontally or vertically), + * however it's important that the same #GtkCellAreaContext which + * was used to request the sizes for a given #GtkTreeModel row be + * used when rendering or processing events for that row. + * + * In order to request the width of all the rows at the root level + * of a #GtkTreeModel one would do the following: + * <example> + * <title>Requesting the width of a hand full of GtkTreeModel rows.</title> + * <programlisting> + * GtkTreeIter iter; + * gint minimum_width; + * gint natural_width; + * + * valid = gtk_tree_model_get_iter_first (model, &iter); + * while (valid) + * { + * gtk_cell_area_apply_attributes (area, model, &iter, FALSE, FALSE); + * gtk_cell_area_get_preferred_width (area, context, widget, NULL, NULL); + * + * valid = gtk_tree_model_iter_next (model, &iter); + * } + * gtk_cell_area_context_get_preferred_width (context, &minimum_width, &natural_width); + * </programlisting> + * </example> + * Note that in this example it's not important to observe the returned minimum and + * natural width of the area for each row unless the cell layouting object is actually + * interested in the widths of individual rows. The overall width is however stored + * in the accompanying #GtkCellAreaContext object and can be consulted at any time. + * + * This can be useful since #GtkCellLayout widgets usually have to support requesting + * and rendering rows in treemodels with an exceedingly large amount of rows. The + * #GtkCellLayout widget in that case would calculate the required width of the rows + * in an idle or timeout source (see g_timeout_add()) and when the widget is requested + * its actual width in #GtkWidgetClass.get_preferred_width() it can simply consult the + * width accumulated so far in the #GtkCellAreaContext object. + * + * A simple example where rows are rendered from top to bottom and take up the full + * width of the layouting widget would look like: + * <example> + * <title>A typical #GtkWidgetClass.get_preferred_width() for a layouting widget.</title> + * <programlisting> + * static void + * foo_get_preferred_width (GtkWidget *widget, + * gint *minimum_size, + * gint *natural_size) + * { + * Foo *foo = FOO (widget); + * FooPrivate *priv = foo->priv; + * + * foo_ensure_at_least_one_handfull_of_rows_have_been_requested (foo); + * + * gtk_cell_area_context_get_preferred_width (priv->context, minimum_size, natural_size); + * } + * </programlisting> + * </example> + * + * In the above example the Foo widget has to make sure that some row sizes have + * been calculated (the amount of rows that Foo judged was appropriate to request + * space for in a single timeout iteration) before simply returning the amount + * of space required by the area via the #GtkCellAreaContext. + * + * Requesting the height for width (or width for height) of an area is a similar + * task except in this case the #GtkCellAreaContext does not store the data (actually + * it does not know how much space the layouting widget plans to allocate it for + * every row, it's up to the layouting widget to render each row of data with + * the appropriate height and width which was requested by the #GtkCellArea). + * + * In order to request the height for width of all the rows at the root level + * of a #GtkTreeModel one would do the following: + * <example> + * <title>Requesting the height for width of a hand full of GtkTreeModel rows.</title> + * <programlisting> + * GtkTreeIter iter; + * gint minimum_height; + * gint natural_height; + * gint full_minimum_height = 0; + * gint full_natural_height = 0; + * + * valid = gtk_tree_model_get_iter_first (model, &iter); + * while (valid) + * { + * gtk_cell_area_apply_attributes (area, model, &iter, FALSE, FALSE); + * gtk_cell_area_get_preferred_height_for_width (area, context, widget, + * width, &minimum_height, &natural_height); + * + * if (width_is_for_allocation) + * cache_row_height (&iter, minimum_height, natural_height); + * + * full_minimum_height += minimum_height; + * full_natural_height += natural_height; + * + * valid = gtk_tree_model_iter_next (model, &iter); + * } + * </programlisting> + * </example> + * + * Note that in the above example we would need to cache the heights returned for each + * treemodel row so that we would know what sizes to render the areas for each row. However + * we would only want to really cache the heights if the request is intended for the + * layouting widgets real allocation. + * + * In some cases the layouting widget is requested the height for an arbitrary for_width, + * this is a special case for layouting widgets who need to request size for tens of thousands + * of treemodel rows. For this case it's only important that the layouting widget calculate + * one reasonably sized chunk of rows and return that height synchronously. The reasoning here + * is that any layouting widget is at least capable of synchronously calculating enough + * height to fill the screen height (or scrolled window height) in response to a single call to + * #GtkWidgetClass.get_preferred_height_for_width(). Returning a perfect height for width that + * is larger than the screen area is inconsequential since after the layouting receives an + * allocation from a scrolled window it simply continues to drive the the scrollbar + * values while more and mode height is required for the row heights that are calculated + * in the background. + * </para> + * </refsect2> + * <refsect2 id="cell-area-rendering"> + * <title>Rendering Areas</title> + * <para> + * Once area sizes have been aquired at least for the rows in the visible area of the + * layouting widget they can be rendered at #GtkWidgetClass.draw() time. + * + * A crued example of how to render all the rows at the root level runs as follows: + * <example> + * <title>Requesting the width of a hand full of GtkTreeModel rows.</title> + * <programlisting> + * GtkAllocation allocation; + * GdkRectangle cell_area = { 0, }; + * GtkTreeIter iter; + * gint minimum_width; + * gint natural_width; + * + * gtk_widget_get_allocation (widget, &allocation); + * cell_area.width = allocation.width; + * + * valid = gtk_tree_model_get_iter_first (model, &iter); + * while (valid) + * { + * cell_area.height = get_cached_height_for_row (&iter); + * + * gtk_cell_area_apply_attributes (area, model, &iter, FALSE, FALSE); + * gtk_cell_area_render (area, context, widget, cr, + * &cell_area, &cell_area, state_flags, FALSE); + * + * cell_area.y += cell_area.height; + * + * valid = gtk_tree_model_iter_next (model, &iter); + * } + * </programlisting> + * </example> + * Note that the cached height in this example really depends on how the layouting + * widget works. The layouting widget might decide to give every row it's minimum + * or natural height or if the model content is expected to fit inside the layouting + * widget with no scrolled window it would make sense to calculate the allocation + * for each row at #GtkWidget.size_allocate() time using gtk_distribute_natural_allocation(). + * </para> + * </refsect2> + * <refsect2 id="cell-area-events-and-focus"> + * <title>Handling Events and Driving Keyboard Focus</title> + * <para> + * Passing events to the area is as simple as handling events on any normal + * widget and then passing them to the gtk_cell_area_event() api as they come + * in. Usually #GtkCellArea is only interested in button events, however some + * customized derived areas can be implemented who are interested in handling + * other events. Handling an event can trigger the #GtkCellArea::focus-changed + * signal to fire as well as #GtkCellArea::add-editable in the case that + * an editable cell was clicked and needs to start editing. You can call + * gtk_cell_area_stop_editing() at any time to cancel any cell editing + * that is currently in progress. + * + * The #GtkCellArea drives keyboard focus from cell to cell in a way similar + * to #GtkWidget. For layouting widgets that support giving focus to cells it's + * important to remember to pass %GTK_CELL_RENDERER_FOCUSED to the area functions + * for the row that has focus and to tell the area to paint the focus at render + * time. + * + * Layouting widgets that accept focus on cells should implement the #GtkWidgetClass.focus() + * virtual method. The layouting widget is always responsible for knowing where + * #GtkTreeModel rows are rendered inside the widget, so at #GtkWidgetClass.focus() time + * the layouting widget should use the #GtkCellArea methods to navigate focus inside the + * area and then observe the GtkDirectionType to pass the focus to adjacent rows and + * areas. + * + * A basic example of how the #GtkWidgetClass.focus() virtual method should be implemented: + * <example> + * <title>Implementing keyboard focus navigation when displaying rows from top to bottom.</title> + * <programlisting> + * static gboolean + * foo_focus (GtkWidget *widget, + * GtkDirectionType direction) + * { + * Foo *foo = FOO (widget); + * FooPrivate *priv = foo->priv; + * gint focus_row; + * gboolean have_focus = FALSE; + * + * focus_row = priv->focus_row; + * + * if (!gtk_widget_has_focus (widget)) + * gtk_widget_grab_focus (widget); + * + * valid = gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, priv->focus_row); + * while (valid) + * { + * gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE); + * + * if (gtk_cell_area_focus (priv->area, direction)) + * { + * priv->focus_row = focus_row; + * have_focus = TRUE; + * break; + * } + * else + * { + * if (direction == GTK_DIR_RIGHT || + * direction == GTK_DIR_LEFT) + * break; + * else if (direction == GTK_DIR_UP || + * direction == GTK_DIR_TAB_BACKWARD) + * { + * if (focus_row == 0) + * break; + * else + * { + * focus_row--; + * valid = gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, focus_row); + * } + * } + * else + * { + * if (focus_row == last_row) + * break; + * else + * { + * focus_row++; + * valid = gtk_tree_model_iter_next (priv->model, &iter); + * } + * } + * } + * } + * return have_focus; + * } + * </programlisting> + * </example> + * </para> + * </refsect2> + * <refsect2 id="cell-properties"> + * <title>Cell Properties</title> + * <para> + * The #GtkCellArea introduces <emphasis>cell properties</emphasis> for #GtkCellRenderers in very + * much the same way that #GtkContainer introduces <link linkend="child-properties">child properties</link> + * for #GtkWidgets. This provides some general interfaces for defining the relationship cell areas + * have with their cells. For instance in a #GtkCellAreaBox a cell might "expand" and recieve extra + * space when the area is allocated more than it's full natural request, or a cell might be configured + * to "align" with adjacent rows which were requested and rendered with the same #GtkCellAreaContext. + * + * Use gtk_cell_area_class_install_cell_property() to install cell properties + * for a cell area class and gtk_cell_area_class_find_cell_property() or + * gtk_cell_area_class_list_cell_properties() to get information about existing + * cell properties. + * + * To set the value of a cell property, use gtk_cell_area_cell_set_property(), + * gtk_cell_area_cell_set() or gtk_cell_area_cell_set_valist(). + * To obtain the value of a cell property, use + * gtk_cell_area_cell_get_property(), gtk_cell_area_cell_get() or + * gtk_cell_area_cell_get_valist(). + * </para> + * </refsect2> + * + */ + +#include "config.h" + +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> + +#include "gtkintl.h" +#include "gtkcelllayout.h" +#include "gtkcellarea.h" +#include "gtkcellareacontext.h" +#include "gtkmarshalers.h" +#include "gtkprivate.h" + +#include <gobject/gvaluecollector.h> + + +/* GObjectClass */ +static void gtk_cell_area_dispose (GObject *object); +static void gtk_cell_area_finalize (GObject *object); +static void gtk_cell_area_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_cell_area_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +/* GtkCellAreaClass */ +static gint gtk_cell_area_real_event (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + GdkEvent *event, + const GdkRectangle *cell_area, + GtkCellRendererState flags); +static void gtk_cell_area_real_render (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + cairo_t *cr, + const GdkRectangle *background_area, + const GdkRectangle *cell_area, + GtkCellRendererState flags, + gboolean paint_focus); +static void gtk_cell_area_real_apply_attributes (GtkCellArea *area, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gboolean is_expander, + gboolean is_expanded); +static void gtk_cell_area_real_get_preferred_height_for_width (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height); +static void gtk_cell_area_real_get_preferred_width_for_height (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width); +static gboolean gtk_cell_area_real_is_activatable (GtkCellArea *area); +static gboolean gtk_cell_area_real_activate (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + GtkCellRendererState flags, + gboolean edit_only); + +/* GtkCellLayoutIface */ +static void gtk_cell_area_cell_layout_init (GtkCellLayoutIface *iface); +static void gtk_cell_area_pack_default (GtkCellLayout *cell_layout, + GtkCellRenderer *renderer, + gboolean expand); +static void gtk_cell_area_clear (GtkCellLayout *cell_layout); +static void gtk_cell_area_add_attribute (GtkCellLayout *cell_layout, + GtkCellRenderer *renderer, + const gchar *attribute, + gint column); +static void gtk_cell_area_set_cell_data_func (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + GtkCellLayoutDataFunc func, + gpointer func_data, + GDestroyNotify destroy); +static void gtk_cell_area_clear_attributes (GtkCellLayout *cell_layout, + GtkCellRenderer *renderer); +static void gtk_cell_area_reorder (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gint position); +static GList *gtk_cell_area_get_cells (GtkCellLayout *cell_layout); +static GtkCellArea *gtk_cell_area_get_area (GtkCellLayout *cell_layout); + +/* GtkBuildableIface */ +static void gtk_cell_area_buildable_init (GtkBuildableIface *iface); +static void gtk_cell_area_buildable_custom_tag_end (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + gpointer *data); + +/* Used in foreach loop to check if a child renderer is present */ +typedef struct { + GtkCellRenderer *renderer; + gboolean has_renderer; +} HasRendererCheck; + +/* Used in foreach loop to get a cell's allocation */ +typedef struct { + GtkCellRenderer *renderer; + GdkRectangle allocation; +} RendererAllocationData; + +/* Used in foreach loop to render cells */ +typedef struct { + GtkCellArea *area; + GtkWidget *widget; + cairo_t *cr; + GdkRectangle focus_rect; + GtkCellRendererState render_flags; + guint paint_focus : 1; + guint focus_all : 1; + guint first_focus : 1; +} CellRenderData; + +/* Used in foreach loop to get a cell by position */ +typedef struct { + gint x; + gint y; + GtkCellRenderer *renderer; + GdkRectangle cell_area; +} CellByPositionData; + +/* Attribute/Cell metadata */ +typedef struct { + const gchar *attribute; + gint column; +} CellAttribute; + +typedef struct { + GSList *attributes; + + GtkCellLayoutDataFunc func; + gpointer data; + GDestroyNotify destroy; +} CellInfo; + +static CellInfo *cell_info_new (GtkCellLayoutDataFunc func, + gpointer data, + GDestroyNotify destroy); +static void cell_info_free (CellInfo *info); +static CellAttribute *cell_attribute_new (GtkCellRenderer *renderer, + const gchar *attribute, + gint column); +static void cell_attribute_free (CellAttribute *attribute); +static gint cell_attribute_find (CellAttribute *cell_attribute, + const gchar *attribute); + +/* Internal functions/signal emissions */ +static void gtk_cell_area_add_editable (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *editable, + const GdkRectangle *cell_area); +static void gtk_cell_area_remove_editable (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *editable); +static void gtk_cell_area_set_edit_widget (GtkCellArea *area, + GtkCellEditable *editable); +static void gtk_cell_area_set_edited_cell (GtkCellArea *area, + GtkCellRenderer *renderer); + + +/* Struct to pass data along while looping over + * cell renderers to apply attributes + */ +typedef struct { + GtkCellArea *area; + GtkTreeModel *model; + GtkTreeIter *iter; + gboolean is_expander; + gboolean is_expanded; +} AttributeData; + +struct _GtkCellAreaPrivate +{ + /* The GtkCellArea bookkeeps any connected + * attributes in this hash table. + */ + GHashTable *cell_info; + + /* Current path is saved as a side-effect + * of gtk_cell_area_apply_attributes() */ + gchar *current_path; + + /* Current cell being edited and editable widget used */ + GtkCellEditable *edit_widget; + GtkCellRenderer *edited_cell; + + /* Signal connections to the editable widget */ + gulong remove_widget_id; + + /* Currently focused cell */ + GtkCellRenderer *focus_cell; + + /* Tracking which cells are focus siblings of focusable cells */ + GHashTable *focus_siblings; + + /* Detail string to pass to gtk_paint_*() functions */ + gchar *style_detail; +}; + +enum { + PROP_0, + PROP_FOCUS_CELL, + PROP_EDITED_CELL, + PROP_EDIT_WIDGET +}; + +enum { + SIGNAL_APPLY_ATTRIBUTES, + SIGNAL_ADD_EDITABLE, + SIGNAL_REMOVE_EDITABLE, + SIGNAL_FOCUS_CHANGED, + LAST_SIGNAL +}; + +/* Keep the paramspec pool internal, no need to deliver notifications + * on cells. at least no percieved need for now */ +static GParamSpecPool *cell_property_pool = NULL; +static guint cell_area_signals[LAST_SIGNAL] = { 0 }; + +#define PARAM_SPEC_PARAM_ID(pspec) ((pspec)->param_id) +#define PARAM_SPEC_SET_PARAM_ID(pspec, id) ((pspec)->param_id = (id)) + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkCellArea, gtk_cell_area, G_TYPE_INITIALLY_UNOWNED, + G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT, + gtk_cell_area_cell_layout_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, + gtk_cell_area_buildable_init)) + +static void +gtk_cell_area_init (GtkCellArea *area) +{ + GtkCellAreaPrivate *priv; + + area->priv = G_TYPE_INSTANCE_GET_PRIVATE (area, + GTK_TYPE_CELL_AREA, + GtkCellAreaPrivate); + priv = area->priv; + + priv->cell_info = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify)cell_info_free); + + priv->focus_siblings = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify)g_list_free); + + priv->focus_cell = NULL; + priv->edited_cell = NULL; + priv->edit_widget = NULL; + + priv->remove_widget_id = 0; +} + +static void +gtk_cell_area_class_init (GtkCellAreaClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + /* GObjectClass */ + object_class->dispose = gtk_cell_area_dispose; + object_class->finalize = gtk_cell_area_finalize; + object_class->get_property = gtk_cell_area_get_property; + object_class->set_property = gtk_cell_area_set_property; + + /* general */ + class->add = NULL; + class->remove = NULL; + class->foreach = NULL; + class->event = gtk_cell_area_real_event; + class->render = gtk_cell_area_real_render; + class->apply_attributes = gtk_cell_area_real_apply_attributes; + + /* geometry */ + class->create_context = NULL; + class->get_request_mode = NULL; + class->get_preferred_width = NULL; + class->get_preferred_height = NULL; + class->get_preferred_height_for_width = gtk_cell_area_real_get_preferred_height_for_width; + class->get_preferred_width_for_height = gtk_cell_area_real_get_preferred_width_for_height; + + /* focus */ + class->is_activatable = gtk_cell_area_real_is_activatable; + class->activate = gtk_cell_area_real_activate; + class->focus = NULL; + + /* Signals */ + /** + * GtkCellArea::apply-attributes: + * @area: the #GtkCellArea to apply the attributes to + * @model: the #GtkTreeModel to apply the attributes from + * @iter: the #GtkTreeIter indicating which row to apply the attributes of + * @is_expander: whether the view shows children for this row + * @is_expanded: whether the view is currently showing the children of this row + * + * This signal is emitted whenever applying attributes to @area from @model + * + * Since: 3.0 + */ + cell_area_signals[SIGNAL_APPLY_ATTRIBUTES] = + g_signal_new (I_("apply-attributes"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GtkCellAreaClass, apply_attributes), + NULL, NULL, + _gtk_marshal_VOID__OBJECT_BOXED_BOOLEAN_BOOLEAN, + G_TYPE_NONE, 4, + GTK_TYPE_TREE_MODEL, + GTK_TYPE_TREE_ITER, + G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN); + + /** + * GtkCellArea::add-editable: + * @area: the #GtkCellArea where editing started + * @renderer: the #GtkCellRenderer that started the edited + * @editable: the #GtkCellEditable widget to add + * @cell_area: the #GtkWidget relative #GdkRectangle coordinates + * where @editable should be added + * @path: the #GtkTreePath string this edit was initiated for + * + * Indicates that editing has started on @renderer and that @editable + * should be added to the owning cell layouting widget at @cell_area. + * + * Since: 3.0 + */ + cell_area_signals[SIGNAL_ADD_EDITABLE] = + g_signal_new (I_("add-editable"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, /* No class closure here */ + NULL, NULL, + _gtk_marshal_VOID__OBJECT_OBJECT_BOXED_STRING, + G_TYPE_NONE, 4, + GTK_TYPE_CELL_RENDERER, + GTK_TYPE_CELL_EDITABLE, + GDK_TYPE_RECTANGLE, + G_TYPE_STRING); + + + /** + * GtkCellArea::remove-editable: + * @area: the #GtkCellArea where editing finished + * @renderer: the #GtkCellRenderer that finished editeding + * @editable: the #GtkCellEditable widget to remove + * + * Indicates that editing finished on @renderer and that @editable + * should be removed from the owning cell layouting widget. + * + * Since: 3.0 + */ + cell_area_signals[SIGNAL_REMOVE_EDITABLE] = + g_signal_new (I_("remove-editable"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, /* No class closure here */ + NULL, NULL, + _gtk_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, + GTK_TYPE_CELL_RENDERER, + GTK_TYPE_CELL_EDITABLE); + + /** + * GtkCellArea::focus-changed: + * @area: the #GtkCellArea where focus changed + * @renderer: the #GtkCellRenderer that has focus + * @path: the current #GtkTreePath string set for @area + * + * Indicates that focus changed on this @area. This signal + * is emitted either as a result of focus handling or event + * handling. + * + * It's possible that the signal is emitted even if the + * currently focused renderer did not change, this is + * because focus may change to the same renderer in the + * same cell area for a different row of data. + * + * Since: 3.0 + */ + 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 */ + /** + * GtkCellArea:focus-cell: + * + * The cell in the area that currently has focus + * + * Since: 3.0 + */ + 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)); + + /** + * GtkCellArea:edited-cell: + * + * The cell in the area that is currently edited + * + * This property is read-only and only changes as + * a result of a call gtk_cell_area_activate_cell(). + * + * Since: 3.0 + */ + 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, + G_PARAM_READABLE)); + + /** + * GtkCellArea:edit-widget: + * + * The widget currently editing the edited cell + * + * This property is read-only and only changes as + * a result of a call gtk_cell_area_activate_cell(). + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_EDIT_WIDGET, + g_param_spec_object + ("edit-widget", + P_("Edit Widget"), + P_("The widget currently editing the edited cell"), + GTK_TYPE_CELL_RENDERER, + G_PARAM_READABLE)); + + /* Pool for Cell Properties */ + if (!cell_property_pool) + cell_property_pool = g_param_spec_pool_new (FALSE); + + g_type_class_add_private (object_class, sizeof (GtkCellAreaPrivate)); +} + +/************************************************************* + * CellInfo Basics * + *************************************************************/ +static CellInfo * +cell_info_new (GtkCellLayoutDataFunc func, + gpointer data, + GDestroyNotify destroy) +{ + CellInfo *info = g_slice_new0 (CellInfo); + + info->func = func; + info->data = data; + info->destroy = destroy; + + return info; +} + +static void +cell_info_free (CellInfo *info) +{ + if (info->destroy) + info->destroy (info->data); + + g_slist_foreach (info->attributes, (GFunc)cell_attribute_free, NULL); + g_slist_free (info->attributes); + + g_slice_free (CellInfo, info); +} + +static CellAttribute * +cell_attribute_new (GtkCellRenderer *renderer, + const gchar *attribute, + gint column) +{ + GParamSpec *pspec; + + /* Check if the attribute really exists and point to + * the property string installed on the cell renderer + * class (dont dup the string) + */ + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (renderer), attribute); + + if (pspec) + { + CellAttribute *cell_attribute = g_slice_new (CellAttribute); + + cell_attribute->attribute = pspec->name; + cell_attribute->column = column; + + return cell_attribute; + } + + return NULL; +} + +static void +cell_attribute_free (CellAttribute *attribute) +{ + g_slice_free (CellAttribute, attribute); +} + +/* GCompareFunc for g_slist_find_custom() */ +static gint +cell_attribute_find (CellAttribute *cell_attribute, + const gchar *attribute) +{ + return g_strcmp0 (cell_attribute->attribute, attribute); +} + +/************************************************************* + * GObjectClass * + *************************************************************/ +static void +gtk_cell_area_finalize (GObject *object) +{ + GtkCellArea *area = GTK_CELL_AREA (object); + GtkCellAreaPrivate *priv = area->priv; + + /* All cell renderers should already be removed at this point, + * just kill our (empty) hash tables here. + */ + g_hash_table_destroy (priv->cell_info); + g_hash_table_destroy (priv->focus_siblings); + + g_free (priv->current_path); + + G_OBJECT_CLASS (gtk_cell_area_parent_class)->finalize (object); +} + + +static void +gtk_cell_area_dispose (GObject *object) +{ + /* This removes every cell renderer that may be added to the GtkCellArea, + * subclasses should be breaking references to the GtkCellRenderers + * at this point. + */ + gtk_cell_layout_clear (GTK_CELL_LAYOUT (object)); + + /* 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); + gtk_cell_area_set_edit_widget (GTK_CELL_AREA (object), NULL); + + G_OBJECT_CLASS (gtk_cell_area_parent_class)->dispose (object); +} + +static void +gtk_cell_area_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkCellArea *area = GTK_CELL_AREA (object); + + switch (prop_id) + { + 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; + } +} + +static void +gtk_cell_area_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkCellArea *area = GTK_CELL_AREA (object); + GtkCellAreaPrivate *priv = area->priv; + + switch (prop_id) + { + case PROP_FOCUS_CELL: + g_value_set_object (value, priv->focus_cell); + break; + case PROP_EDITED_CELL: + g_value_set_object (value, priv->edited_cell); + break; + case PROP_EDIT_WIDGET: + g_value_set_object (value, priv->edit_widget); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/************************************************************* + * GtkCellAreaClass * + *************************************************************/ +static gint +gtk_cell_area_real_event (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + GdkEvent *event, + const GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GtkCellAreaPrivate *priv = area->priv; + gboolean retval = FALSE; + + if (event->type == GDK_KEY_PRESS && (flags & GTK_CELL_RENDERER_FOCUSED) != 0) + { + GdkEventKey *key_event = (GdkEventKey *)event; + + /* Cancel any edits in progress */ + if (priv->edited_cell && (key_event->keyval == GDK_KEY_Escape)) + { + gtk_cell_area_stop_editing (area, TRUE); + retval = TRUE; + } + } + else if (event->type == GDK_BUTTON_PRESS) + { + GdkEventButton *button_event = (GdkEventButton *)event; + + if (button_event->button == 1) + { + GtkCellRenderer *renderer = NULL; + GtkCellRenderer *focus_renderer; + GdkRectangle alloc_area; + gint event_x, event_y; + + /* 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; + + /* Dont try to search for an event coordinate that is not in the area, that will + * trigger a runtime warning. + */ + if (event_x >= cell_area->x && event_x <= cell_area->x + cell_area->width && + event_y >= cell_area->y && event_y <= cell_area->y + cell_area->height) + renderer = + gtk_cell_area_get_cell_at_position (area, context, widget, + cell_area, event_x, event_y, + &alloc_area); + + if (renderer) + { + focus_renderer = gtk_cell_area_get_focus_from_sibling (area, renderer); + if (!focus_renderer) + focus_renderer = renderer; + + /* If we're already editing, cancel it and set focus */ + 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, focus_renderer); + retval = TRUE; + } + else + { + /* If we are activating via a focus sibling, + * we need to fetch the right cell area for the real event renderer */ + if (focus_renderer != renderer) + gtk_cell_area_get_cell_allocation (area, context, widget, focus_renderer, + cell_area, &alloc_area); + + gtk_cell_area_set_focus_cell (area, focus_renderer); + retval = gtk_cell_area_activate_cell (area, widget, focus_renderer, + event, &alloc_area, flags); + } + } + } + } + + return retval; +} + +static gboolean +render_cell (GtkCellRenderer *renderer, + const GdkRectangle *cell_area, + const GdkRectangle *cell_background, + CellRenderData *data) +{ + GtkCellRenderer *focus_cell; + GtkCellRendererState flags; + GdkRectangle inner_area; + + focus_cell = gtk_cell_area_get_focus_cell (data->area); + flags = data->render_flags; + + gtk_cell_area_inner_cell_area (data->area, data->widget, cell_area, &inner_area); + + if ((flags & GTK_CELL_RENDERER_FOCUSED) && + (data->focus_all || + (focus_cell && + (renderer == focus_cell || + gtk_cell_area_is_focus_sibling (data->area, focus_cell, renderer))))) + { + gint focus_line_width; + GdkRectangle cell_focus; + + gtk_cell_renderer_get_aligned_area (renderer, data->widget, flags, &inner_area, &cell_focus); + + gtk_widget_style_get (data->widget, + "focus-line-width", &focus_line_width, + NULL); + + /* The focus rectangle is located around the aligned area of the cell */ + cell_focus.x -= focus_line_width; + cell_focus.y -= focus_line_width; + cell_focus.width += 2 * focus_line_width; + cell_focus.height += 2 * focus_line_width; + + if (data->first_focus) + { + data->first_focus = FALSE; + data->focus_rect = cell_focus; + } + else + { + gdk_rectangle_union (&data->focus_rect, &cell_focus, &data->focus_rect); + } + } + else + flags &= ~GTK_CELL_RENDERER_FOCUSED; + + gtk_cell_renderer_render (renderer, data->cr, data->widget, + cell_background, &inner_area, flags); + + return FALSE; +} + +static void +gtk_cell_area_real_render (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + cairo_t *cr, + const GdkRectangle *background_area, + const GdkRectangle *cell_area, + GtkCellRendererState flags, + gboolean paint_focus) +{ + CellRenderData render_data = + { + area, + widget, + cr, + { 0, }, + flags, + paint_focus, + FALSE, TRUE + }; + + /* Make sure we dont paint a focus rectangle while there + * is an editable widget in play + */ + if (gtk_cell_area_get_edited_cell (area)) + render_data.paint_focus = FALSE; + + /* If no cell can activate but the caller wants focus painted, + * then we paint focus around all cells */ + if ((flags & GTK_CELL_RENDERER_FOCUSED) != 0 && paint_focus && + !gtk_cell_area_is_activatable (area)) + render_data.focus_all = TRUE; + + gtk_cell_area_foreach_alloc (area, context, widget, cell_area, background_area, + (GtkCellAllocCallback)render_cell, &render_data); + + if (render_data.paint_focus && + render_data.focus_rect.width != 0 && + render_data.focus_rect.height != 0) + { + GtkStateType renderer_state = + flags & GTK_CELL_RENDERER_SELECTED ? GTK_STATE_SELECTED : + (flags & GTK_CELL_RENDERER_PRELIT ? GTK_STATE_PRELIGHT : + (flags & GTK_CELL_RENDERER_INSENSITIVE ? GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL)); + + cairo_save (cr); + + gdk_cairo_rectangle (cr, background_area); + cairo_clip (cr); + + gtk_paint_focus (gtk_widget_get_style (widget), cr, + renderer_state, widget, + gtk_cell_area_get_style_detail (area), + render_data.focus_rect.x, render_data.focus_rect.y, + render_data.focus_rect.width, render_data.focus_rect.height); + + cairo_restore (cr); + } +} + +static void +apply_cell_attributes (GtkCellRenderer *renderer, + CellInfo *info, + AttributeData *data) +{ + CellAttribute *attribute; + GSList *list; + GValue value = { 0, }; + gboolean is_expander; + gboolean is_expanded; + + g_object_freeze_notify (G_OBJECT (renderer)); + + /* Whether a row expands or is presently expanded can only be + * provided by the view (as these states can vary across views + * accessing the same model). + */ + g_object_get (renderer, "is-expander", &is_expander, NULL); + if (is_expander != data->is_expander) + g_object_set (renderer, "is-expander", data->is_expander, NULL); + + g_object_get (renderer, "is-expanded", &is_expanded, NULL); + if (is_expanded != data->is_expanded) + g_object_set (renderer, "is-expanded", data->is_expanded, NULL); + + /* Apply the attributes directly to the renderer */ + for (list = info->attributes; list; list = list->next) + { + attribute = list->data; + + gtk_tree_model_get_value (data->model, data->iter, attribute->column, &value); + g_object_set_property (G_OBJECT (renderer), attribute->attribute, &value); + g_value_unset (&value); + } + + /* Call any GtkCellLayoutDataFunc that may have been set by the user + */ + if (info->func) + info->func (GTK_CELL_LAYOUT (data->area), renderer, + data->model, data->iter, info->data); + + g_object_thaw_notify (G_OBJECT (renderer)); +} + +static void +gtk_cell_area_real_apply_attributes (GtkCellArea *area, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gboolean is_expander, + gboolean is_expanded) +{ + + GtkCellAreaPrivate *priv; + AttributeData data; + GtkTreePath *path; + + priv = area->priv; + + /* Feed in data needed to apply to every renderer */ + data.area = area; + data.model = tree_model; + data.iter = iter; + data.is_expander = is_expander; + data.is_expanded = is_expanded; + + /* 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); +} + +static void +gtk_cell_area_real_get_preferred_height_for_width (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height) +{ + /* If the area doesnt do height-for-width, fallback on base preferred height */ + GTK_CELL_AREA_GET_CLASS (area)->get_preferred_width (area, context, widget, minimum_height, natural_height); +} + +static void +gtk_cell_area_real_get_preferred_width_for_height (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width) +{ + /* If the area doesnt do width-for-height, fallback on base preferred width */ + GTK_CELL_AREA_GET_CLASS (area)->get_preferred_width (area, context, widget, minimum_width, natural_width); +} + +static gboolean +get_is_activatable (GtkCellRenderer *renderer, + gboolean *activatable) +{ + + if (gtk_cell_renderer_is_activatable (renderer)) + *activatable = TRUE; + + return *activatable; +} + +static gboolean +gtk_cell_area_real_is_activatable (GtkCellArea *area) +{ + gboolean activatable = FALSE; + + /* Checks if any renderer can focus for the currently applied + * attributes. + * + * Subclasses can override this in the case that they are also + * rendering widgets as well as renderers. + */ + gtk_cell_area_foreach (area, (GtkCellCallback)get_is_activatable, &activatable); + + return activatable; +} + +static gboolean +gtk_cell_area_real_activate (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + GtkCellRendererState flags, + gboolean edit_only) +{ + GtkCellAreaPrivate *priv = area->priv; + GdkRectangle renderer_area; + GtkCellRenderer *activate_cell = NULL; + GtkCellRendererMode mode; + + if (priv->focus_cell) + { + g_object_get (priv->focus_cell, "mode", &mode, NULL); + + if (gtk_cell_renderer_get_visible (priv->focus_cell) && + (edit_only ? mode == GTK_CELL_RENDERER_MODE_EDITABLE : + mode != GTK_CELL_RENDERER_MODE_INERT)) + activate_cell = priv->focus_cell; + } + else + { + GList *cells, *l; + + /* GtkTreeView sometimes wants to activate a cell when no + * cells are in focus. + */ + cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area)); + for (l = cells; l && !activate_cell; l = l->next) + { + GtkCellRenderer *renderer = l->data; + + g_object_get (renderer, "mode", &mode, NULL); + + if (gtk_cell_renderer_get_visible (renderer) && + (edit_only ? mode == GTK_CELL_RENDERER_MODE_EDITABLE : + mode != GTK_CELL_RENDERER_MODE_INERT)) + activate_cell = renderer; + } + g_list_free (cells); + } + + if (activate_cell) + { + /* Get the allocation of the focused cell. + */ + gtk_cell_area_get_cell_allocation (area, context, widget, activate_cell, + cell_area, &renderer_area); + + /* Activate or Edit the cell + * + * Currently just not sending an event, renderers afaics dont use + * the event argument anyway, worst case is we can synthesize one. + */ + if (gtk_cell_area_activate_cell (area, widget, activate_cell, NULL, + &renderer_area, flags)) + return TRUE; + } + + return FALSE; +} + +/************************************************************* + * GtkCellLayoutIface * + *************************************************************/ +static void +gtk_cell_area_cell_layout_init (GtkCellLayoutIface *iface) +{ + iface->pack_start = gtk_cell_area_pack_default; + iface->pack_end = gtk_cell_area_pack_default; + iface->clear = gtk_cell_area_clear; + iface->add_attribute = gtk_cell_area_add_attribute; + iface->set_cell_data_func = gtk_cell_area_set_cell_data_func; + iface->clear_attributes = gtk_cell_area_clear_attributes; + iface->reorder = gtk_cell_area_reorder; + iface->get_cells = gtk_cell_area_get_cells; + iface->get_area = gtk_cell_area_get_area; +} + +static void +gtk_cell_area_pack_default (GtkCellLayout *cell_layout, + GtkCellRenderer *renderer, + gboolean expand) +{ + gtk_cell_area_add (GTK_CELL_AREA (cell_layout), renderer); +} + +static void +gtk_cell_area_clear (GtkCellLayout *cell_layout) +{ + GtkCellArea *area = GTK_CELL_AREA (cell_layout); + GList *l, *cells = + gtk_cell_layout_get_cells (cell_layout); + + for (l = cells; l; l = l->next) + { + GtkCellRenderer *renderer = l->data; + gtk_cell_area_remove (area, renderer); + } + + g_list_free (cells); +} + +static void +gtk_cell_area_add_attribute (GtkCellLayout *cell_layout, + GtkCellRenderer *renderer, + const gchar *attribute, + gint column) +{ + gtk_cell_area_attribute_connect (GTK_CELL_AREA (cell_layout), + renderer, attribute, column); +} + +static void +gtk_cell_area_set_cell_data_func (GtkCellLayout *cell_layout, + GtkCellRenderer *renderer, + GtkCellLayoutDataFunc func, + gpointer func_data, + GDestroyNotify destroy) +{ + GtkCellArea *area = GTK_CELL_AREA (cell_layout); + GtkCellAreaPrivate *priv = area->priv; + CellInfo *info; + + info = g_hash_table_lookup (priv->cell_info, renderer); + + if (info) + { + if (info->destroy && info->data) + info->destroy (info->data); + + if (func) + { + info->func = func; + info->data = func_data; + info->destroy = destroy; + } + else + { + info->func = NULL; + info->data = NULL; + info->destroy = NULL; + } + } + else + { + info = cell_info_new (func, func_data, destroy); + + g_hash_table_insert (priv->cell_info, renderer, info); + } +} + +static void +gtk_cell_area_clear_attributes (GtkCellLayout *cell_layout, + GtkCellRenderer *renderer) +{ + GtkCellArea *area = GTK_CELL_AREA (cell_layout); + GtkCellAreaPrivate *priv = area->priv; + CellInfo *info; + + info = g_hash_table_lookup (priv->cell_info, renderer); + + if (info) + { + g_slist_foreach (info->attributes, (GFunc)cell_attribute_free, NULL); + g_slist_free (info->attributes); + + info->attributes = NULL; + } +} + +static void +gtk_cell_area_reorder (GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + gint position) +{ + g_warning ("GtkCellLayout::reorder not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (cell_layout))); +} + +static gboolean +accum_cells (GtkCellRenderer *renderer, + GList **accum) +{ + *accum = g_list_prepend (*accum, renderer); + + return FALSE; +} + +static GList * +gtk_cell_area_get_cells (GtkCellLayout *cell_layout) +{ + GList *cells = NULL; + + gtk_cell_area_foreach (GTK_CELL_AREA (cell_layout), + (GtkCellCallback)accum_cells, + &cells); + + return g_list_reverse (cells); +} + +static GtkCellArea * +gtk_cell_area_get_area (GtkCellLayout *cell_layout) +{ + return GTK_CELL_AREA (cell_layout); +} + +/************************************************************* + * GtkBuildableIface * + *************************************************************/ +static void +gtk_cell_area_buildable_init (GtkBuildableIface *iface) +{ + iface->add_child = _gtk_cell_layout_buildable_add_child; + iface->custom_tag_start = _gtk_cell_layout_buildable_custom_tag_start; + iface->custom_tag_end = gtk_cell_area_buildable_custom_tag_end; +} + +static void +gtk_cell_area_buildable_custom_tag_end (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + gpointer *data) +{ + /* Just ignore the boolean return from here */ + _gtk_cell_layout_buildable_custom_tag_end (buildable, builder, child, tagname, data); +} + +/************************************************************* + * 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. + * + * Since: 3.0 + */ +void +gtk_cell_area_add (GtkCellArea *area, + GtkCellRenderer *renderer) +{ + GtkCellAreaClass *class; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + + class = GTK_CELL_AREA_GET_CLASS (area); + + if (class->add) + class->add (area, renderer); + else + g_warning ("GtkCellAreaClass::add not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); +} + +/** + * gtk_cell_area_remove: + * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer to remove from @area + * + * Removes @renderer from @area. + * + * Since: 3.0 + */ +void +gtk_cell_area_remove (GtkCellArea *area, + GtkCellRenderer *renderer) +{ + GtkCellAreaClass *class; + GtkCellAreaPrivate *priv; + GList *renderers, *l; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + + class = GTK_CELL_AREA_GET_CLASS (area); + priv = area->priv; + + /* Remove any custom attributes and custom cell data func here first */ + g_hash_table_remove (priv->cell_info, renderer); + + /* Remove focus siblings of this renderer */ + g_hash_table_remove (priv->focus_siblings, renderer); + + /* Remove this renderer from any focus renderer's sibling list */ + renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area)); + + for (l = renderers; l; l = l->next) + { + GtkCellRenderer *focus_renderer = l->data; + + if (gtk_cell_area_is_focus_sibling (area, focus_renderer, renderer)) + { + gtk_cell_area_remove_focus_sibling (area, focus_renderer, renderer); + break; + } + } + + g_list_free (renderers); + + if (class->remove) + class->remove (area, renderer); + else + g_warning ("GtkCellAreaClass::remove not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); +} + +static gboolean +get_has_renderer (GtkCellRenderer *renderer, + HasRendererCheck *check) +{ + if (renderer == check->renderer) + check->has_renderer = TRUE; + + return check->has_renderer; +} + +/** + * gtk_cell_area_has_renderer: + * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer to check + * + * Checks if @area contains @renderer. + * + * Return value: %TRUE if @renderer is in the @area. + * + * Since: 3.0 + */ +gboolean +gtk_cell_area_has_renderer (GtkCellArea *area, + GtkCellRenderer *renderer) +{ + HasRendererCheck check = { renderer, FALSE }; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE); + g_return_val_if_fail (GTK_IS_CELL_RENDERER (renderer), FALSE); + + gtk_cell_area_foreach (area, (GtkCellCallback)get_has_renderer, &check); + + return check.has_renderer; +} + +/** + * gtk_cell_area_foreach: + * @area: a #GtkCellArea + * @callback: the #GtkCellCallback to call + * @callback_data: user provided data pointer + * + * Calls @callback for every #GtkCellRenderer in @area. + * + * Since: 3.0 + */ +void +gtk_cell_area_foreach (GtkCellArea *area, + GtkCellCallback callback, + gpointer callback_data) +{ + GtkCellAreaClass *class; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (callback != NULL); + + class = GTK_CELL_AREA_GET_CLASS (area); + + if (class->foreach) + class->foreach (area, callback, callback_data); + else + g_warning ("GtkCellAreaClass::foreach not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); +} + +/** + * gtk_cell_area_foreach_alloc: + * @area: a #GtkCellArea + * @context: the #GtkCellAreaContext for this row of data. + * @widget: the #GtkWidget that @area is rendering to + * @cell_area: the @widget relative coordinates and size for @area + * @background_area: the @widget relative coordinates of the background area + * @callback: the #GtkCellAllocCallback to call + * @callback_data: user provided data pointer + * + * Calls @callback for every #GtkCellRenderer in @area with the + * allocated rectangle inside @cell_area. + * + * Since: 3.0 + */ +void +gtk_cell_area_foreach_alloc (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + const GdkRectangle *background_area, + GtkCellAllocCallback callback, + gpointer callback_data) +{ + GtkCellAreaClass *class; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (cell_area != NULL); + g_return_if_fail (callback != NULL); + + class = GTK_CELL_AREA_GET_CLASS (area); + + if (class->foreach_alloc) + class->foreach_alloc (area, context, widget, cell_area, background_area, callback, callback_data); + else + g_warning ("GtkCellAreaClass::foreach_alloc not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); +} + +/** + * gtk_cell_area_event: + * @area: a #GtkCellArea + * @context: the #GtkCellAreaContext for this row of data. + * @widget: the #GtkWidget that @area is rendering to + * @event: the #GdkEvent to handle + * @cell_area: the @widget relative coordinates for @area + * @flags: the #GtkCellRendererState for @area in this row. + * + * Delegates event handling to a #GtkCellArea. + * + * Return value: %TRUE if the event was handled by @area. + * + * Since: 3.0 + */ +gint +gtk_cell_area_event (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + GdkEvent *event, + const GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GtkCellAreaClass *class; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), 0); + g_return_val_if_fail (GTK_IS_CELL_AREA_CONTEXT (context), 0); + g_return_val_if_fail (GTK_IS_WIDGET (widget), 0); + g_return_val_if_fail (event != NULL, 0); + g_return_val_if_fail (cell_area != NULL, 0); + + class = GTK_CELL_AREA_GET_CLASS (area); + + if (class->event) + return class->event (area, context, widget, event, cell_area, flags); + + g_warning ("GtkCellAreaClass::event not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); + return 0; +} + +/** + * gtk_cell_area_render: + * @area: a #GtkCellArea + * @context: the #GtkCellAreaContext for this row of data. + * @widget: the #GtkWidget that @area is rendering to + * @cr: the #cairo_t to render with + * @background_area: the @widget relative coordinates for @area's background + * @cell_area: the @widget relative coordinates for @area + * @flags: the #GtkCellRendererState for @area in this row. + * @paint_focus: whether @area should paint focus on focused cells for focused rows or not. + * + * Renders @area's cells according to @area's layout onto @widget at + * the given coordinates. + * + * Since: 3.0 + */ +void +gtk_cell_area_render (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + cairo_t *cr, + const GdkRectangle *background_area, + const GdkRectangle *cell_area, + GtkCellRendererState flags, + gboolean paint_focus) +{ + GtkCellAreaClass *class; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (cr != NULL); + g_return_if_fail (background_area != NULL); + g_return_if_fail (cell_area != NULL); + + class = GTK_CELL_AREA_GET_CLASS (area); + + if (class->render) + class->render (area, context, widget, cr, background_area, cell_area, flags, paint_focus); + else + g_warning ("GtkCellAreaClass::render not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); +} + +/** + * gtk_cell_area_set_style_detail: + * @area: a #GtkCellArea + * @detail: the #GtkStyle detail string to set + * + * Sets the detail string used in any gtk_paint_*() functions + * used by @area. + * + * Since: 3.0 + */ +void +gtk_cell_area_set_style_detail (GtkCellArea *area, + const gchar *detail) +{ + GtkCellAreaPrivate *priv; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + + priv = area->priv; + + if (g_strcmp0 (priv->style_detail, detail) != 0) + { + g_free (priv->style_detail); + priv->style_detail = g_strdup (detail); + } +} + +/** + * gtk_cell_area_get_style_detail: + * @area: a #GtkCellArea + * + * Gets the detail string used in any gtk_paint_*() functions + * used by @area. + * + * Return value: the detail string, the string belongs to the area and should not be freed. + * + * Since: 3.0 + */ +G_CONST_RETURN gchar * +gtk_cell_area_get_style_detail (GtkCellArea *area) +{ + GtkCellAreaPrivate *priv; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + + priv = area->priv; + + return priv->style_detail; +} + +static gboolean +get_cell_allocation (GtkCellRenderer *renderer, + const GdkRectangle *cell_area, + const GdkRectangle *cell_background, + RendererAllocationData *data) +{ + if (data->renderer == renderer) + data->allocation = *cell_area; + + return (data->renderer == renderer); +} + +/** + * gtk_cell_area_get_cell_allocation: + * @area: a #GtkCellArea + * @context: the #GtkCellAreaContext 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: (out): where to store the allocation for @renderer + * + * Derives the allocation of @renderer inside @area if @area + * were to be renderered in @cell_area. + * + * Since: 3.0 + */ +void +gtk_cell_area_get_cell_allocation (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + GtkCellRenderer *renderer, + const GdkRectangle *cell_area, + GdkRectangle *allocation) +{ + RendererAllocationData data = { renderer, { 0, } }; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context)); + 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); + + gtk_cell_area_foreach_alloc (area, context, widget, cell_area, cell_area, + (GtkCellAllocCallback)get_cell_allocation, &data); + + *allocation = data.allocation; +} + +static gboolean +get_cell_by_position (GtkCellRenderer *renderer, + const GdkRectangle *cell_area, + const GdkRectangle *cell_background, + CellByPositionData *data) +{ + if (data->x >= cell_area->x && data->x < cell_area->x + cell_area->width && + data->y >= cell_area->y && data->y < cell_area->y + cell_area->height) + { + data->renderer = renderer; + data->cell_area = *cell_area; + } + + return (data->renderer != NULL); +} + +/** + * gtk_cell_area_get_cell_at_position: + * @area: a #GtkCellArea + * @context: the #GtkCellAreaContext used to hold sizes for @area. + * @widget: the #GtkWidget that @area is rendering on + * @cell_area: the whole allocated area for @area in @widget + * for this row + * @x: the x position + * @y: the y position + * @alloc_area: (out) (allow-none): where to store the inner allocated area of the + * returned cell renderer, or %NULL. + * + * Gets the #GtkCellRenderer at @x and @y coordinates inside @area and optionally + * returns the full cell allocation for it inside @cell_area. + * + * Return value: the #GtkCellRenderer at @x and @y. + * + * Since: 3.0 + */ +GtkCellRenderer * +gtk_cell_area_get_cell_at_position (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + gint x, + gint y, + GdkRectangle *alloc_area) +{ + CellByPositionData data = { x, y, NULL, { 0, } }; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + g_return_val_if_fail (GTK_IS_CELL_AREA_CONTEXT (context), NULL); + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + g_return_val_if_fail (cell_area != NULL, NULL); + g_return_val_if_fail (x >= cell_area->x && x <= cell_area->x + cell_area->width, NULL); + g_return_val_if_fail (y >= cell_area->y && y <= cell_area->y + cell_area->height, NULL); + + gtk_cell_area_foreach_alloc (area, context, widget, cell_area, cell_area, + (GtkCellAllocCallback)get_cell_by_position, &data); + + if (alloc_area) + *alloc_area = data.cell_area; + + return data.renderer; +} + +/************************************************************* + * API: Geometry * + *************************************************************/ +/** + * gtk_cell_area_create_context: + * @area: a #GtkCellArea + * + * Creates a #GtkCellAreaContext to be used with @area for + * all purposes. #GtkCellAreaContext stores geometry information + * for rows for which it was operated on, it is important to use + * the same context for the same row of data at all times (i.e. + * one should render and handle events with the same #GtkCellAreaContext + * which was used to request the size of those rows of data). + * + * Return value: (transfer full): a newly created #GtkCellAreaContext which can be used with @area. + * + * Since: 3.0 + */ +GtkCellAreaContext * +gtk_cell_area_create_context (GtkCellArea *area) +{ + GtkCellAreaClass *class; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + + class = GTK_CELL_AREA_GET_CLASS (area); + + if (class->create_context) + return class->create_context (area); + + g_warning ("GtkCellAreaClass::create_context not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); + + return NULL; +} + +/** + * gtk_cell_area_copy_context: + * @area: a #GtkCellArea + * @context: the #GtkCellAreaContext to copy + * + * This is sometimes needed for cases where rows need to share + * alignments in one orientation but may be separately grouped + * in the opposing orientation. + * + * For instance, #GtkIconView creates all icons (rows) to have + * the same width and the cells theirin to have the same + * horizontal alignments. However each row of icons may have + * a separate collective height. #GtkIconView uses this to + * request the heights of each row based on a context which + * was already used to request all the row widths that are + * to be displayed. + * + * Return value: (transfer full): a newly created #GtkCellAreaContext copy of @context. + * + * Since: 3.0 + */ +GtkCellAreaContext * +gtk_cell_area_copy_context (GtkCellArea *area, + GtkCellAreaContext *context) +{ + GtkCellAreaClass *class; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + g_return_val_if_fail (GTK_IS_CELL_AREA_CONTEXT (context), NULL); + + class = GTK_CELL_AREA_GET_CLASS (area); + + if (class->copy_context) + return class->copy_context (area, context); + + g_warning ("GtkCellAreaClass::copy_context not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); + + return NULL; +} + +/** + * gtk_cell_area_get_request_mode: + * @area: a #GtkCellArea + * + * Gets whether the area prefers a height-for-width layout + * or a width-for-height layout. + * + * Return value: The #GtkSizeRequestMode preferred by @area. + * + * Since: 3.0 + */ +GtkSizeRequestMode +gtk_cell_area_get_request_mode (GtkCellArea *area) +{ + GtkCellAreaClass *class; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), + GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH); + + class = GTK_CELL_AREA_GET_CLASS (area); + + if (class->get_request_mode) + return class->get_request_mode (area); + + g_warning ("GtkCellAreaClass::get_request_mode not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); + + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; +} + +/** + * gtk_cell_area_get_preferred_width: + * @area: a #GtkCellArea + * @context: the #GtkCellAreaContext to perform this request with + * @widget: the #GtkWidget where @area will be rendering + * @minimum_width: (out) (allow-none): location to store the minimum width, or %NULL + * @natural_width: (out) (allow-none): location to store the natural width, or %NULL + * + * Retrieves a cell area's initial minimum and natural width. + * + * @area will store some geometrical information in @context along the way, + * when requesting sizes over an arbitrary number of rows, its not important + * to check the @minimum_width and @natural_width of this call but rather to + * consult gtk_cell_area_context_get_preferred_width() after a series of + * requests. + * + * Since: 3.0 + */ +void +gtk_cell_area_get_preferred_width (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint *minimum_width, + gint *natural_width) +{ + GtkCellAreaClass *class; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + class = GTK_CELL_AREA_GET_CLASS (area); + + if (class->get_preferred_width) + class->get_preferred_width (area, context, widget, minimum_width, natural_width); + else + g_warning ("GtkCellAreaClass::get_preferred_width not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); +} + +/** + * gtk_cell_area_get_preferred_height_for_width: + * @area: a #GtkCellArea + * @context: the #GtkCellAreaContext which has already been requested for widths. + * @widget: the #GtkWidget where @area will be rendering + * @width: the width for which to check the height of this area + * @minimum_height: (out) (allow-none): location to store the minimum height, or %NULL + * @natural_height: (out) (allow-none): location to store the natural height, or %NULL + * + * Retrieves a cell area's minimum and natural height if it would be given + * the specified @width. + * + * @area stores some geometrical information in @context along the way + * while calling gtk_cell_area_get_preferred_width(), it's important to + * perform a series of gtk_cell_area_get_preferred_width() requests with + * @context first and then call gtk_cell_area_get_preferred_height_for_width() + * on each cell area individually to get the height for width of each + * fully requested row. + * + * If at some point, the width of a single row changes, it should be + * requested with gtk_cell_area_get_preferred_width() again and then + * the full width of the requested rows checked again with + * gtk_cell_area_context_get_preferred_width(). + * + * Since: 3.0 + */ +void +gtk_cell_area_get_preferred_height_for_width (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height) +{ + GtkCellAreaClass *class; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + class = GTK_CELL_AREA_GET_CLASS (area); + class->get_preferred_height_for_width (area, context, widget, width, minimum_height, natural_height); +} + + +/** + * gtk_cell_area_get_preferred_height: + * @area: a #GtkCellArea + * @context: the #GtkCellAreaContext to perform this request with + * @widget: the #GtkWidget where @area will be rendering + * @minimum_height: (out) (allow-none): location to store the minimum height, or %NULL + * @natural_height: (out) (allow-none): location to store the natural height, or %NULL + * + * Retrieves a cell area's initial minimum and natural height. + * + * @area will store some geometrical information in @context along the way, + * when requesting sizes over an arbitrary number of rows, its not important + * to check the @minimum_height and @natural_height of this call but rather to + * consult gtk_cell_area_context_get_preferred_height() after a series of + * requests. + * + * Since: 3.0 + */ +void +gtk_cell_area_get_preferred_height (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint *minimum_height, + gint *natural_height) +{ + GtkCellAreaClass *class; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + class = GTK_CELL_AREA_GET_CLASS (area); + + if (class->get_preferred_height) + class->get_preferred_height (area, context, widget, minimum_height, natural_height); + else + g_warning ("GtkCellAreaClass::get_preferred_height not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); +} + +/** + * gtk_cell_area_get_preferred_width_for_height: + * @area: a #GtkCellArea + * @context: the #GtkCellAreaContext which has already been requested for widths. + * @widget: the #GtkWidget where @area will be rendering + * @height: the height for which to check the width of this area + * @minimum_width: (out) (allow-none): location to store the minimum width, or %NULL + * @natural_width: (out) (allow-none): location to store the natural width, or %NULL + * + * Retrieves a cell area's minimum and natural width if it would be given + * the specified @height. + * + * @area stores some geometrical information in @context along the way + * while calling gtk_cell_area_get_preferred_height(), it's important to + * perform a series of gtk_cell_area_get_preferred_height() requests with + * @context first and then call gtk_cell_area_get_preferred_width_for_height() + * on each cell area individually to get the height for width of each + * fully requested row. + * + * If at some point, the height of a single row changes, it should be + * requested with gtk_cell_area_get_preferred_height() again and then + * the full height of the requested rows checked again with + * gtk_cell_area_context_get_preferred_height(). + * + * Since: 3.0 + */ +void +gtk_cell_area_get_preferred_width_for_height (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width) +{ + GtkCellAreaClass *class; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + class = GTK_CELL_AREA_GET_CLASS (area); + class->get_preferred_width_for_height (area, context, widget, height, minimum_width, natural_width); +} + +/************************************************************* + * 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. + * + * Since: 3.0 + */ +void +gtk_cell_area_attribute_connect (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *attribute, + gint column) +{ + GtkCellAreaPrivate *priv; + CellInfo *info; + CellAttribute *cell_attribute; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + g_return_if_fail (attribute != NULL); + g_return_if_fail (gtk_cell_area_has_renderer (area, renderer)); + + priv = area->priv; + info = g_hash_table_lookup (priv->cell_info, renderer); + + if (!info) + { + info = cell_info_new (NULL, NULL, NULL); + + g_hash_table_insert (priv->cell_info, renderer, info); + } + else + { + GSList *node; + + /* Check we are not adding the same attribute twice */ + if ((node = g_slist_find_custom (info->attributes, attribute, + (GCompareFunc)cell_attribute_find)) != NULL) + { + cell_attribute = node->data; + + g_warning ("Cannot connect attribute `%s' for cell renderer class `%s' " + "since `%s' is already attributed to column %d", + attribute, + g_type_name (G_TYPE_FROM_INSTANCE (area)), + attribute, cell_attribute->column); + return; + } + } + + cell_attribute = cell_attribute_new (renderer, attribute, column); + + if (!cell_attribute) + { + g_warning ("Cannot connect attribute `%s' for cell renderer class `%s' " + "since attribute does not exist", + attribute, + g_type_name (G_TYPE_FROM_INSTANCE (area))); + return; + } + + 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. + * + * Since: 3.0 + */ +void +gtk_cell_area_attribute_disconnect (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *attribute) +{ + GtkCellAreaPrivate *priv; + CellInfo *info; + CellAttribute *cell_attribute; + GSList *node; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + g_return_if_fail (attribute != NULL); + g_return_if_fail (gtk_cell_area_has_renderer (area, renderer)); + + priv = area->priv; + info = g_hash_table_lookup (priv->cell_info, renderer); + + if (info) + { + node = g_slist_find_custom (info->attributes, attribute, + (GCompareFunc)cell_attribute_find); + if (node) + { + cell_attribute = node->data; + + cell_attribute_free (cell_attribute); + + info->attributes = g_slist_delete_link (info->attributes, node); + } + } +} + +/** + * gtk_cell_area_apply_attributes + * @area: a #GtkCellArea + * @tree_model: the #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. + * + * Since: 3.0 + */ +void +gtk_cell_area_apply_attributes (GtkCellArea *area, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gboolean is_expander, + gboolean is_expanded) +{ + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_TREE_MODEL (tree_model)); + g_return_if_fail (iter != NULL); + + g_signal_emit (area, cell_area_signals[SIGNAL_APPLY_ATTRIBUTES], 0, + tree_model, iter, is_expander, is_expanded); +} + +/** + * 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. + * + * Return value: The current #GtkTreePath string for the current + * attributes applied to @area. This string belongs to the area and + * should not be freed. + * + * Since: 3.0 + */ +G_CONST_RETURN 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 * + *************************************************************/ +/** + * gtk_cell_area_class_install_cell_property: + * @aclass: a #GtkCellAreaClass + * @property_id: the id for the property + * @pspec: the #GParamSpec for the property + * + * Installs a cell property on a cell area class. + * + * Since: 3.0 + */ +void +gtk_cell_area_class_install_cell_property (GtkCellAreaClass *aclass, + guint property_id, + GParamSpec *pspec) +{ + g_return_if_fail (GTK_IS_CELL_AREA_CLASS (aclass)); + g_return_if_fail (G_IS_PARAM_SPEC (pspec)); + if (pspec->flags & G_PARAM_WRITABLE) + g_return_if_fail (aclass->set_cell_property != NULL); + if (pspec->flags & G_PARAM_READABLE) + g_return_if_fail (aclass->get_cell_property != NULL); + g_return_if_fail (property_id > 0); + g_return_if_fail (PARAM_SPEC_PARAM_ID (pspec) == 0); /* paranoid */ + g_return_if_fail ((pspec->flags & (G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY)) == 0); + + if (g_param_spec_pool_lookup (cell_property_pool, pspec->name, G_OBJECT_CLASS_TYPE (aclass), TRUE)) + { + g_warning (G_STRLOC ": class `%s' already contains a cell property named `%s'", + G_OBJECT_CLASS_NAME (aclass), pspec->name); + return; + } + g_param_spec_ref (pspec); + g_param_spec_sink (pspec); + PARAM_SPEC_SET_PARAM_ID (pspec, property_id); + g_param_spec_pool_insert (cell_property_pool, pspec, G_OBJECT_CLASS_TYPE (aclass)); +} + +/** + * gtk_cell_area_class_find_cell_property: + * @aclass: a #GtkCellAreaClass + * @property_name: the name of the child property to find + * + * Finds a cell property of a cell area class by name. + * + * Return value: (allow-none): the #GParamSpec of the child property or %NULL if @aclass has no + * child property with that name. + * + * Since: 3.0 + */ +GParamSpec* +gtk_cell_area_class_find_cell_property (GtkCellAreaClass *aclass, + const gchar *property_name) +{ + g_return_val_if_fail (GTK_IS_CELL_AREA_CLASS (aclass), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + return g_param_spec_pool_lookup (cell_property_pool, + property_name, + G_OBJECT_CLASS_TYPE (aclass), + TRUE); +} + +/** + * gtk_cell_area_class_list_cell_properties: + * @aclass: a #GtkCellAreaClass + * @n_properties: location to return the number of cell properties found + * + * Returns all cell properties of a cell area class. + * + * Return value: a newly allocated %NULL-terminated array of #GParamSpec*. + * The array must be freed with g_free(). + * + * Since: 3.0 + */ +GParamSpec** +gtk_cell_area_class_list_cell_properties (GtkCellAreaClass *aclass, + guint *n_properties) +{ + GParamSpec **pspecs; + guint n; + + g_return_val_if_fail (GTK_IS_CELL_AREA_CLASS (aclass), NULL); + + pspecs = g_param_spec_pool_list (cell_property_pool, + G_OBJECT_CLASS_TYPE (aclass), + &n); + if (n_properties) + *n_properties = n; + + return pspecs; +} + +/** + * gtk_cell_area_add_with_properties: + * @area: a #GtkCellArea + * @renderer: a #GtkCellRenderer to be placed inside @area + * @first_prop_name: the name of the first cell property to set + * @Varargs: a %NULL-terminated list of property names and values, starting + * with @first_prop_name + * + * Adds @renderer to @area, setting cell properties at the same time. + * See gtk_cell_area_add() and gtk_cell_area_child_set() for more details. + * + * Since: 3.0 + */ +void +gtk_cell_area_add_with_properties (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *first_prop_name, + ...) +{ + GtkCellAreaClass *class; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + + class = GTK_CELL_AREA_GET_CLASS (area); + + if (class->add) + { + va_list var_args; + + class->add (area, renderer); + + va_start (var_args, first_prop_name); + gtk_cell_area_cell_set_valist (area, renderer, first_prop_name, var_args); + va_end (var_args); + } + else + g_warning ("GtkCellAreaClass::add not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); +} + +/** + * gtk_cell_area_cell_set: + * @area: a #GtkCellArea + * @renderer: a #GtkCellRenderer which is a cell inside @area + * @first_prop_name: the name of the first cell property to set + * @Varargs: a %NULL-terminated list of property names and values, starting + * with @first_prop_name + * + * Sets one or more cell properties for @cell in @area. + * + * Since: 3.0 + */ +void +gtk_cell_area_cell_set (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *first_prop_name, + ...) +{ + va_list var_args; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + + va_start (var_args, first_prop_name); + gtk_cell_area_cell_set_valist (area, renderer, first_prop_name, var_args); + va_end (var_args); +} + +/** + * gtk_cell_area_cell_get: + * @area: a #GtkCellArea + * @renderer: a #GtkCellRenderer which is inside @area + * @first_prop_name: the name of the first cell property to get + * @Varargs: return location for the first cell property, followed + * optionally by more name/return location pairs, followed by %NULL + * + * Gets the values of one or more cell properties for @renderer in @area. + * + * Since: 3.0 + */ +void +gtk_cell_area_cell_get (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *first_prop_name, + ...) +{ + va_list var_args; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + + va_start (var_args, first_prop_name); + gtk_cell_area_cell_get_valist (area, renderer, first_prop_name, var_args); + va_end (var_args); +} + +static inline void +area_get_cell_property (GtkCellArea *area, + GtkCellRenderer *renderer, + GParamSpec *pspec, + GValue *value) +{ + GtkCellAreaClass *class = g_type_class_peek (pspec->owner_type); + + class->get_cell_property (area, renderer, PARAM_SPEC_PARAM_ID (pspec), value, pspec); +} + +static inline void +area_set_cell_property (GtkCellArea *area, + GtkCellRenderer *renderer, + GParamSpec *pspec, + const GValue *value) +{ + GValue tmp_value = { 0, }; + GtkCellAreaClass *class = g_type_class_peek (pspec->owner_type); + + /* provide a copy to work from, convert (if necessary) and validate */ + g_value_init (&tmp_value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + if (!g_value_transform (value, &tmp_value)) + g_warning ("unable to set cell property `%s' of type `%s' from value of type `%s'", + pspec->name, + g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)), + G_VALUE_TYPE_NAME (value)); + else if (g_param_value_validate (pspec, &tmp_value) && !(pspec->flags & G_PARAM_LAX_VALIDATION)) + { + gchar *contents = g_strdup_value_contents (value); + + g_warning ("value \"%s\" of type `%s' is invalid for property `%s' of type `%s'", + contents, + G_VALUE_TYPE_NAME (value), + pspec->name, + g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec))); + g_free (contents); + } + else + { + class->set_cell_property (area, renderer, PARAM_SPEC_PARAM_ID (pspec), &tmp_value, pspec); + } + g_value_unset (&tmp_value); +} + +/** + * gtk_cell_area_cell_set_valist: + * @area: a #GtkCellArea + * @renderer: a #GtkCellRenderer which inside @area + * @first_property_name: the name of the first cell property to set + * @var_args: a %NULL-terminated list of property names and values, starting + * with @first_prop_name + * + * Sets one or more cell properties for @renderer in @area. + * + * Since: 3.0 + */ +void +gtk_cell_area_cell_set_valist (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *first_property_name, + va_list var_args) +{ + const gchar *name; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + + name = first_property_name; + while (name) + { + GValue value = { 0, }; + gchar *error = NULL; + GParamSpec *pspec = + g_param_spec_pool_lookup (cell_property_pool, name, + G_OBJECT_TYPE (area), TRUE); + if (!pspec) + { + g_warning ("%s: cell area class `%s' has no cell property named `%s'", + G_STRLOC, G_OBJECT_TYPE_NAME (area), name); + break; + } + if (!(pspec->flags & G_PARAM_WRITABLE)) + { + g_warning ("%s: cell property `%s' of cell area class `%s' is not writable", + G_STRLOC, pspec->name, G_OBJECT_TYPE_NAME (area)); + break; + } + + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + G_VALUE_COLLECT (&value, var_args, 0, &error); + if (error) + { + g_warning ("%s: %s", G_STRLOC, error); + g_free (error); + + /* we purposely leak the value here, it might not be + * in a sane state if an error condition occoured + */ + break; + } + area_set_cell_property (area, renderer, pspec, &value); + g_value_unset (&value); + name = va_arg (var_args, gchar*); + } +} + +/** + * gtk_cell_area_cell_get_valist: + * @area: a #GtkCellArea + * @renderer: a #GtkCellRenderer inside @area + * @first_property_name: the name of the first property to get + * @var_args: return location for the first property, followed + * optionally by more name/return location pairs, followed by %NULL + * + * Gets the values of one or more cell properties for @renderer in @area. + * + * Since: 3.0 + */ +void +gtk_cell_area_cell_get_valist (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *first_property_name, + va_list var_args) +{ + const gchar *name; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + + name = first_property_name; + while (name) + { + GValue value = { 0, }; + GParamSpec *pspec; + gchar *error; + + pspec = g_param_spec_pool_lookup (cell_property_pool, name, + G_OBJECT_TYPE (area), TRUE); + if (!pspec) + { + g_warning ("%s: cell area class `%s' has no cell property named `%s'", + G_STRLOC, G_OBJECT_TYPE_NAME (area), name); + break; + } + if (!(pspec->flags & G_PARAM_READABLE)) + { + g_warning ("%s: cell property `%s' of cell area class `%s' is not readable", + G_STRLOC, pspec->name, G_OBJECT_TYPE_NAME (area)); + break; + } + + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + area_get_cell_property (area, renderer, pspec, &value); + G_VALUE_LCOPY (&value, var_args, 0, &error); + if (error) + { + g_warning ("%s: %s", G_STRLOC, error); + g_free (error); + g_value_unset (&value); + break; + } + g_value_unset (&value); + name = va_arg (var_args, gchar*); + } +} + +/** + * gtk_cell_area_cell_set_property: + * @area: a #GtkCellArea + * @renderer: a #GtkCellRenderer inside @area + * @property_name: the name of the cell property to set + * @value: the value to set the cell property to + * + * Sets a cell property for @renderer in @area. + * + * Since: 3.0 + */ +void +gtk_cell_area_cell_set_property (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *property_name, + const GValue *value) +{ + GParamSpec *pspec; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + g_return_if_fail (property_name != NULL); + g_return_if_fail (G_IS_VALUE (value)); + + pspec = g_param_spec_pool_lookup (cell_property_pool, property_name, + G_OBJECT_TYPE (area), TRUE); + if (!pspec) + g_warning ("%s: cell area class `%s' has no cell property named `%s'", + G_STRLOC, G_OBJECT_TYPE_NAME (area), property_name); + else if (!(pspec->flags & G_PARAM_WRITABLE)) + g_warning ("%s: cell property `%s' of cell area class `%s' is not writable", + G_STRLOC, pspec->name, G_OBJECT_TYPE_NAME (area)); + else + { + area_set_cell_property (area, renderer, pspec, value); + } +} + +/** + * gtk_cell_area_cell_get_property: + * @area: a #GtkCellArea + * @renderer: a #GtkCellRenderer inside @area + * @property_name: the name of the property to get + * @value: a location to return the value + * + * Gets the value of a cell property for @renderer in @area. + * + * Since: 3.0 + */ +void +gtk_cell_area_cell_get_property (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *property_name, + GValue *value) +{ + GParamSpec *pspec; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + g_return_if_fail (property_name != NULL); + g_return_if_fail (G_IS_VALUE (value)); + + pspec = g_param_spec_pool_lookup (cell_property_pool, property_name, + G_OBJECT_TYPE (area), TRUE); + if (!pspec) + g_warning ("%s: cell area class `%s' has no cell property named `%s'", + G_STRLOC, G_OBJECT_TYPE_NAME (area), property_name); + else if (!(pspec->flags & G_PARAM_READABLE)) + g_warning ("%s: cell property `%s' of cell area class `%s' is not readable", + G_STRLOC, pspec->name, G_OBJECT_TYPE_NAME (area)); + else + { + GValue *prop_value, tmp_value = { 0, }; + + /* auto-conversion of the callers value type + */ + if (G_VALUE_TYPE (value) == G_PARAM_SPEC_VALUE_TYPE (pspec)) + { + g_value_reset (value); + prop_value = value; + } + else if (!g_value_type_transformable (G_PARAM_SPEC_VALUE_TYPE (pspec), G_VALUE_TYPE (value))) + { + g_warning ("can't retrieve cell property `%s' of type `%s' as value of type `%s'", + pspec->name, + g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)), + G_VALUE_TYPE_NAME (value)); + return; + } + else + { + g_value_init (&tmp_value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + prop_value = &tmp_value; + } + + area_get_cell_property (area, renderer, pspec, prop_value); + + if (prop_value != value) + { + g_value_transform (prop_value, value); + g_value_unset (&tmp_value); + } + } +} + +/************************************************************* + * API: Focus * + *************************************************************/ + +/** + * gtk_cell_area_is_activatable: + * @area: a #GtkCellArea + * + * Returns whether the area can do anything when activated, + * after applying new attributes to @area. + * + * Return value: whether @area can do anything when activated. + * + * Since: 3.0 + */ +gboolean +gtk_cell_area_is_activatable (GtkCellArea *area) +{ + g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE); + + return GTK_CELL_AREA_GET_CLASS (area)->is_activatable (area); +} + +/** + * gtk_cell_area_focus: + * @area: a #GtkCellArea + * @direction: the #GtkDirectionType + * + * This should be called by the @area's owning layout widget + * when focus is to be passed to @area, or moved within @area + * for a given @direction and row data. + * + * Implementing #GtkCellArea classes should implement this + * method to receive and navigate focus in it's own way particular + * to how it lays out cells. + * + * Return value: %TRUE if focus remains inside @area as a result of this call. + * + * Since: 3.0 + */ +gboolean +gtk_cell_area_focus (GtkCellArea *area, + GtkDirectionType direction) +{ + GtkCellAreaClass *class; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE); + + class = GTK_CELL_AREA_GET_CLASS (area); + + if (class->focus) + return class->focus (area, direction); + + g_warning ("GtkCellAreaClass::focus not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); + + return FALSE; +} + +/** + * gtk_cell_area_activate: + * @area: a #GtkCellArea + * @context: the #GtkCellAreaContext in context with the current row data + * @widget: the #GtkWidget that @area is rendering on + * @cell_area: the size and location of @area relative to @widget's allocation + * @flags: the #GtkCellRendererState flags for @area for this row of data. + * @edit_only: if %TRUE then only cell renderers that are %GTK_CELL_RENDERER_MODE_EDITABLE + * will be activated. + * + * Activates @area, usually by activating the currently focused + * cell, however some subclasses which embed widgets in the area + * can also activate a widget if it currently has the focus. + * + * Return value: Whether @area was successfully activated. + * + * Since: 3.0 + */ +gboolean +gtk_cell_area_activate (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + GtkCellRendererState flags, + gboolean edit_only) +{ + g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE); + + return GTK_CELL_AREA_GET_CLASS (area)->activate (area, context, widget, cell_area, flags, edit_only); +} + + +/** + * gtk_cell_area_set_focus_cell: + * @area: a #GtkCellArea + * @focus_cell: the #GtkCellRenderer to give focus to + * + * This is generally called from #GtkCellArea implementations + * either gtk_cell_area_grab_focus() or gtk_cell_area_update_focus() + * is called. It's also up to the #GtkCellArea implementation + * to update the focused cell when receiving events from + * gtk_cell_area_event() appropriately. + * + * Since: 3.0 + */ +void +gtk_cell_area_set_focus_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->focus_cell != renderer) + { + if (priv->focus_cell) + g_object_unref (priv->focus_cell); + + priv->focus_cell = renderer; + + if (priv->focus_cell) + g_object_ref (priv->focus_cell); + + 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); + +} + +/** + * gtk_cell_area_get_focus_cell: + * @area: a #GtkCellArea + * + * Retrieves the currently focused cell for @area + * + * Return value: the currently focused cell in @area. + * + * Since: 3.0 + */ +GtkCellRenderer * +gtk_cell_area_get_focus_cell (GtkCellArea *area) +{ + GtkCellAreaPrivate *priv; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + + priv = area->priv; + + return priv->focus_cell; +} + + +/************************************************************* + * API: Focus Siblings * + *************************************************************/ + +/** + * gtk_cell_area_add_focus_sibling: + * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer expected to have focus + * @sibling: the #GtkCellRenderer to add to @renderer's focus area + * + * Adds @sibling to @renderer's focusable area, focus will be drawn + * around @renderer and all of it's siblings if @renderer can + * focus for a given row. + * + * Events handled by focus siblings can also activate the given + * focusable @renderer. + * + * Since: 3.0 + */ +void +gtk_cell_area_add_focus_sibling (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellRenderer *sibling) +{ + GtkCellAreaPrivate *priv; + GList *siblings; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + g_return_if_fail (GTK_IS_CELL_RENDERER (sibling)); + g_return_if_fail (renderer != sibling); + g_return_if_fail (gtk_cell_area_has_renderer (area, renderer)); + g_return_if_fail (gtk_cell_area_has_renderer (area, sibling)); + g_return_if_fail (!gtk_cell_area_is_focus_sibling (area, renderer, sibling)); + + /* XXX We should also check that sibling is not in any other renderer's sibling + * list already, a renderer can be sibling of only one focusable renderer + * at a time. + */ + + priv = area->priv; + + siblings = g_hash_table_lookup (priv->focus_siblings, renderer); + + if (siblings) + siblings = g_list_append (siblings, sibling); + else + { + siblings = g_list_append (siblings, sibling); + g_hash_table_insert (priv->focus_siblings, renderer, siblings); + } +} + +/** + * gtk_cell_area_remove_focus_sibling: + * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer expected to have focus + * @sibling: the #GtkCellRenderer to remove from @renderer's focus area + * + * Removes @sibling from @renderer's focus sibling list + * (see gtk_cell_area_add_focus_sibling()). + * + * Since: 3.0 + */ +void +gtk_cell_area_remove_focus_sibling (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellRenderer *sibling) +{ + GtkCellAreaPrivate *priv; + GList *siblings; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + g_return_if_fail (GTK_IS_CELL_RENDERER (sibling)); + g_return_if_fail (gtk_cell_area_is_focus_sibling (area, renderer, sibling)); + + priv = area->priv; + + siblings = g_hash_table_lookup (priv->focus_siblings, renderer); + + siblings = g_list_copy (siblings); + siblings = g_list_remove (siblings, sibling); + + if (!siblings) + g_hash_table_remove (priv->focus_siblings, renderer); + else + g_hash_table_insert (priv->focus_siblings, renderer, siblings); +} + +/** + * gtk_cell_area_is_focus_sibling: + * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer expected to have focus + * @sibling: the #GtkCellRenderer to check against @renderer's sibling list + * + * Returns %TRUE if @sibling is one of @renderer's focus siblings + * (see gtk_cell_area_add_focus_sibling()). + * + * Since: 3.0 + */ +gboolean +gtk_cell_area_is_focus_sibling (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellRenderer *sibling) +{ + GtkCellAreaPrivate *priv; + GList *siblings, *l; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE); + g_return_val_if_fail (GTK_IS_CELL_RENDERER (renderer), FALSE); + g_return_val_if_fail (GTK_IS_CELL_RENDERER (sibling), FALSE); + + priv = area->priv; + + siblings = g_hash_table_lookup (priv->focus_siblings, renderer); + + for (l = siblings; l; l = l->next) + { + GtkCellRenderer *a_sibling = l->data; + + if (a_sibling == sibling) + return TRUE; + } + + return FALSE; +} + +/** + * gtk_cell_area_get_focus_siblings: + * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer expected to have focus + * + * Gets the focus sibling cell renderers for @renderer. + * + * Return value: (element-type GtkCellRenderer) (transfer none): A #GList of #GtkCellRenderers. + * The returned list is internal and should not be freed. + * + * Since: 3.0 + */ +const GList * +gtk_cell_area_get_focus_siblings (GtkCellArea *area, + GtkCellRenderer *renderer) +{ + GtkCellAreaPrivate *priv; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + g_return_val_if_fail (GTK_IS_CELL_RENDERER (renderer), NULL); + + priv = area->priv; + + return g_hash_table_lookup (priv->focus_siblings, renderer); +} + +/** + * gtk_cell_area_get_focus_from_sibling: + * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer + * + * Gets the #GtkCellRenderer which is expected to be focusable + * for which @renderer is, or may be a sibling. + * + * This is handy for #GtkCellArea subclasses when handling events, + * after determining the renderer at the event location it can + * then chose to activate the focus cell for which the event + * cell may have been a sibling. + * + * Return value: the #GtkCellRenderer for which @renderer is a sibling, or %NULL. + * + * Since: 3.0 + */ +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 * + *************************************************************/ +static void +gtk_cell_area_add_editable (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *editable, + const GdkRectangle *cell_area) +{ + g_signal_emit (area, cell_area_signals[SIGNAL_ADD_EDITABLE], 0, + renderer, editable, cell_area, area->priv->current_path); +} + +static void +gtk_cell_area_remove_editable (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *editable) +{ + g_signal_emit (area, cell_area_signals[SIGNAL_REMOVE_EDITABLE], 0, renderer, editable); +} + +static void +cell_area_remove_widget_cb (GtkCellEditable *editable, + GtkCellArea *area) +{ + GtkCellAreaPrivate *priv = area->priv; + + g_assert (priv->edit_widget == editable); + g_assert (priv->edited_cell != NULL); + + gtk_cell_area_remove_editable (area, priv->edited_cell, priv->edit_widget); + + /* Now that we're done with editing the widget and it can be removed, + * remove our references to the widget and disconnect handlers */ + gtk_cell_area_set_edited_cell (area, NULL); + gtk_cell_area_set_edit_widget (area, NULL); +} + +static 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"); + } +} + +static void +gtk_cell_area_set_edit_widget (GtkCellArea *area, + GtkCellEditable *editable) +{ + GtkCellAreaPrivate *priv; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (editable == NULL || GTK_IS_CELL_EDITABLE (editable)); + + priv = area->priv; + + if (priv->edit_widget != editable) + { + if (priv->edit_widget) + { + g_signal_handler_disconnect (priv->edit_widget, priv->remove_widget_id); + + g_object_unref (priv->edit_widget); + } + + priv->edit_widget = editable; + + if (priv->edit_widget) + { + priv->remove_widget_id = + g_signal_connect (priv->edit_widget, "remove-widget", + G_CALLBACK (cell_area_remove_widget_cb), area); + + g_object_ref (priv->edit_widget); + } + + g_object_notify (G_OBJECT (area), "edit-widget"); + } +} + +/** + * gtk_cell_area_get_edited_cell: + * @area: a #GtkCellArea + * + * Gets the #GtkCellRenderer in @area that is currently + * being edited. + * + * Return value: The currently edited #GtkCellRenderer + * + * Since: 3.0 + */ +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; +} + +/** + * gtk_cell_area_get_edit_widget: + * @area: a #GtkCellArea + * + * Gets the #GtkCellEditable widget currently used + * to edit the currently edited cell. + * + * Return value: The currently active #GtkCellEditable widget + * + * Since: 3.0 + */ +GtkCellEditable * +gtk_cell_area_get_edit_widget (GtkCellArea *area) +{ + GtkCellAreaPrivate *priv; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + + priv = area->priv; + + return priv->edit_widget; +} + +/** + * gtk_cell_area_activate_cell: + * @area: a #GtkCellArea + * @widget: the #GtkWidget that @area is rendering onto + * @renderer: the #GtkCellRenderer in @area to activate + * @event: the #GdkEvent for which cell activation should occur + * @cell_area: the #GdkRectangle in @widget relative coordinates + * of @renderer for the current row. + * @flags: the #GtkCellRendererState for @renderer + * + * This is used by #GtkCellArea subclasses when handling events + * to activate cells, the base #GtkCellArea class activates cells + * for keyboard events for free in it's own GtkCellArea->activate() + * implementation. + * + * Return value: whether cell activation was successful + * + * Since: 3.0 + */ +gboolean +gtk_cell_area_activate_cell (GtkCellArea *area, + GtkWidget *widget, + GtkCellRenderer *renderer, + GdkEvent *event, + const GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GtkCellRendererMode mode; + GtkCellAreaPrivate *priv; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE); + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + g_return_val_if_fail (GTK_IS_CELL_RENDERER (renderer), FALSE); + g_return_val_if_fail (cell_area != NULL, FALSE); + + priv = area->priv; + + g_object_get (renderer, "mode", &mode, NULL); + + if (mode == GTK_CELL_RENDERER_MODE_ACTIVATABLE) + { + if (gtk_cell_renderer_activate (renderer, + event, widget, + priv->current_path, + cell_area, + cell_area, + flags)) + return TRUE; + } + else if (mode == GTK_CELL_RENDERER_MODE_EDITABLE) + { + GtkCellEditable *editable_widget; + GdkRectangle inner_area; + + gtk_cell_area_inner_cell_area (area, widget, cell_area, &inner_area); + + editable_widget = + gtk_cell_renderer_start_editing (renderer, + event, widget, + priv->current_path, + &inner_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, renderer); + gtk_cell_area_set_edit_widget (area, editable_widget); + + /* Signal that editing started so that callers can get + * a handle on the editable_widget */ + gtk_cell_area_add_editable (area, priv->focus_cell, editable_widget, cell_area); + + /* If the signal was successfully handled start the editing */ + if (gtk_widget_get_parent (GTK_WIDGET (editable_widget))) + { + gtk_cell_editable_start_editing (editable_widget, NULL); + gtk_widget_grab_focus (GTK_WIDGET (editable_widget)); + } + else + { + /* Otherwise clear the editing state and fire a warning */ + gtk_cell_area_set_edited_cell (area, NULL); + gtk_cell_area_set_edit_widget (area, NULL); + + g_warning ("GtkCellArea::add-editable fired in the dark, no cell editing was started."); + } + + return TRUE; + } + } + + return FALSE; +} + +/** + * gtk_cell_area_stop_editing: + * @area: a #GtkCellArea + * @canceled: whether editing was canceled. + * + * Explicitly stops the editing of the currently + * edited cell (see gtk_cell_area_get_edited_cell()). + * + * If @canceled is %TRUE, the cell renderer will emit + * the ::editing-canceled signal. + * + * Since: 3.0 + */ +void +gtk_cell_area_stop_editing (GtkCellArea *area, + gboolean canceled) +{ + GtkCellAreaPrivate *priv; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + + priv = area->priv; + + if (priv->edited_cell) + { + GtkCellEditable *edit_widget = g_object_ref (priv->edit_widget); + GtkCellRenderer *edit_cell = g_object_ref (priv->edited_cell); + + /* Stop editing of the cell renderer */ + gtk_cell_renderer_stop_editing (priv->edited_cell, canceled); + + /* Remove any references to the editable widget */ + gtk_cell_area_set_edited_cell (area, NULL); + gtk_cell_area_set_edit_widget (area, NULL); + + /* Send the remove-widget signal explicitly (this is done after setting + * the edit cell/widget NULL to avoid feedback) + */ + gtk_cell_area_remove_editable (area, edit_cell, edit_widget); + g_object_unref (edit_cell); + g_object_unref (edit_widget); + } +} + +/************************************************************* + * API: Convenience for area implementations * + *************************************************************/ + +/** + * gtk_cell_area_inner_cell_area: + * @area: a #GtkCellArea + * @widget: the #GtkWidget that @area is rendering onto + * @cell_area: the @widget relative coordinates where one of @area's cells + * is to be placed + * @inner_area: (out): the return location for the inner cell area + * + * This is a convenience function for #GtkCellArea implementations + * to get the inner area where a given #GtkCellRenderer will be + * rendered. It removes any padding previously added by gtk_cell_area_request_renderer(). + * + * Since: 3.0 + */ +void +gtk_cell_area_inner_cell_area (GtkCellArea *area, + GtkWidget *widget, + const GdkRectangle *cell_area, + GdkRectangle *inner_area) +{ + gint focus_line_width; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (cell_area != NULL); + g_return_if_fail (inner_area != NULL); + + gtk_widget_style_get (widget, "focus-line-width", &focus_line_width, NULL); + + *inner_area = *cell_area; + + inner_area->x += focus_line_width; + inner_area->width -= focus_line_width * 2; + inner_area->y += focus_line_width; + inner_area->height -= focus_line_width * 2; +} + +/** + * gtk_cell_area_request_renderer: + * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer to request size for + * @orientation: the #GtkOrientation in which to request size + * @widget: the #GtkWidget that @area is rendering onto + * @for_size: the allocation contextual size to request for, or -1 if + * the base request for the orientation is to be returned. + * @minimum_size: (out) (allow-none): location to store the minimum size, or %NULL + * @natural_size: (out) (allow-none): location to store the natural size, or %NULL + * + * This is a convenience function for #GtkCellArea implementations + * to request size for cell renderers. It's important to use this + * function to request size and then use gtk_cell_area_inner_cell_area() + * at render and event time since this function will add padding + * around the cell for focus painting. + * + * Since: 3.0 + */ +void +gtk_cell_area_request_renderer (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkOrientation orientation, + GtkWidget *widget, + gint for_size, + gint *minimum_size, + gint *natural_size) +{ + GtkCellAreaPrivate *priv; + gint focus_line_width; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (minimum_size != NULL); + g_return_if_fail (natural_size != NULL); + + priv = area->priv; + + gtk_widget_style_get (widget, "focus-line-width", &focus_line_width, NULL); + + focus_line_width *= 2; + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + if (for_size < 0) + gtk_cell_renderer_get_preferred_width (renderer, widget, minimum_size, natural_size); + else + { + for_size = MAX (0, for_size - focus_line_width); + + gtk_cell_renderer_get_preferred_width_for_height (renderer, widget, for_size, + minimum_size, natural_size); + } + } + else /* GTK_ORIENTATION_VERTICAL */ + { + if (for_size < 0) + gtk_cell_renderer_get_preferred_height (renderer, widget, minimum_size, natural_size); + else + { + for_size = MAX (0, for_size - focus_line_width); + + gtk_cell_renderer_get_preferred_height_for_width (renderer, widget, for_size, + minimum_size, natural_size); + } + } + + *minimum_size += focus_line_width; + *natural_size += focus_line_width; +} diff --git a/gtk/gtkcellarea.h b/gtk/gtkcellarea.h new file mode 100644 index 0000000000..008b8bb10f --- /dev/null +++ b/gtk/gtkcellarea.h @@ -0,0 +1,463 @@ +/* gtkcellarea.h + * + * Copyright (C) 2010 Openismus GmbH + * + * Authors: + * Tristan Van Berkom <tristanvb@openismus.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only <gtk/gtk.h> can be included directly." +#endif + +#ifndef __GTK_CELL_AREA_H__ +#define __GTK_CELL_AREA_H__ + +#include <gtk/gtkcellrenderer.h> +#include <gtk/gtkwidget.h> +#include <gtk/gtktreemodel.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_CELL_AREA (gtk_cell_area_get_type ()) +#define GTK_CELL_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CELL_AREA, GtkCellArea)) +#define GTK_CELL_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_CELL_AREA, GtkCellAreaClass)) +#define GTK_IS_CELL_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CELL_AREA)) +#define GTK_IS_CELL_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_CELL_AREA)) +#define GTK_CELL_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_CELL_AREA, GtkCellAreaClass)) + +typedef struct _GtkCellArea GtkCellArea; +typedef struct _GtkCellAreaClass GtkCellAreaClass; +typedef struct _GtkCellAreaPrivate GtkCellAreaPrivate; +typedef struct _GtkCellAreaContext GtkCellAreaContext; + +/** + * GTK_CELL_AREA_WARN_INVALID_CELL_PROPERTY_ID: + * @object: the #GObject on which set_cell_property() or get_get_property() + * was called + * @property_id: the numeric id of the property + * @pspec: the #GParamSpec of the property + * + * This macro should be used to emit a standard warning about unexpected + * properties in set_cell_property() and get_cell_property() implementations. + */ +#define GTK_CELL_AREA_WARN_INVALID_CELL_PROPERTY_ID(object, property_id, pspec) \ + G_OBJECT_WARN_INVALID_PSPEC ((object), "cell property id", (property_id), (pspec)) + +/** + * GtkCellCallback: + * @renderer: the cell renderer to operate on + * @data: user-supplied data + * + * The type of the callback functions used for iterating over + * the cell renderers of a #GtkCellArea, see gtk_cell_area_foreach(). + * + * Return value: %TRUE to stop iterating over cells. + */ +typedef gboolean (*GtkCellCallback) (GtkCellRenderer *renderer, + gpointer data); + +/** + * GtkCellAllocCallback: + * @renderer: the cell renderer to operate on + * @cell_area: the area allocated to @renderer inside the rectangle provided to gtk_cell_area_foreach_alloc(). + * @cell_background: the background area for @renderer inside the background + * area provided to gtk_cell_area_foreach_alloc(). + * @data: user-supplied data + * + * The type of the callback functions used for iterating over + * the cell renderers and their allocated areas inside a #GtkCellArea, + * see gtk_cell_area_foreach_alloc(). + * + * Return value: %TRUE to stop iterating over cells. + */ +typedef gboolean (*GtkCellAllocCallback) (GtkCellRenderer *renderer, + const GdkRectangle *cell_area, + const GdkRectangle *cell_background, + gpointer data); + + +struct _GtkCellArea +{ + /*< private >*/ + GInitiallyUnowned parent_instance; + + GtkCellAreaPrivate *priv; +}; + + +/** + * GtkCellAreaClass: + * @add: adds a #GtkCellRenderer to the area. + * @remove: removes a #GtkCellRenderer from the area. + * @foreach: Calls the #GtkCellCallback function on every #GtkCellRenderer in the area + * with the provided user data until the callback returns %TRUE. + * @foreach_alloc: Calls the #GtkCellAllocCallback function on every #GtkCellRenderer in the area + * with the allocated area for the cell and the provided user data until the callback returns %TRUE. + * @event: Handle an event in the area, this is generally used to activate a cell + * at the event location for button events but can also be used to generically pass + * events to #GtkWidgets drawn onto the area. + * @render: Actually render the area's cells to the specified rectangle, @background_area + * should be correctly distributed to the cells coresponding background areas. + * @apply_attributes: Apply the cell attributes to the cells. This is implemented as a signal and + * generally #GtkCellArea subclasses dont need to implement this since it's handled by the base + * class but can be overridden to apply some custom attributes. + * @create_context: Creates and returns a class specific #GtkCellAreaContext to store cell + * alignment and allocation details for a said #GtkCellArea class. + * @copy_context: Creates a new #GtkCellAreaContext in the same state as the passed @context + * with any cell alignment data and allocations in tact. + * @get_request_mode: This allows an area to tell its layouting widget whether it prefers to + * be allocated in %GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH or %GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT mode. + * @get_preferred_width: Calculates the minimum and natural width of the area's cells + * with the current attributes applied while considering the particular layouting details + * of the said #GtkCellArea. While requests are performed over a series of rows, alignments + * and overall minimum and natural sizes should be stored in the corresponding #GtkCellAreaContext. + * @get_preferred_height_for_width: Calculates the minimum and natural height for the area + * if the passed @context would be allocated the given width. When implementing this virtual + * method it is safe to assume that @context has already stored the aligned cell widths + * for every #GtkTreeModel row that @context will be allocated for since this information + * was stored at #GtkCellAreaClass.get_preferred_width() time. This virtual method should + * also store any necessary alignments of cell heights for the case that the context is + * allocated a height. + * @get_preferred_height: Calculates the minimum and natural height of the area's cells + * with the current attributes applied. Essentially this is the same as + * #GtkCellAreaClass.get_preferred_width() only for areas that are being requested as + * %GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT. + * @get_preferred_width_for_height: Calculates the minimum and natural width for the area + * if the passed @context would be allocated the given height. The same as + * #GtkCellAreaClass.get_preferred_height_for_width() only for handling requests in the + * %GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT mode. + * @set_cell_property: This should be implemented to handle changes in child cell properties + * for a given #GtkCellRenderer that were previously installed on the #GtkCellAreaClass with + * gtk_cell_area_class_install_cell_property(). + * @get_cell_property: This should be implemented to report the values of child cell properties + * for a given child #GtkCellRenderer. + * @focus: This virtual method should be implemented to navigate focus from cell to cell + * inside the #GtkCellArea. The #GtkCellArea should move focus from cell to cell inside + * the area and return %FALSE if focus logically leaves the area with the following exceptions: + * When the area contains no activatable cells, the entire area recieves focus. Focus should not + * be given to cells that are actually "focus siblings" of other sibling cells + * (see gtk_cell_area_get_focus_from_sibling()). Focus is set by calling gtk_cell_area_set_focus_cell(). + * @is_activatable: Returns whether the #GtkCellArea can respond to #GtkCellAreaClass.activate(), + * usually this does not need to be implemented since the base class takes care of this however + * it can be enhanced if the #GtkCellArea subclass can handle activation in other ways than + * activating its #GtkCellRenderers. + * @activate: This is called when the layouting widget rendering the #GtkCellArea activates + * the focus cell (see gtk_cell_area_get_focus_cell()). + */ +struct _GtkCellAreaClass +{ + /*< private >*/ + GInitiallyUnownedClass parent_class; + + /*< public >*/ + + /* Basic methods */ + void (* add) (GtkCellArea *area, + GtkCellRenderer *renderer); + void (* remove) (GtkCellArea *area, + GtkCellRenderer *renderer); + void (* foreach) (GtkCellArea *area, + GtkCellCallback callback, + gpointer callback_data); + void (* foreach_alloc) (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + const GdkRectangle *background_area, + GtkCellAllocCallback callback, + gpointer callback_data); + gint (* event) (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + GdkEvent *event, + const GdkRectangle *cell_area, + GtkCellRendererState flags); + void (* render) (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + cairo_t *cr, + const GdkRectangle *background_area, + const GdkRectangle *cell_area, + GtkCellRendererState flags, + gboolean paint_focus); + void (* apply_attributes) (GtkCellArea *area, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gboolean is_expander, + gboolean is_expanded); + + /* Geometry */ + GtkCellAreaContext *(* create_context) (GtkCellArea *area); + GtkCellAreaContext *(* copy_context) (GtkCellArea *area, + GtkCellAreaContext *context); + GtkSizeRequestMode (* get_request_mode) (GtkCellArea *area); + void (* get_preferred_width) (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint *minimum_width, + gint *natural_width); + void (* get_preferred_height_for_width) (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height); + void (* get_preferred_height) (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint *minimum_height, + gint *natural_height); + void (* get_preferred_width_for_height) (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width); + + /* Cell Properties */ + void (* set_cell_property) (GtkCellArea *area, + GtkCellRenderer *renderer, + guint property_id, + const GValue *value, + GParamSpec *pspec); + void (* get_cell_property) (GtkCellArea *area, + GtkCellRenderer *renderer, + guint property_id, + GValue *value, + GParamSpec *pspec); + + /* Focus */ + gboolean (* focus) (GtkCellArea *area, + GtkDirectionType direction); + gboolean (* is_activatable) (GtkCellArea *area); + gboolean (* activate) (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + GtkCellRendererState flags, + gboolean edit_only); + + /*< private >*/ + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); + void (*_gtk_reserved5) (void); + void (*_gtk_reserved6) (void); + void (*_gtk_reserved7) (void); + void (*_gtk_reserved8) (void); +}; + +GType gtk_cell_area_get_type (void) G_GNUC_CONST; + +/* Basic methods */ +void gtk_cell_area_add (GtkCellArea *area, + GtkCellRenderer *renderer); +void gtk_cell_area_remove (GtkCellArea *area, + GtkCellRenderer *renderer); +gboolean gtk_cell_area_has_renderer (GtkCellArea *area, + GtkCellRenderer *renderer); +void gtk_cell_area_foreach (GtkCellArea *area, + GtkCellCallback callback, + gpointer callback_data); +void gtk_cell_area_foreach_alloc (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + const GdkRectangle *background_area, + GtkCellAllocCallback callback, + gpointer callback_data); +gint gtk_cell_area_event (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + GdkEvent *event, + const GdkRectangle *cell_area, + GtkCellRendererState flags); +void gtk_cell_area_render (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + cairo_t *cr, + const GdkRectangle *background_area, + const GdkRectangle *cell_area, + GtkCellRendererState flags, + gboolean paint_focus); +void gtk_cell_area_set_style_detail (GtkCellArea *area, + const gchar *detail); +G_CONST_RETURN gchar *gtk_cell_area_get_style_detail (GtkCellArea *area); + + +void gtk_cell_area_get_cell_allocation (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + GtkCellRenderer *renderer, + const GdkRectangle *cell_area, + GdkRectangle *allocation); +GtkCellRenderer *gtk_cell_area_get_cell_at_position (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + gint x, + gint y, + GdkRectangle *alloc_area); + +/* Geometry */ +GtkCellAreaContext *gtk_cell_area_create_context (GtkCellArea *area); +GtkCellAreaContext *gtk_cell_area_copy_context (GtkCellArea *area, + GtkCellAreaContext *context); +GtkSizeRequestMode gtk_cell_area_get_request_mode (GtkCellArea *area); +void gtk_cell_area_get_preferred_width (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint *minimum_size, + gint *natural_size); +void gtk_cell_area_get_preferred_height_for_width (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height); +void gtk_cell_area_get_preferred_height (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint *minimum_size, + gint *natural_size); +void gtk_cell_area_get_preferred_width_for_height (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + 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, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gboolean is_expander, + gboolean is_expanded); +void gtk_cell_area_attribute_connect (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *attribute, + gint column); +void gtk_cell_area_attribute_disconnect (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *attribute); + +/* Cell Properties */ +void gtk_cell_area_class_install_cell_property (GtkCellAreaClass *aclass, + guint property_id, + GParamSpec *pspec); +GParamSpec* gtk_cell_area_class_find_cell_property (GtkCellAreaClass *aclass, + const gchar *property_name); +GParamSpec** gtk_cell_area_class_list_cell_properties (GtkCellAreaClass *aclass, + guint *n_properties); +void gtk_cell_area_add_with_properties (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *first_prop_name, + ...) G_GNUC_NULL_TERMINATED; +void gtk_cell_area_cell_set (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *first_prop_name, + ...) G_GNUC_NULL_TERMINATED; +void gtk_cell_area_cell_get (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *first_prop_name, + ...) G_GNUC_NULL_TERMINATED; +void gtk_cell_area_cell_set_valist (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *first_property_name, + va_list var_args); +void gtk_cell_area_cell_get_valist (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *first_property_name, + va_list var_args); +void gtk_cell_area_cell_set_property (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *property_name, + const GValue *value); +void gtk_cell_area_cell_get_property (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *property_name, + GValue *value); + +/* Focus */ +gboolean gtk_cell_area_is_activatable (GtkCellArea *area); +gboolean gtk_cell_area_activate (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + GtkCellRendererState flags, + gboolean edit_only); +gboolean gtk_cell_area_focus (GtkCellArea *area, + GtkDirectionType direction); +void gtk_cell_area_set_focus_cell (GtkCellArea *area, + GtkCellRenderer *renderer); +GtkCellRenderer *gtk_cell_area_get_focus_cell (GtkCellArea *area); + + +/* Focus siblings */ +void gtk_cell_area_add_focus_sibling (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellRenderer *sibling); +void gtk_cell_area_remove_focus_sibling (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellRenderer *sibling); +gboolean gtk_cell_area_is_focus_sibling (GtkCellArea *area, + GtkCellRenderer *renderer, + 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 */ +GtkCellRenderer *gtk_cell_area_get_edited_cell (GtkCellArea *area); +GtkCellEditable *gtk_cell_area_get_edit_widget (GtkCellArea *area); +gboolean gtk_cell_area_activate_cell (GtkCellArea *area, + GtkWidget *widget, + GtkCellRenderer *renderer, + GdkEvent *event, + const GdkRectangle *cell_area, + GtkCellRendererState flags); +void gtk_cell_area_stop_editing (GtkCellArea *area, + gboolean canceled); + +/* Functions for area implementations */ + +/* Distinguish the inner cell area from the whole requested area including margins */ +void gtk_cell_area_inner_cell_area (GtkCellArea *area, + GtkWidget *widget, + const GdkRectangle *cell_area, + GdkRectangle *inner_area); + +/* Request the size of a cell while respecting the cell margins (requests are margin inclusive) */ +void gtk_cell_area_request_renderer (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkOrientation orientation, + GtkWidget *widget, + gint for_size, + gint *minimum_size, + gint *natural_size); + +G_END_DECLS + +#endif /* __GTK_CELL_AREA_H__ */ diff --git a/gtk/gtkcellareabox.c b/gtk/gtkcellareabox.c new file mode 100644 index 0000000000..e21bb0c9e9 --- /dev/null +++ b/gtk/gtkcellareabox.c @@ -0,0 +1,2089 @@ +/* gtkcellareabox.c + * + * Copyright (C) 2010 Openismus GmbH + * + * Authors: + * Tristan Van Berkom <tristanvb@openismus.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +/** + * SECTION:gtkcellareabox + * @Short_Description: A cell area that renders #GtkCellRenderers into a row or a column + * @Title: GtkCellAreaBox + * + * The #GtkCellAreaBox renders cell renderers into a row or a column depending on + * its #GtkOrientation. + * + * GtkCellAreaBox uses a notion of <emphasis>packing</emphasis>. Packing + * refers to adding cell renderers with reference to a particular position + * in a #GtkCellAreaBox. There are two reference positions: the + * <emphasis>start</emphasis> and the <emphasis>end</emphasis> of the box. + * When the #GtkCellAreaBox is oriented in the %GTK_ORIENTATION_VERTICAL orientation, + * the start is defined as the top of the box and the end is defined as the bottom. + * In the %GTK_ORIENTATION_HORIZONTAL orientation start is defined as the + * left side and the end is defined as the right side. + * + * Alignments of #GtkCellRenderers rendered in adjacent rows can be configured + * by configuring the #GtkCellAreaBox:align child cell property with + * gtk_cell_area_cell_set_property() or by specifying the "align" argument + * to gtk_cell_area_box_pack_start() and gtk_cell_area_box_pack_end(). + */ + +#include "config.h" +#include "gtkintl.h" +#include "gtkorientable.h" +#include "gtkcelllayout.h" +#include "gtkcellareabox.h" +#include "gtkcellareaboxcontext.h" +#include "gtkprivate.h" + + +/* GObjectClass */ +static void gtk_cell_area_box_finalize (GObject *object); +static void gtk_cell_area_box_dispose (GObject *object); +static void gtk_cell_area_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_cell_area_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +/* GtkCellAreaClass */ +static void gtk_cell_area_box_add (GtkCellArea *area, + GtkCellRenderer *renderer); +static void gtk_cell_area_box_remove (GtkCellArea *area, + GtkCellRenderer *renderer); +static void gtk_cell_area_box_foreach (GtkCellArea *area, + GtkCellCallback callback, + gpointer callback_data); +static void gtk_cell_area_box_foreach_alloc (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + const GdkRectangle *background_area, + GtkCellAllocCallback callback, + gpointer callback_data); +static void gtk_cell_area_box_set_cell_property (GtkCellArea *area, + GtkCellRenderer *renderer, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_cell_area_box_get_cell_property (GtkCellArea *area, + GtkCellRenderer *renderer, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static GtkCellAreaContext *gtk_cell_area_box_create_context (GtkCellArea *area); +static GtkCellAreaContext *gtk_cell_area_box_copy_context (GtkCellArea *area, + GtkCellAreaContext *context); +static GtkSizeRequestMode gtk_cell_area_box_get_request_mode (GtkCellArea *area); +static void gtk_cell_area_box_get_preferred_width (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint *minimum_width, + gint *natural_width); +static void gtk_cell_area_box_get_preferred_height (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint *minimum_height, + gint *natural_height); +static void gtk_cell_area_box_get_preferred_height_for_width (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height); +static void gtk_cell_area_box_get_preferred_width_for_height (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width); +static gboolean gtk_cell_area_box_focus (GtkCellArea *area, + GtkDirectionType direction); + +/* GtkCellLayoutIface */ +static void gtk_cell_area_box_cell_layout_init (GtkCellLayoutIface *iface); +static void gtk_cell_area_box_layout_pack_start (GtkCellLayout *cell_layout, + GtkCellRenderer *renderer, + gboolean expand); +static void gtk_cell_area_box_layout_pack_end (GtkCellLayout *cell_layout, + GtkCellRenderer *renderer, + gboolean expand); +static void gtk_cell_area_box_layout_reorder (GtkCellLayout *cell_layout, + GtkCellRenderer *renderer, + gint position); +static void gtk_cell_area_box_focus_changed (GtkCellArea *area, + GParamSpec *pspec, + GtkCellAreaBox *box); + + +/* CellInfo/CellGroup metadata handling and convenience functions */ +typedef struct { + GtkCellRenderer *renderer; + + guint expand : 1; /* Whether the cell expands */ + guint pack : 1; /* Whether the cell is packed from the start or end */ + guint align : 1; /* Whether to align this cell's position with adjacent rows */ +} CellInfo; + +typedef struct { + GList *cells; + + guint id : 8; + guint n_cells : 8; + guint expand_cells : 8; +} CellGroup; + +typedef struct { + GtkCellRenderer *renderer; + + gint position; + gint size; +} AllocatedCell; + +static CellInfo *cell_info_new (GtkCellRenderer *renderer, + GtkPackType pack, + gboolean expand, + gboolean align); +static void cell_info_free (CellInfo *info); +static gint cell_info_find (CellInfo *info, + GtkCellRenderer *renderer); + +static AllocatedCell *allocated_cell_new (GtkCellRenderer *renderer, + gint position, + gint size); +static void allocated_cell_free (AllocatedCell *cell); +static GList *list_consecutive_cells (GtkCellAreaBox *box); +static gint count_expand_groups (GtkCellAreaBox *box); +static void context_weak_notify (GtkCellAreaBox *box, + GtkCellAreaBoxContext *dead_context); +static void reset_contexts (GtkCellAreaBox *box); +static void init_context_groups (GtkCellAreaBox *box); +static void init_context_group (GtkCellAreaBox *box, + GtkCellAreaBoxContext *context); +static GSList *get_allocated_cells (GtkCellAreaBox *box, + GtkCellAreaBoxContext *context, + GtkWidget *widget, + gint width, + gint height); + + +struct _GtkCellAreaBoxPrivate +{ + GtkOrientation orientation; + + /* We hold on to the previously focused cell when navigating + * up and down in a horizontal box (or left and right on a vertical one) + * this way we always re-enter the last focused cell. */ + GtkCellRenderer *last_focus_cell; + gulong focus_cell_id; + + GList *cells; + GArray *groups; + + GSList *contexts; + + gint spacing; + + /* We hold on to the rtl state from a widget we are requested for + * so that we can navigate focus correctly */ + gboolean rtl; +}; + +enum { + PROP_0, + PROP_ORIENTATION, + PROP_SPACING +}; + +enum { + CELL_PROP_0, + CELL_PROP_EXPAND, + CELL_PROP_ALIGN, + CELL_PROP_PACK_TYPE +}; + +G_DEFINE_TYPE_WITH_CODE (GtkCellAreaBox, gtk_cell_area_box, GTK_TYPE_CELL_AREA, + G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT, + gtk_cell_area_box_cell_layout_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)); + +#define OPPOSITE_ORIENTATION(orientation) \ + ((orientation) == GTK_ORIENTATION_HORIZONTAL ? \ + GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL) + +static void +gtk_cell_area_box_init (GtkCellAreaBox *box) +{ + GtkCellAreaBoxPrivate *priv; + + box->priv = G_TYPE_INSTANCE_GET_PRIVATE (box, + GTK_TYPE_CELL_AREA_BOX, + GtkCellAreaBoxPrivate); + priv = box->priv; + + priv->orientation = GTK_ORIENTATION_HORIZONTAL; + priv->groups = g_array_new (FALSE, TRUE, sizeof (CellGroup)); + priv->cells = NULL; + priv->contexts = NULL; + priv->spacing = 0; + priv->rtl = FALSE; + + /* Watch whenever focus is given to a cell, even if it's not with keynav, + * this way we remember upon entry of the area where focus was last time around */ + priv->focus_cell_id = g_signal_connect (box, "notify::focus-cell", + G_CALLBACK (gtk_cell_area_box_focus_changed), box); +} + +static void +gtk_cell_area_box_class_init (GtkCellAreaBoxClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkCellAreaClass *area_class = GTK_CELL_AREA_CLASS (class); + + /* GObjectClass */ + object_class->finalize = gtk_cell_area_box_finalize; + object_class->dispose = gtk_cell_area_box_dispose; + object_class->set_property = gtk_cell_area_box_set_property; + 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->foreach = gtk_cell_area_box_foreach; + area_class->foreach_alloc = gtk_cell_area_box_foreach_alloc; + 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_context = gtk_cell_area_box_create_context; + area_class->copy_context = gtk_cell_area_box_copy_context; + area_class->get_request_mode = gtk_cell_area_box_get_request_mode; + area_class->get_preferred_width = gtk_cell_area_box_get_preferred_width; + area_class->get_preferred_height = gtk_cell_area_box_get_preferred_height; + area_class->get_preferred_height_for_width = gtk_cell_area_box_get_preferred_height_for_width; + area_class->get_preferred_width_for_height = gtk_cell_area_box_get_preferred_width_for_height; + + area_class->focus = gtk_cell_area_box_focus; + + /* Properties */ + g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation"); + + /** + * GtkCellAreaBox:spacing: + * + * The amount of space to reserve between cells. + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_SPACING, + g_param_spec_int ("spacing", + P_("Spacing"), + P_("Space which is inserted between cells"), + 0, + G_MAXINT, + 0, + GTK_PARAM_READWRITE)); + + /* Cell Properties */ + /** + * GtkCellAreaBox:expand: + * + * Whether the cell renderer should receive extra space when the area receives + * more than its natural size. + * + * Since: 3.0 + */ + gtk_cell_area_class_install_cell_property (area_class, + CELL_PROP_EXPAND, + g_param_spec_boolean + ("expand", + P_("Expand"), + P_("Whether the cell expands"), + FALSE, + GTK_PARAM_READWRITE)); + + /** + * GtkCellAreaBox:align: + * + * Whether the cell renderer should be aligned in adjacent rows. + * + * Since: 3.0 + */ + gtk_cell_area_class_install_cell_property (area_class, + CELL_PROP_ALIGN, + g_param_spec_boolean + ("align", + P_("Align"), + P_("Whether cell should align with adjacent rows"), + TRUE, + GTK_PARAM_READWRITE)); + + /** + * GtkCellAreaBox:pack-type: + * + * A GtkPackType indicating whether the cell renderer is packed with reference to the + * start or end of the area. + * + * Since: 3.0 + */ + gtk_cell_area_class_install_cell_property (area_class, + CELL_PROP_PACK_TYPE, + g_param_spec_enum + ("pack-type", + P_("Pack Type"), + P_("A GtkPackType indicating whether the cell is packed with " + "reference to the start or end of the cell area"), + GTK_TYPE_PACK_TYPE, GTK_PACK_START, + GTK_PARAM_READWRITE)); + + g_type_class_add_private (object_class, sizeof (GtkCellAreaBoxPrivate)); +} + + +/************************************************************* + * CellInfo/CellGroup basics and convenience functions * + *************************************************************/ +static CellInfo * +cell_info_new (GtkCellRenderer *renderer, + GtkPackType pack, + gboolean expand, + gboolean align) +{ + CellInfo *info = g_slice_new (CellInfo); + + info->renderer = g_object_ref_sink (renderer); + info->pack = pack; + info->expand = expand; + info->align = align; + + return info; +} + +static void +cell_info_free (CellInfo *info) +{ + g_object_unref (info->renderer); + + g_slice_free (CellInfo, info); +} + +static gint +cell_info_find (CellInfo *info, + GtkCellRenderer *renderer) +{ + return (info->renderer == renderer) ? 0 : -1; +} + +static AllocatedCell * +allocated_cell_new (GtkCellRenderer *renderer, + gint position, + gint size) +{ + AllocatedCell *cell = g_slice_new (AllocatedCell); + + cell->renderer = renderer; + cell->position = position; + cell->size = size; + + return cell; +} + +static void +allocated_cell_free (AllocatedCell *cell) +{ + g_slice_free (AllocatedCell, cell); +} + +static GList * +list_consecutive_cells (GtkCellAreaBox *box) +{ + GtkCellAreaBoxPrivate *priv = box->priv; + GList *l, *consecutive_cells = NULL, *pack_end_cells = NULL; + CellInfo *info; + + /* List cells in consecutive order taking their + * PACK_START/PACK_END options into account + */ + for (l = priv->cells; l; l = l->next) + { + info = l->data; + + if (info->pack == GTK_PACK_START) + consecutive_cells = g_list_prepend (consecutive_cells, info); + } + + for (l = priv->cells; l; l = l->next) + { + info = l->data; + + if (info->pack == GTK_PACK_END) + pack_end_cells = g_list_prepend (pack_end_cells, info); + } + + consecutive_cells = g_list_reverse (consecutive_cells); + consecutive_cells = g_list_concat (consecutive_cells, pack_end_cells); + + return consecutive_cells; +} + +static void +cell_groups_clear (GtkCellAreaBox *box) +{ + GtkCellAreaBoxPrivate *priv = box->priv; + gint i; + + for (i = 0; i < priv->groups->len; i++) + { + CellGroup *group = &g_array_index (priv->groups, CellGroup, i); + + g_list_free (group->cells); + } + + g_array_set_size (priv->groups, 0); +} + +static void +cell_groups_rebuild (GtkCellAreaBox *box) +{ + GtkCellAreaBoxPrivate *priv = box->priv; + CellGroup group = { 0, }; + CellGroup *group_ptr; + GList *cells, *l; + guint id = 0; + + cell_groups_clear (box); + + if (!priv->cells) + return; + + cells = list_consecutive_cells (box); + + /* First group is implied */ + g_array_append_val (priv->groups, group); + group_ptr = &g_array_index (priv->groups, CellGroup, id); + + for (l = cells; l; l = l->next) + { + CellInfo *info = l->data; + + /* A new group starts with any aligned cell, the first group is implied */ + if (info->align && l != cells) + { + memset (&group, 0x0, sizeof (CellGroup)); + group.id = ++id; + + g_array_append_val (priv->groups, group); + group_ptr = &g_array_index (priv->groups, CellGroup, id); + } + + group_ptr->cells = g_list_prepend (group_ptr->cells, info); + group_ptr->n_cells++; + + /* A group expands if it contains any expand cells */ + if (info->expand) + group_ptr->expand_cells++; + } + + g_list_free (cells); + + for (id = 0; id < priv->groups->len; id++) + { + group_ptr = &g_array_index (priv->groups, CellGroup, id); + + group_ptr->cells = g_list_reverse (group_ptr->cells); + } + + /* Contexts need to be updated with the new grouping information */ + init_context_groups (box); +} + +static gint +count_visible_cells (CellGroup *group, + gint *expand_cells) +{ + GList *l; + gint visible_cells = 0; + gint n_expand_cells = 0; + + for (l = group->cells; l; l = l->next) + { + CellInfo *info = l->data; + + if (gtk_cell_renderer_get_visible (info->renderer)) + { + visible_cells++; + + if (info->expand) + n_expand_cells++; + } + } + + if (expand_cells) + *expand_cells = n_expand_cells; + + return visible_cells; +} + +static gint +count_expand_groups (GtkCellAreaBox *box) +{ + GtkCellAreaBoxPrivate *priv = box->priv; + gint i; + gint expand_groups = 0; + + for (i = 0; i < priv->groups->len; i++) + { + CellGroup *group = &g_array_index (priv->groups, CellGroup, i); + + if (group->expand_cells > 0) + expand_groups++; + } + + return expand_groups; +} + +static void +context_weak_notify (GtkCellAreaBox *box, + GtkCellAreaBoxContext *dead_context) +{ + GtkCellAreaBoxPrivate *priv = box->priv; + + priv->contexts = g_slist_remove (priv->contexts, dead_context); +} + +static void +init_context_group (GtkCellAreaBox *box, + GtkCellAreaBoxContext *context) +{ + GtkCellAreaBoxPrivate *priv = box->priv; + gint *expand_groups, i; + + expand_groups = g_new (gboolean, priv->groups->len); + + for (i = 0; i < priv->groups->len; i++) + { + CellGroup *group = &g_array_index (priv->groups, CellGroup, i); + + expand_groups[i] = (group->expand_cells > 0); + } + + /* This call implies reseting the request info */ + gtk_cell_area_box_init_groups (context, priv->groups->len, expand_groups); + g_free (expand_groups); +} + +static void +init_context_groups (GtkCellAreaBox *box) +{ + GtkCellAreaBoxPrivate *priv = box->priv; + GSList *l; + + /* When the box's groups are reconstructed, contexts need to + * be reinitialized. + */ + for (l = priv->contexts; l; l = l->next) + { + GtkCellAreaBoxContext *context = l->data; + + init_context_group (box, context); + } +} + +static void +reset_contexts (GtkCellAreaBox *box) +{ + GtkCellAreaBoxPrivate *priv = box->priv; + GSList *l; + + /* When the box layout changes, contexts need to + * be reset and sizes for the box get requested again + */ + for (l = priv->contexts; l; l = l->next) + { + GtkCellAreaContext *context = l->data; + + gtk_cell_area_context_reset (context); + } +} + +/* Fall back on a completely unaligned dynamic allocation of cells + * when not allocated for the said orientation, alignment of cells + * is not done when each area gets a different size in the orientation + * of the box. + */ +static GSList * +allocate_cells_manually (GtkCellAreaBox *box, + GtkWidget *widget, + gint width, + gint height) +{ + GtkCellAreaBoxPrivate *priv = box->priv; + GList *cells, *l; + GSList *allocated_cells = NULL; + GtkRequestedSize *sizes; + gint i; + gint nvisible = 0, nexpand = 0, group_expand; + gint avail_size, extra_size, extra_extra, full_size; + gint position = 0, for_size; + gboolean rtl; + + if (!priv->cells) + return NULL; + + /* For vertical oriented boxes, we just let the cell renderers realign themselves for rtl */ + rtl = (priv->orientation == GTK_ORIENTATION_HORIZONTAL && + gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL); + + cells = list_consecutive_cells (box); + + /* Count the visible and expand cells */ + for (i = 0; i < priv->groups->len; i++) + { + CellGroup *group = &g_array_index (priv->groups, CellGroup, i); + + nvisible += count_visible_cells (group, &group_expand); + nexpand += group_expand; + } + + if (nvisible <= 0) + { + g_list_free (cells); + return NULL; + } + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + full_size = avail_size = width; + for_size = height; + } + else + { + full_size = avail_size = height; + for_size = width; + } + + /* Go ahead and collect the requests on the fly */ + sizes = g_new0 (GtkRequestedSize, nvisible); + for (l = cells, i = 0; l; l = l->next) + { + CellInfo *info = l->data; + + if (!gtk_cell_renderer_get_visible (info->renderer)) + continue; + + gtk_cell_area_request_renderer (GTK_CELL_AREA (box), info->renderer, + priv->orientation, + widget, for_size, + &sizes[i].minimum_size, + &sizes[i].natural_size); + + avail_size -= sizes[i].minimum_size; + + sizes[i].data = info; + + i++; + } + + /* Naturally distribute the allocation */ + avail_size -= (nvisible - 1) * priv->spacing; + if (avail_size > 0) + avail_size = gtk_distribute_natural_allocation (avail_size, nvisible, sizes); + else + avail_size = 0; + + /* Calculate/distribute expand for cells */ + if (nexpand > 0) + { + extra_size = avail_size / nexpand; + extra_extra = avail_size % nexpand; + } + else + extra_size = extra_extra = 0; + + /* Create the allocated cells */ + for (i = 0; i < nvisible; i++) + { + CellInfo *info = sizes[i].data; + AllocatedCell *cell; + + if (info->expand) + { + sizes[i].minimum_size += extra_size; + if (extra_extra) + { + sizes[i].minimum_size++; + extra_extra--; + } + } + + if (rtl) + cell = allocated_cell_new (info->renderer, + full_size - (position + sizes[i].minimum_size), + sizes[i].minimum_size); + else + cell = allocated_cell_new (info->renderer, position, sizes[i].minimum_size); + + allocated_cells = g_slist_prepend (allocated_cells, cell); + + position += sizes[i].minimum_size; + position += priv->spacing; + } + + g_free (sizes); + g_list_free (cells); + + /* Note it might not be important to reverse the list here at all, + * we have the correct positions, no need to allocate from left to right */ + return g_slist_reverse (allocated_cells); +} + +/* Returns an allocation for each cell in the orientation of the box, + * used in ->render()/->event() implementations to get a straight-forward + * list of allocated cells to operate on. + */ +static GSList * +get_allocated_cells (GtkCellAreaBox *box, + GtkCellAreaBoxContext *context, + GtkWidget *widget, + gint width, + gint height) +{ + GtkCellAreaBoxAllocation *group_allocs; + GtkCellArea *area = GTK_CELL_AREA (box); + GtkCellAreaBoxPrivate *priv = box->priv; + GList *cell_list; + GSList *allocated_cells = NULL; + gint i, j, n_allocs; + gint for_size, full_size; + gboolean rtl; + + group_allocs = gtk_cell_area_box_context_get_orientation_allocs (context, &n_allocs); + if (!group_allocs) + return allocate_cells_manually (box, widget, width, height); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + full_size = width; + for_size = height; + } + else + { + full_size = height; + for_size = width; + } + + /* For vertical oriented boxes, we just let the cell renderers realign themselves for rtl */ + rtl = (priv->orientation == GTK_ORIENTATION_HORIZONTAL && + gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL); + + for (i = 0; i < n_allocs; i++) + { + /* We dont always allocate all groups, sometimes the requested group has only invisible + * cells for every row, hence the usage of group_allocs[i].group_idx here + */ + CellGroup *group = &g_array_index (priv->groups, CellGroup, group_allocs[i].group_idx); + + /* Exception for single cell groups */ + if (group->n_cells == 1) + { + CellInfo *info = group->cells->data; + AllocatedCell *cell; + + if (rtl) + cell = allocated_cell_new (info->renderer, + full_size - (group_allocs[i].position + group_allocs[i].size), + group_allocs[i].size); + else + cell = allocated_cell_new (info->renderer, group_allocs[i].position, group_allocs[i].size); + + allocated_cells = g_slist_prepend (allocated_cells, cell); + } + else + { + GtkRequestedSize *sizes; + gint avail_size, position; + gint visible_cells, expand_cells; + gint extra_size, extra_extra; + + visible_cells = count_visible_cells (group, &expand_cells); + + /* If this row has no visible cells in this group, just + * skip the allocation */ + if (visible_cells == 0) + continue; + + /* Offset the allocation to the group position and allocate into + * the group's available size */ + position = group_allocs[i].position; + avail_size = group_allocs[i].size; + + sizes = g_new (GtkRequestedSize, visible_cells); + + for (j = 0, cell_list = group->cells; cell_list; cell_list = cell_list->next) + { + CellInfo *info = cell_list->data; + + if (!gtk_cell_renderer_get_visible (info->renderer)) + continue; + + gtk_cell_area_request_renderer (area, info->renderer, + priv->orientation, + widget, for_size, + &sizes[j].minimum_size, + &sizes[j].natural_size); + + sizes[j].data = info; + avail_size -= sizes[j].minimum_size; + + j++; + } + + /* Distribute cells naturally within the group */ + avail_size -= (visible_cells - 1) * priv->spacing; + if (avail_size > 0) + avail_size = gtk_distribute_natural_allocation (avail_size, visible_cells, sizes); + else + avail_size = 0; + + /* Calculate/distribute expand for cells */ + if (expand_cells > 0) + { + extra_size = avail_size / expand_cells; + extra_extra = avail_size % expand_cells; + } + else + extra_size = extra_extra = 0; + + /* Create the allocated cells (loop only over visible cells here) */ + for (j = 0; j < visible_cells; j++) + { + CellInfo *info = sizes[j].data; + AllocatedCell *cell; + + if (info->expand) + { + sizes[j].minimum_size += extra_size; + if (extra_extra) + { + sizes[j].minimum_size++; + extra_extra--; + } + } + + if (rtl) + cell = allocated_cell_new (info->renderer, + full_size - (position + sizes[j].minimum_size), + sizes[j].minimum_size); + else + cell = allocated_cell_new (info->renderer, position, sizes[j].minimum_size); + + allocated_cells = g_slist_prepend (allocated_cells, cell); + + position += sizes[j].minimum_size; + position += priv->spacing; + } + + g_free (sizes); + } + } + + /* Note it might not be important to reverse the list here at all, + * we have the correct positions, no need to allocate from left to right */ + return g_slist_reverse (allocated_cells); +} + + +static void +gtk_cell_area_box_focus_changed (GtkCellArea *area, + GParamSpec *pspec, + GtkCellAreaBox *box) +{ + if (gtk_cell_area_get_focus_cell (area)) + box->priv->last_focus_cell = gtk_cell_area_get_focus_cell (area); +} + +/************************************************************* + * GObjectClass * + *************************************************************/ +static void +gtk_cell_area_box_finalize (GObject *object) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (object); + GtkCellAreaBoxPrivate *priv = box->priv; + GSList *l; + + /* Unref/free the context list */ + for (l = priv->contexts; l; l = l->next) + g_object_weak_unref (G_OBJECT (l->data), (GWeakNotify)context_weak_notify, box); + + g_slist_free (priv->contexts); + priv->contexts = NULL; + + /* Free the cell grouping info */ + cell_groups_clear (box); + g_array_free (priv->groups, TRUE); + + G_OBJECT_CLASS (gtk_cell_area_box_parent_class)->finalize (object); +} + +static void +gtk_cell_area_box_dispose (GObject *object) +{ + G_OBJECT_CLASS (gtk_cell_area_box_parent_class)->dispose (object); +} + +static void +gtk_cell_area_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (object); + + switch (prop_id) + { + case PROP_ORIENTATION: + box->priv->orientation = g_value_get_enum (value); + + /* Notify that size needs to be requested again */ + reset_contexts (box); + break; + case PROP_SPACING: + gtk_cell_area_box_set_spacing (box, g_value_get_int (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_cell_area_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (object); + + switch (prop_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, box->priv->orientation); + break; + case PROP_SPACING: + g_value_set_int (value, gtk_cell_area_box_get_spacing (box)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/************************************************************* + * GtkCellAreaClass * + *************************************************************/ +static void +gtk_cell_area_box_add (GtkCellArea *area, + GtkCellRenderer *renderer) +{ + gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), + renderer, FALSE, TRUE); +} + +static void +gtk_cell_area_box_remove (GtkCellArea *area, + GtkCellRenderer *renderer) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxPrivate *priv = box->priv; + GList *node; + + if (priv->last_focus_cell == renderer) + priv->last_focus_cell = NULL; + + node = g_list_find_custom (priv->cells, renderer, + (GCompareFunc)cell_info_find); + + if (node) + { + CellInfo *info = node->data; + + cell_info_free (info); + + priv->cells = g_list_delete_link (priv->cells, node); + + /* Reconstruct cell groups */ + cell_groups_rebuild (box); + } + else + g_warning ("Trying to remove a cell renderer that is not present GtkCellAreaBox"); +} + +static void +gtk_cell_area_box_foreach (GtkCellArea *area, + GtkCellCallback callback, + gpointer callback_data) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxPrivate *priv = box->priv; + GList *list; + + for (list = priv->cells; list; list = list->next) + { + CellInfo *info = list->data; + + if (callback (info->renderer, callback_data)) + break; + } +} + +static void +gtk_cell_area_box_foreach_alloc (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + const GdkRectangle *background_area, + GtkCellAllocCallback callback, + gpointer callback_data) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxPrivate *priv = box->priv; + GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context); + GSList *allocated_cells, *l; + GdkRectangle cell_alloc, cell_background; + gboolean rtl; + + rtl = (priv->orientation == GTK_ORIENTATION_HORIZONTAL && + gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL); + + cell_alloc = *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_context, widget, + cell_area->width, cell_area->height); + + for (l = allocated_cells; l; l = l->next) + { + AllocatedCell *cell = l->data; + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + cell_alloc.x = cell_area->x + cell->position; + cell_alloc.width = cell->size; + } + else + { + cell_alloc.y = cell_area->y + cell->position; + cell_alloc.height = cell->size; + } + + /* Stop iterating over cells if they flow out of the render area, + * this can happen because the render area can actually be + * smaller than the requested area (treeview columns can + * be user resizable and can be resized to be smaller than + * the actual requested area). */ + if (cell_alloc.x > cell_area->x + cell_area->width || + cell_alloc.x + cell_alloc.width < cell_area->x || + cell_alloc.y > cell_area->y + cell_area->height) + break; + + /* Special case for the last cell (or first cell in rtl)... let the last cell consume + * the remaining space in the area (the last cell is allowed to consume the remaining + * space if the space given for rendering is actually larger than allocation, this can + * happen in the expander GtkTreeViewColumn where only the deepest depth column + * receives the allocation... shallow columns recieve more width). */ + if (!l->next) + { + if (rtl) + { + /* Fill the leading space for the first cell in the area (still last in the list) */ + cell_alloc.width = (cell_alloc.x - cell_area->x) + cell_alloc.width; + cell_alloc.x = cell_area->x; + } + else + { + cell_alloc.width = cell_area->x + cell_area->width - cell_alloc.x; + cell_alloc.height = cell_area->y + cell_area->height - cell_alloc.y; + } + } + else + { + /* If the cell we are rendering doesnt fit into the remaining space, clip it + * so that the underlying renderer has a chance to deal with it (for instance + * text renderers get a chance to ellipsize). + */ + if (cell_alloc.x + cell_alloc.width > cell_area->x + cell_area->width) + cell_alloc.width = cell_area->x + cell_area->width - cell_alloc.x; + + if (cell_alloc.y + cell_alloc.height > cell_area->y + cell_area->height) + cell_alloc.height = cell_area->y + cell_area->height - cell_alloc.y; + } + + /* Add portions of the background_area to the cell_alloc + * to create the cell_background */ + cell_background = cell_alloc; + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + if (l == allocated_cells) + { + /* Add the depth to the first cell */ + if (rtl) + { + cell_background.width += background_area->width - cell_area->width; + cell_background.x = background_area->x + background_area->width - cell_background.width; + } + else + { + cell_background.width += cell_area->x - background_area->x; + cell_background.x = background_area->x; + } + } + + if (l->next == NULL) + { + /* Grant this cell the remaining space */ + int remain = cell_background.x - background_area->x; + + if (rtl) + cell_background.x -= remain; + else + cell_background.width = background_area->width - remain; + } + + cell_background.y = background_area->y; + cell_background.height = background_area->height; + } + else + { + if (l == allocated_cells) + { + cell_background.height += cell_background.y - background_area->y; + cell_background.y = background_area->y; + } + + if (l->next == NULL) + cell_background.height = + background_area->height - (cell_background.y - background_area->y); + + cell_background.x = background_area->x; + cell_background.width = background_area->width; + } + + if (callback (cell->renderer, &cell_alloc, &cell_background, callback_data)) + break; + } + + g_slist_foreach (allocated_cells, (GFunc)allocated_cell_free, NULL); + g_slist_free (allocated_cells); +} + +static void +gtk_cell_area_box_set_cell_property (GtkCellArea *area, + GtkCellRenderer *renderer, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxPrivate *priv = box->priv; + GList *node; + CellInfo *info; + gboolean rebuild = FALSE; + gboolean val; + GtkPackType pack_type; + + node = g_list_find_custom (priv->cells, renderer, + (GCompareFunc)cell_info_find); + if (!node) + return; + + info = node->data; + + switch (prop_id) + { + case CELL_PROP_EXPAND: + val = g_value_get_boolean (value); + + if (info->expand != val) + { + info->expand = val; + rebuild = TRUE; + } + break; + + case CELL_PROP_ALIGN: + val = g_value_get_boolean (value); + + if (info->align != val) + { + info->align = val; + rebuild = TRUE; + } + break; + + case CELL_PROP_PACK_TYPE: + pack_type = g_value_get_enum (value); + + if (info->pack != pack_type) + { + info->pack = pack_type; + rebuild = TRUE; + } + break; + default: + GTK_CELL_AREA_WARN_INVALID_CELL_PROPERTY_ID (area, prop_id, pspec); + break; + } + + /* Groups need to be rebuilt */ + if (rebuild) + cell_groups_rebuild (box); +} + +static void +gtk_cell_area_box_get_cell_property (GtkCellArea *area, + GtkCellRenderer *renderer, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxPrivate *priv = box->priv; + GList *node; + CellInfo *info; + + node = g_list_find_custom (priv->cells, renderer, + (GCompareFunc)cell_info_find); + if (!node) + return; + + info = node->data; + + switch (prop_id) + { + case CELL_PROP_EXPAND: + g_value_set_boolean (value, info->expand); + break; + + case CELL_PROP_ALIGN: + g_value_set_boolean (value, info->align); + break; + + case CELL_PROP_PACK_TYPE: + g_value_set_enum (value, info->pack); + break; + default: + GTK_CELL_AREA_WARN_INVALID_CELL_PROPERTY_ID (area, prop_id, pspec); + break; + } +} + + +static GtkCellAreaContext * +gtk_cell_area_box_create_context (GtkCellArea *area) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxPrivate *priv = box->priv; + GtkCellAreaContext *context = + (GtkCellAreaContext *)g_object_new (GTK_TYPE_CELL_AREA_BOX_CONTEXT, + "area", area, NULL); + + priv->contexts = g_slist_prepend (priv->contexts, context); + + g_object_weak_ref (G_OBJECT (context), (GWeakNotify)context_weak_notify, box); + + /* Tell the new group about our cell layout */ + init_context_group (box, GTK_CELL_AREA_BOX_CONTEXT (context)); + + return context; +} + +static GtkCellAreaContext * +gtk_cell_area_box_copy_context (GtkCellArea *area, + GtkCellAreaContext *context) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxPrivate *priv = box->priv; + GtkCellAreaContext *copy = + (GtkCellAreaContext *)gtk_cell_area_box_context_copy (GTK_CELL_AREA_BOX (area), + GTK_CELL_AREA_BOX_CONTEXT (context)); + + priv->contexts = g_slist_prepend (priv->contexts, copy); + + g_object_weak_ref (G_OBJECT (copy), (GWeakNotify)context_weak_notify, box); + + return copy; +} + +static GtkSizeRequestMode +gtk_cell_area_box_get_request_mode (GtkCellArea *area) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxPrivate *priv = box->priv; + + return (priv->orientation) == GTK_ORIENTATION_HORIZONTAL ? + GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH : + GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT; +} + +static void +compute_size (GtkCellAreaBox *box, + GtkOrientation orientation, + GtkCellAreaBoxContext *context, + GtkWidget *widget, + gint for_size, + gint *minimum_size, + gint *natural_size) +{ + GtkCellAreaBoxPrivate *priv = box->priv; + GtkCellArea *area = GTK_CELL_AREA (box); + GList *list; + gint i; + gint min_size = 0; + gint nat_size = 0; + + for (i = 0; i < priv->groups->len; i++) + { + CellGroup *group = &g_array_index (priv->groups, CellGroup, i); + gint group_min_size = 0; + gint group_nat_size = 0; + + for (list = group->cells; list; list = list->next) + { + CellInfo *info = list->data; + gint renderer_min_size, renderer_nat_size; + + if (!gtk_cell_renderer_get_visible (info->renderer)) + continue; + + gtk_cell_area_request_renderer (area, info->renderer, orientation, widget, for_size, + &renderer_min_size, &renderer_nat_size); + + if (orientation == priv->orientation) + { + if (min_size > 0) + { + min_size += priv->spacing; + nat_size += priv->spacing; + } + + if (group_min_size > 0) + { + group_min_size += priv->spacing; + group_nat_size += priv->spacing; + } + + min_size += renderer_min_size; + nat_size += renderer_nat_size; + group_min_size += renderer_min_size; + group_nat_size += renderer_nat_size; + } + else + { + min_size = MAX (min_size, renderer_min_size); + nat_size = MAX (nat_size, renderer_nat_size); + group_min_size = MAX (group_min_size, renderer_min_size); + group_nat_size = MAX (group_nat_size, renderer_nat_size); + } + } + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + if (for_size < 0) + gtk_cell_area_box_context_push_group_width (context, group->id, group_min_size, group_nat_size); + else + gtk_cell_area_box_context_push_group_width_for_height (context, group->id, for_size, + group_min_size, group_nat_size); + } + else + { + if (for_size < 0) + gtk_cell_area_box_context_push_group_height (context, group->id, group_min_size, group_nat_size); + else + gtk_cell_area_box_context_push_group_height_for_width (context, group->id, for_size, + group_min_size, group_nat_size); + } + } + + *minimum_size = min_size; + *natural_size = nat_size; + + /* Update rtl state for focus navigation to work */ + priv->rtl = (priv->orientation == GTK_ORIENTATION_HORIZONTAL && + gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL); +} + +GtkRequestedSize * +get_group_sizes (GtkCellArea *area, + CellGroup *group, + GtkOrientation orientation, + GtkWidget *widget, + gint *n_sizes) +{ + GtkRequestedSize *sizes; + GList *l; + gint i; + + *n_sizes = count_visible_cells (group, NULL); + sizes = g_new (GtkRequestedSize, *n_sizes); + + for (l = group->cells, i = 0; l; l = l->next) + { + CellInfo *info = l->data; + + if (!gtk_cell_renderer_get_visible (info->renderer)) + continue; + + sizes[i].data = info; + + gtk_cell_area_request_renderer (area, info->renderer, + orientation, widget, -1, + &sizes[i].minimum_size, + &sizes[i].natural_size); + + i++; + } + + return sizes; +} + +static void +compute_group_size_for_opposing_orientation (GtkCellAreaBox *box, + CellGroup *group, + GtkWidget *widget, + gint for_size, + gint *minimum_size, + gint *natural_size) +{ + GtkCellAreaBoxPrivate *priv = box->priv; + GtkCellArea *area = GTK_CELL_AREA (box); + + /* Exception for single cell groups */ + if (group->n_cells == 1) + { + CellInfo *info = group->cells->data; + + gtk_cell_area_request_renderer (area, info->renderer, + OPPOSITE_ORIENTATION (priv->orientation), + widget, for_size, minimum_size, natural_size); + } + else + { + GtkRequestedSize *orientation_sizes; + CellInfo *info; + gint n_sizes, i; + gint avail_size = for_size; + gint extra_size, extra_extra; + gint min_size = 0, nat_size = 0; + + orientation_sizes = get_group_sizes (area, group, priv->orientation, widget, &n_sizes); + + /* First naturally allocate the cells in the group into the for_size */ + avail_size -= (n_sizes - 1) * priv->spacing; + for (i = 0; i < n_sizes; i++) + avail_size -= orientation_sizes[i].minimum_size; + + if (avail_size > 0) + avail_size = gtk_distribute_natural_allocation (avail_size, n_sizes, orientation_sizes); + else + avail_size = 0; + + /* Calculate/distribute expand for cells */ + if (group->expand_cells > 0) + { + extra_size = avail_size / group->expand_cells; + extra_extra = avail_size % group->expand_cells; + } + else + extra_size = extra_extra = 0; + + for (i = 0; i < n_sizes; i++) + { + gint cell_min, cell_nat; + + info = orientation_sizes[i].data; + + if (info->expand) + { + orientation_sizes[i].minimum_size += extra_size; + if (extra_extra) + { + orientation_sizes[i].minimum_size++; + extra_extra--; + } + } + + gtk_cell_area_request_renderer (area, info->renderer, + OPPOSITE_ORIENTATION (priv->orientation), + widget, + orientation_sizes[i].minimum_size, + &cell_min, &cell_nat); + + min_size = MAX (min_size, cell_min); + nat_size = MAX (nat_size, cell_nat); + } + + *minimum_size = min_size; + *natural_size = nat_size; + + g_free (orientation_sizes); + } +} + +static void +compute_size_for_opposing_orientation (GtkCellAreaBox *box, + GtkCellAreaBoxContext *context, + GtkWidget *widget, + gint for_size, + gint *minimum_size, + gint *natural_size) +{ + GtkCellAreaBoxPrivate *priv = box->priv; + CellGroup *group; + GtkRequestedSize *orientation_sizes; + gint n_groups, n_expand_groups, i; + gint avail_size = for_size; + gint extra_size, extra_extra; + gint min_size = 0, nat_size = 0; + + n_expand_groups = count_expand_groups (box); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + orientation_sizes = gtk_cell_area_box_context_get_widths (context, &n_groups); + else + orientation_sizes = gtk_cell_area_box_context_get_heights (context, &n_groups); + + /* First start by naturally allocating space among groups of cells */ + avail_size -= (n_groups - 1) * priv->spacing; + for (i = 0; i < n_groups; i++) + avail_size -= orientation_sizes[i].minimum_size; + + if (avail_size > 0) + avail_size = gtk_distribute_natural_allocation (avail_size, n_groups, orientation_sizes); + else + avail_size = 0; + + /* Calculate/distribute expand for groups */ + if (n_expand_groups > 0) + { + extra_size = avail_size / n_expand_groups; + extra_extra = avail_size % n_expand_groups; + } + else + extra_size = extra_extra = 0; + + /* Now we need to naturally allocate sizes for cells in each group + * and push the height-for-width for each group accordingly while accumulating + * the overall height-for-width for this row. + */ + for (i = 0; i < n_groups; i++) + { + gint group_min, group_nat; + gint group_idx = GPOINTER_TO_INT (orientation_sizes[i].data); + + group = &g_array_index (priv->groups, CellGroup, group_idx); + + if (group->expand_cells > 0) + { + orientation_sizes[i].minimum_size += extra_size; + if (extra_extra) + { + orientation_sizes[i].minimum_size++; + extra_extra--; + } + } + + /* Now we have the allocation for the group, request it's height-for-width */ + compute_group_size_for_opposing_orientation (box, group, widget, + orientation_sizes[i].minimum_size, + &group_min, &group_nat); + + min_size = MAX (min_size, group_min); + nat_size = MAX (nat_size, group_nat); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + gtk_cell_area_box_context_push_group_height_for_width (context, group_idx, for_size, + group_min, group_nat); + } + else + { + gtk_cell_area_box_context_push_group_width_for_height (context, group_idx, for_size, + group_min, group_nat); + } + } + + *minimum_size = min_size; + *natural_size = nat_size; + + g_free (orientation_sizes); + + /* Update rtl state for focus navigation to work */ + priv->rtl = (priv->orientation == GTK_ORIENTATION_HORIZONTAL && + gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL); +} + + + +static void +gtk_cell_area_box_get_preferred_width (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint *minimum_width, + gint *natural_width) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxContext *box_context; + gint min_width, nat_width; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (context)); + + box_context = GTK_CELL_AREA_BOX_CONTEXT (context); + + /* Compute the size of all renderers for current row data, + * bumping cell alignments in the context along the way */ + compute_size (box, GTK_ORIENTATION_HORIZONTAL, + box_context, widget, -1, &min_width, &nat_width); + + if (minimum_width) + *minimum_width = min_width; + + if (natural_width) + *natural_width = nat_width; +} + +static void +gtk_cell_area_box_get_preferred_height (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint *minimum_height, + gint *natural_height) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxContext *box_context; + gint min_height, nat_height; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (context)); + + box_context = GTK_CELL_AREA_BOX_CONTEXT (context); + + /* Compute the size of all renderers for current row data, + * bumping cell alignments in the context along the way */ + compute_size (box, GTK_ORIENTATION_VERTICAL, + box_context, widget, -1, &min_height, &nat_height); + + if (minimum_height) + *minimum_height = min_height; + + if (natural_height) + *natural_height = nat_height; +} + +static void +gtk_cell_area_box_get_preferred_height_for_width (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxContext *box_context; + GtkCellAreaBoxPrivate *priv; + gint min_height, nat_height; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (context)); + + box_context = GTK_CELL_AREA_BOX_CONTEXT (context); + priv = box->priv; + + if (priv->orientation == GTK_ORIENTATION_VERTICAL) + { + /* Add up vertical requests of height for width and push the overall + * cached sizes for alignments */ + compute_size (box, priv->orientation, box_context, widget, width, &min_height, &nat_height); + } + else + { + /* Juice: virtually allocate cells into the for_width using the + * alignments and then return the overall height for that width, and cache it */ + compute_size_for_opposing_orientation (box, box_context, widget, width, &min_height, &nat_height); + } + + if (minimum_height) + *minimum_height = min_height; + + if (natural_height) + *natural_height = nat_height; +} + +static void +gtk_cell_area_box_get_preferred_width_for_height (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxContext *box_context; + GtkCellAreaBoxPrivate *priv; + gint min_width, nat_width; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (context)); + + box_context = GTK_CELL_AREA_BOX_CONTEXT (context); + priv = box->priv; + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + /* Add up horizontal requests of width for height and push the overall + * cached sizes for alignments */ + compute_size (box, priv->orientation, box_context, widget, height, &min_width, &nat_width); + } + else + { + /* Juice: horizontally allocate cells into the for_height using the + * alignments and then return the overall width for that height, and cache it */ + compute_size_for_opposing_orientation (box, box_context, widget, height, &min_width, &nat_width); + } + + if (minimum_width) + *minimum_width = min_width; + + if (natural_width) + *natural_width = nat_width; +} + +enum { + FOCUS_NONE, + FOCUS_PREV, + FOCUS_NEXT, + FOCUS_LAST_CELL +}; + +static gboolean +gtk_cell_area_box_focus (GtkCellArea *area, + GtkDirectionType direction) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxPrivate *priv = box->priv; + gint cycle = FOCUS_NONE; + gboolean cycled_focus = FALSE; + GtkCellRenderer *focus_cell; + + focus_cell = gtk_cell_area_get_focus_cell (area); + + /* Special case, when there is no activatable cell, focus + * is painted around the entire area... in this case we + * let focus leave the area directly. + */ + if (focus_cell && !gtk_cell_area_is_activatable (area)) + { + gtk_cell_area_set_focus_cell (area, NULL); + return FALSE; + } + + switch (direction) + { + case GTK_DIR_TAB_FORWARD: + cycle = priv->rtl ? FOCUS_PREV : FOCUS_NEXT; + break; + case GTK_DIR_TAB_BACKWARD: + cycle = priv->rtl ? FOCUS_NEXT : FOCUS_PREV; + break; + case GTK_DIR_UP: + if (priv->orientation == GTK_ORIENTATION_VERTICAL || !priv->last_focus_cell) + cycle = FOCUS_PREV; + else if (!focus_cell) + cycle = FOCUS_LAST_CELL; + break; + case GTK_DIR_DOWN: + if (priv->orientation == GTK_ORIENTATION_VERTICAL || !priv->last_focus_cell) + cycle = FOCUS_NEXT; + else if (!focus_cell) + cycle = FOCUS_LAST_CELL; + break; + case GTK_DIR_LEFT: + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL || !priv->last_focus_cell) + cycle = priv->rtl ? FOCUS_NEXT : FOCUS_PREV; + else if (!focus_cell) + cycle = FOCUS_LAST_CELL; + break; + case GTK_DIR_RIGHT: + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL || !priv->last_focus_cell) + cycle = priv->rtl ? FOCUS_PREV : FOCUS_NEXT; + else if (!focus_cell) + cycle = FOCUS_LAST_CELL; + break; + default: + break; + } + + if (cycle == FOCUS_LAST_CELL) + { + gtk_cell_area_set_focus_cell (area, priv->last_focus_cell); + cycled_focus = TRUE; + } + else if (cycle != FOCUS_NONE) + { + gboolean found_cell = FALSE; + GList *list; + gint i; + + /* If there is no focused cell, focus on the first (or last) one in the list */ + if (!focus_cell) + found_cell = TRUE; + + for (i = (cycle == FOCUS_NEXT) ? 0 : priv->groups->len -1; + cycled_focus == FALSE && i >= 0 && i < priv->groups->len; + i = (cycle == FOCUS_NEXT) ? i + 1 : i - 1) + { + CellGroup *group = &g_array_index (priv->groups, CellGroup, i); + + for (list = (cycle == FOCUS_NEXT) ? g_list_first (group->cells) : g_list_last (group->cells); + cycled_focus == FALSE && list; list = (cycle == FOCUS_NEXT) ? list->next : list->prev) + { + CellInfo *info = list->data; + + if (info->renderer == focus_cell) + found_cell = TRUE; + else if (found_cell && /* Dont give focus to cells that are siblings to a focus cell */ + gtk_cell_area_get_focus_from_sibling (area, info->renderer) == NULL) + { + gtk_cell_area_set_focus_cell (area, info->renderer); + cycled_focus = TRUE; + } + } + } + } + + if (!cycled_focus) + gtk_cell_area_set_focus_cell (area, NULL); + + return cycled_focus; +} + + +/************************************************************* + * GtkCellLayoutIface * + *************************************************************/ +static void +gtk_cell_area_box_cell_layout_init (GtkCellLayoutIface *iface) +{ + iface->pack_start = gtk_cell_area_box_layout_pack_start; + iface->pack_end = gtk_cell_area_box_layout_pack_end; + iface->reorder = gtk_cell_area_box_layout_reorder; +} + +static void +gtk_cell_area_box_layout_pack_start (GtkCellLayout *cell_layout, + GtkCellRenderer *renderer, + gboolean expand) +{ + gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (cell_layout), renderer, expand, TRUE); +} + +static void +gtk_cell_area_box_layout_pack_end (GtkCellLayout *cell_layout, + GtkCellRenderer *renderer, + gboolean expand) +{ + gtk_cell_area_box_pack_end (GTK_CELL_AREA_BOX (cell_layout), renderer, expand, TRUE); +} + +static void +gtk_cell_area_box_layout_reorder (GtkCellLayout *cell_layout, + GtkCellRenderer *renderer, + gint position) +{ + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (cell_layout); + GtkCellAreaBoxPrivate *priv = box->priv; + GList *node; + CellInfo *info; + + node = g_list_find_custom (priv->cells, renderer, + (GCompareFunc)cell_info_find); + + if (node) + { + info = node->data; + + priv->cells = g_list_delete_link (priv->cells, node); + priv->cells = g_list_insert (priv->cells, info, position); + + cell_groups_rebuild (box); + } +} + +/************************************************************* + * API * + *************************************************************/ +/** + * gtk_cell_area_box_new: + * + * Creates a new #GtkCellAreaBox. + * + * Return value: a newly created #GtkCellAreaBox + */ +GtkCellArea * +gtk_cell_area_box_new (void) +{ + return (GtkCellArea *)g_object_new (GTK_TYPE_CELL_AREA_BOX, NULL); +} + +/** + * gtk_cell_area_box_pack_start: + * @box: a #GtkCellAreaBox + * @renderer: the #GtkCellRenderer to add + * @expand: whether @renderer should receive extra space when the area receives + * more than its natural size + * @align: whether @renderer should be aligned in adjacent rows. + * + * Adds @renderer to @box, packed with reference to the start of @box. + * + * The @renderer is packed after any other #GtkCellRenderer packed with reference + * to the start of @box. + * + * Since: 3.0 + */ +void +gtk_cell_area_box_pack_start (GtkCellAreaBox *box, + GtkCellRenderer *renderer, + gboolean expand, + gboolean align) +{ + GtkCellAreaBoxPrivate *priv; + CellInfo *info; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX (box)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + + priv = box->priv; + + if (g_list_find_custom (priv->cells, renderer, + (GCompareFunc)cell_info_find)) + { + g_warning ("Refusing to add the same cell renderer to a GtkCellAreaBox twice"); + return; + } + + info = cell_info_new (renderer, GTK_PACK_START, expand, align); + + priv->cells = g_list_append (priv->cells, info); + + cell_groups_rebuild (box); +} + +/** + * gtk_cell_area_box_pack_end: + * @box: a #GtkCellAreaBox + * @renderer: the #GtkCellRenderer to add + * @expand: whether @renderer should receive extra space when the area receives + * more than its natural size + * @align: whether @renderer should be aligned in adjacent rows. + * + * Adds @renderer to @box, packed with reference to the end of @box. + * + * The @renderer is packed after (away from end of) any other #GtkCellRenderer + * packed with reference to the end of @box. + * + * Since: 3.0 + */ +void +gtk_cell_area_box_pack_end (GtkCellAreaBox *box, + GtkCellRenderer *renderer, + gboolean expand, + gboolean align) +{ + GtkCellAreaBoxPrivate *priv; + CellInfo *info; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX (box)); + g_return_if_fail (GTK_IS_CELL_RENDERER (renderer)); + + priv = box->priv; + + if (g_list_find_custom (priv->cells, renderer, + (GCompareFunc)cell_info_find)) + { + g_warning ("Refusing to add the same cell renderer to a GtkCellArea twice"); + return; + } + + info = cell_info_new (renderer, GTK_PACK_END, expand, align); + + priv->cells = g_list_append (priv->cells, info); + + cell_groups_rebuild (box); +} + +/** + * gtk_cell_area_box_get_spacing: + * @box: a #GtkCellAreaBox + * + * Gets the spacing added between cell renderers. + * + * Return value: the space added between cell renderers in @box. + * + * Since: 3.0 + */ +gint +gtk_cell_area_box_get_spacing (GtkCellAreaBox *box) +{ + g_return_val_if_fail (GTK_IS_CELL_AREA_BOX (box), 0); + + return box->priv->spacing; +} + +/** + * gtk_cell_area_box_set_spacing: + * @box: a #GtkCellAreaBox + * @spacing: the space to add between #GtkCellRenderers + * + * Sets the spacing to add between cell renderers in @box. + * + * Since: 3.0 + */ +void +gtk_cell_area_box_set_spacing (GtkCellAreaBox *box, + gint spacing) +{ + GtkCellAreaBoxPrivate *priv; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX (box)); + + priv = box->priv; + + if (priv->spacing != spacing) + { + priv->spacing = spacing; + + g_object_notify (G_OBJECT (box), "spacing"); + + /* Notify that size needs to be requested again */ + reset_contexts (box); + } +} diff --git a/gtk/gtkcellareabox.h b/gtk/gtkcellareabox.h new file mode 100644 index 0000000000..a6d3a061bb --- /dev/null +++ b/gtk/gtkcellareabox.h @@ -0,0 +1,84 @@ +/* gtkcellareabox.h + * + * Copyright (C) 2010 Openismus GmbH + * + * Authors: + * Tristan Van Berkom <tristanvb@openismus.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only <gtk/gtk.h> can be included directly." +#endif + +#ifndef __GTK_CELL_AREA_BOX_H__ +#define __GTK_CELL_AREA_BOX_H__ + +#include <gtk/gtkcellarea.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_CELL_AREA_BOX (gtk_cell_area_box_get_type ()) +#define GTK_CELL_AREA_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CELL_AREA_BOX, GtkCellAreaBox)) +#define GTK_CELL_AREA_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_CELL_AREA_BOX, GtkCellAreaBoxClass)) +#define GTK_IS_CELL_AREA_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CELL_AREA_BOX)) +#define GTK_IS_CELL_AREA_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_CELL_AREA_BOX)) +#define GTK_CELL_AREA_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_CELL_AREA_BOX, GtkCellAreaBoxClass)) + +typedef struct _GtkCellAreaBox GtkCellAreaBox; +typedef struct _GtkCellAreaBoxClass GtkCellAreaBoxClass; +typedef struct _GtkCellAreaBoxPrivate GtkCellAreaBoxPrivate; + +struct _GtkCellAreaBox +{ + /*< private >*/ + GtkCellArea parent_instance; + + GtkCellAreaBoxPrivate *priv; +}; + +struct _GtkCellAreaBoxClass +{ + /*< private >*/ + GtkCellAreaClass parent_class; + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); +}; + +GType gtk_cell_area_box_get_type (void) G_GNUC_CONST; + +GtkCellArea *gtk_cell_area_box_new (void); +void gtk_cell_area_box_pack_start (GtkCellAreaBox *box, + GtkCellRenderer *renderer, + gboolean expand, + gboolean align); +void gtk_cell_area_box_pack_end (GtkCellAreaBox *box, + GtkCellRenderer *renderer, + gboolean expand, + gboolean align); +gint gtk_cell_area_box_get_spacing (GtkCellAreaBox *box); +void gtk_cell_area_box_set_spacing (GtkCellAreaBox *box, + gint spacing); + + +G_END_DECLS + +#endif /* __GTK_CELL_AREA_BOX_H__ */ diff --git a/gtk/gtkcellareaboxcontext.c b/gtk/gtkcellareaboxcontext.c new file mode 100644 index 0000000000..2c0fa22ac7 --- /dev/null +++ b/gtk/gtkcellareaboxcontext.c @@ -0,0 +1,853 @@ +/* gtkcellareaboxcontext.c + * + * Copyright (C) 2010 Openismus GmbH + * + * Authors: + * Tristan Van Berkom <tristanvb@openismus.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "gtkintl.h" +#include "gtkcellareabox.h" +#include "gtkcellareaboxcontext.h" +#include "gtkorientable.h" + +/* GObjectClass */ +static void gtk_cell_area_box_context_finalize (GObject *object); + +/* GtkCellAreaContextClass */ +static void gtk_cell_area_box_context_reset (GtkCellAreaContext *context); +static void gtk_cell_area_box_context_allocate (GtkCellAreaContext *context, + gint width, + gint height); +static void gtk_cell_area_box_context_get_preferred_height_for_width (GtkCellAreaContext *context, + gint width, + gint *minimum_height, + gint *natural_height); +static void gtk_cell_area_box_context_get_preferred_width_for_height (GtkCellAreaContext *context, + gint height, + gint *minimum_width, + gint *natural_width); + + + +/* Internal functions */ +static void gtk_cell_area_box_context_sum (GtkCellAreaBoxContext *context, + GtkOrientation orientation, + gint for_size, + gint *minimum_size, + gint *natural_size); +static void free_cache_array (GArray *array); +static GArray *group_array_new (GtkCellAreaBoxContext *context); +static GArray *get_array (GtkCellAreaBoxContext *context, + GtkOrientation orientation, + gint for_size); +static gboolean group_expands (GtkCellAreaBoxContext *context, + gint group_idx); +static gint count_expand_groups (GtkCellAreaBoxContext *context); + + +/* CachedSize management */ +typedef struct { + gint min_size; + gint nat_size; +} CachedSize; + +struct _GtkCellAreaBoxContextPrivate +{ + /* Table of per renderer CachedSizes */ + GArray *base_widths; + GArray *base_heights; + + /* Table of per height/width hash tables of per renderer CachedSizes */ + GHashTable *widths; + GHashTable *heights; + + /* Whether each group expands */ + gboolean *expand; + + /* Allocation info for this context if any */ + gint alloc_width; + gint alloc_height; + gint n_orientation_allocs; + GtkCellAreaBoxAllocation *orientation_allocs; +}; + +G_DEFINE_TYPE (GtkCellAreaBoxContext, gtk_cell_area_box_context, GTK_TYPE_CELL_AREA_CONTEXT); + +static void +free_cache_array (GArray *array) +{ + g_array_free (array, TRUE); +} + +static GArray * +group_array_new (GtkCellAreaBoxContext *context) +{ + GtkCellAreaBoxContextPrivate *priv = context->priv; + GArray *group_array; + + group_array = g_array_new (FALSE, TRUE, sizeof (CachedSize)); + g_array_set_size (group_array, priv->base_widths->len); + + return group_array; +} + +static GArray * +get_array (GtkCellAreaBoxContext *context, + GtkOrientation orientation, + gint for_size) +{ + GtkCellAreaBoxContextPrivate *priv = context->priv; + GArray *array; + + if (for_size < 0) + { + if (orientation == GTK_ORIENTATION_HORIZONTAL) + array = priv->base_widths; + else + array = priv->base_heights; + } + else + { + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + array = g_hash_table_lookup (priv->widths, GINT_TO_POINTER (for_size)); + + if (!array) + array = priv->base_widths; + } + else + { + array = g_hash_table_lookup (priv->heights, GINT_TO_POINTER (for_size)); + + if (!array) + array = priv->base_heights; + } + } + + return array; +} + +static gboolean +group_expands (GtkCellAreaBoxContext *context, + gint group_idx) +{ + GtkCellAreaBoxContextPrivate *priv = context->priv; + + g_assert (group_idx >= 0 && group_idx < priv->base_widths->len); + + return priv->expand[group_idx]; +} + +static gint +count_expand_groups (GtkCellAreaBoxContext *context) +{ + GtkCellAreaBoxContextPrivate *priv = context->priv; + gint i, expand = 0; + + for (i = 0; i < priv->base_widths->len; i++) + { + if (priv->expand[i]) + expand++; + } + + return expand; +} + +static void +gtk_cell_area_box_context_init (GtkCellAreaBoxContext *box_context) +{ + GtkCellAreaBoxContextPrivate *priv; + + box_context->priv = G_TYPE_INSTANCE_GET_PRIVATE (box_context, + GTK_TYPE_CELL_AREA_BOX_CONTEXT, + GtkCellAreaBoxContextPrivate); + priv = box_context->priv; + + priv->base_widths = g_array_new (FALSE, TRUE, sizeof (CachedSize)); + priv->base_heights = g_array_new (FALSE, TRUE, sizeof (CachedSize)); + + priv->widths = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify)free_cache_array); + priv->heights = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify)free_cache_array); + + priv->alloc_width = 0; + priv->alloc_height = 0; + priv->orientation_allocs = NULL; + priv->n_orientation_allocs = 0; +} + +static void +gtk_cell_area_box_context_class_init (GtkCellAreaBoxContextClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkCellAreaContextClass *context_class = GTK_CELL_AREA_CONTEXT_CLASS (class); + + /* GObjectClass */ + object_class->finalize = gtk_cell_area_box_context_finalize; + + context_class->reset = gtk_cell_area_box_context_reset; + context_class->allocate = gtk_cell_area_box_context_allocate; + context_class->get_preferred_height_for_width = gtk_cell_area_box_context_get_preferred_height_for_width; + context_class->get_preferred_width_for_height = gtk_cell_area_box_context_get_preferred_width_for_height; + + g_type_class_add_private (object_class, sizeof (GtkCellAreaBoxContextPrivate)); +} + +/************************************************************* + * GObjectClass * + *************************************************************/ +static void +gtk_cell_area_box_context_finalize (GObject *object) +{ + GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (object); + GtkCellAreaBoxContextPrivate *priv = box_context->priv; + + g_array_free (priv->base_widths, TRUE); + g_array_free (priv->base_heights, TRUE); + g_hash_table_destroy (priv->widths); + g_hash_table_destroy (priv->heights); + + g_free (priv->orientation_allocs); + g_free (priv->expand); + + G_OBJECT_CLASS (gtk_cell_area_box_context_parent_class)->finalize (object); +} + +/************************************************************* + * GtkCellAreaContextClass * + *************************************************************/ +static void +gtk_cell_area_box_context_reset (GtkCellAreaContext *context) +{ + GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context); + GtkCellAreaBoxContextPrivate *priv = box_context->priv; + CachedSize *size; + gint i; + + for (i = 0; i < priv->base_widths->len; i++) + { + size = &g_array_index (priv->base_widths, CachedSize, i); + + size->min_size = 0; + size->nat_size = 0; + + size = &g_array_index (priv->base_heights, CachedSize, i); + + size->min_size = 0; + size->nat_size = 0; + } + + /* Reset context sizes as well */ + g_hash_table_remove_all (priv->widths); + g_hash_table_remove_all (priv->heights); + + /* Clear the allocation */ + g_free (priv->orientation_allocs); + priv->orientation_allocs = NULL; + priv->n_orientation_allocs = 0; + + GTK_CELL_AREA_CONTEXT_CLASS + (gtk_cell_area_box_context_parent_class)->reset (context); +} + +static GtkRequestedSize * +gtk_cell_area_box_context_get_requests (GtkCellAreaBoxContext *box_context, + GtkOrientation orientation, + gint for_size, + gint *n_requests) +{ + GtkCellAreaBoxContextPrivate *priv; + GtkRequestedSize *requests; + GArray *array; + CachedSize *size; + gint visible_groups = 0; + gint i, j; + + g_return_val_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context), NULL); + + priv = box_context->priv; + array = get_array (box_context, orientation, for_size); + + for (i = 0; i < array->len; i++) + { + size = &g_array_index (array, CachedSize, i); + + if (size->nat_size > 0) + visible_groups++; + } + + requests = g_new (GtkRequestedSize, visible_groups); + + for (j = 0, i = 0; i < array->len; i++) + { + size = &g_array_index (array, CachedSize, i); + + if (size->nat_size > 0) + { + requests[j].data = GINT_TO_POINTER (i); + requests[j].minimum_size = size->min_size; + requests[j].natural_size = size->nat_size; + j++; + } + } + + if (n_requests) + *n_requests = visible_groups; + + return requests; +} + +static GtkCellAreaBoxAllocation * +allocate_for_orientation (GtkCellAreaBoxContext *context, + GtkOrientation orientation, + gint spacing, + gint size, + gint for_size, + gint *n_allocs) +{ + GtkCellAreaBoxAllocation *allocs; + GtkRequestedSize *sizes; + GArray *array; + gint n_expand_groups = 0; + gint i, n_groups, position; + gint extra_size, extra_extra; + gint avail_size = size; + + sizes = gtk_cell_area_box_context_get_requests (context, orientation, for_size, &n_groups); + array = get_array (context, orientation, for_size); + n_expand_groups = count_expand_groups (context); + + /* First start by naturally allocating space among groups */ + avail_size -= (n_groups - 1) * spacing; + for (i = 0; i < n_groups; i++) + avail_size -= sizes[i].minimum_size; + + if (avail_size > 0) + avail_size = gtk_distribute_natural_allocation (avail_size, n_groups, sizes); + else + avail_size = 0; + + /* Calculate/distribute expand for groups */ + if (n_expand_groups > 0) + { + extra_size = avail_size / n_expand_groups; + extra_extra = avail_size % n_expand_groups; + } + else + extra_size = extra_extra = 0; + + allocs = g_new (GtkCellAreaBoxAllocation, n_groups); + + for (position = 0, i = 0; i < n_groups; i++) + { + allocs[i].group_idx = GPOINTER_TO_INT (sizes[i].data); + allocs[i].position = position; + allocs[i].size = sizes[i].minimum_size; + + if (group_expands (context, allocs[i].group_idx)) + { + allocs[i].size += extra_size; + if (extra_extra) + { + allocs[i].size++; + extra_extra--; + } + } + + position += allocs[i].size; + position += spacing; + } + + if (n_allocs) + *n_allocs = n_groups; + + g_free (sizes); + + return allocs; +} + +static void +gtk_cell_area_box_context_allocate (GtkCellAreaContext *context, + gint width, + gint height) +{ + GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context); + GtkCellAreaBoxContextPrivate *priv = box_context->priv; + GtkCellArea *area; + GtkOrientation orientation; + gint spacing; + + area = gtk_cell_area_context_get_area (context); + orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (area)); + spacing = gtk_cell_area_box_get_spacing (GTK_CELL_AREA_BOX (area)); + + g_free (priv->orientation_allocs); + priv->orientation_allocs = NULL; + priv->n_orientation_allocs = 0; + + if (orientation == GTK_ORIENTATION_HORIZONTAL && width > 0) + priv->orientation_allocs = allocate_for_orientation (box_context, orientation, + spacing, width, height, + &priv->n_orientation_allocs); + else if (orientation == GTK_ORIENTATION_VERTICAL && height > 0) + priv->orientation_allocs = allocate_for_orientation (box_context, orientation, + spacing, height, width, + &priv->n_orientation_allocs); + + GTK_CELL_AREA_CONTEXT_CLASS (gtk_cell_area_box_context_parent_class)->allocate (context, width, height); +} + +static void +gtk_cell_area_box_context_sum (GtkCellAreaBoxContext *context, + GtkOrientation orientation, + gint for_size, + gint *minimum_size, + gint *natural_size) +{ + GtkCellArea *area; + GtkOrientation box_orientation; + GArray *array; + gint spacing, i; + gint min_size = 0, nat_size = 0; + + area = gtk_cell_area_context_get_area (GTK_CELL_AREA_CONTEXT (context)); + spacing = gtk_cell_area_box_get_spacing (GTK_CELL_AREA_BOX (area)); + box_orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (area)); + array = get_array (context, orientation, for_size); + + for (i = 0; i < array->len; i++) + { + CachedSize *size = &g_array_index (array, CachedSize, i); + + if (box_orientation == orientation) + { + /* Dont add spacing for 0 size groups, they can be 0 size because + * they contain only invisible cells for this round of requests + */ + if (min_size > 0 && size->nat_size > 0) + { + min_size += spacing; + nat_size += spacing; + } + + min_size += size->min_size; + nat_size += size->nat_size; + } + else + { + min_size = MAX (min_size, size->min_size); + nat_size = MAX (nat_size, size->nat_size); + } + } + + if (for_size < 0) + { + if (orientation == GTK_ORIENTATION_HORIZONTAL) + gtk_cell_area_context_push_preferred_width (GTK_CELL_AREA_CONTEXT (context), min_size, nat_size); + else + gtk_cell_area_context_push_preferred_height (GTK_CELL_AREA_CONTEXT (context), min_size, nat_size); + } + + if (minimum_size) + *minimum_size = min_size; + if (natural_size) + *natural_size = nat_size; +} + +static void +gtk_cell_area_box_context_get_preferred_height_for_width (GtkCellAreaContext *context, + gint width, + gint *minimum_height, + gint *natural_height) +{ + gtk_cell_area_box_context_sum (GTK_CELL_AREA_BOX_CONTEXT (context), GTK_ORIENTATION_VERTICAL, + width, minimum_height, natural_height); +} + +static void +gtk_cell_area_box_context_get_preferred_width_for_height (GtkCellAreaContext *context, + gint height, + gint *minimum_width, + gint *natural_width) +{ + gtk_cell_area_box_context_sum (GTK_CELL_AREA_BOX_CONTEXT (context), GTK_ORIENTATION_HORIZONTAL, + height, minimum_width, natural_width); +} + +/************************************************************* + * API * + *************************************************************/ +static void +copy_size_array (GArray *src_array, + GArray *dest_array) +{ + gint i; + + for (i = 0; i < src_array->len; i++) + { + CachedSize *src = &g_array_index (src_array, CachedSize, i); + CachedSize *dest = &g_array_index (dest_array, CachedSize, i); + + memcpy (dest, src, sizeof (CachedSize)); + } +} + +static void +for_size_copy (gpointer key, + GArray *size_array, + GHashTable *dest_hash) +{ + GArray *new_array; + + new_array = g_array_new (FALSE, TRUE, sizeof (CachedSize)); + g_array_set_size (new_array, size_array->len); + + copy_size_array (size_array, new_array); + + g_hash_table_insert (dest_hash, key, new_array); +} + +GtkCellAreaBoxContext * +gtk_cell_area_box_context_copy (GtkCellAreaBox *box, + GtkCellAreaBoxContext *context) +{ + GtkCellAreaBoxContext *copy; + + copy = g_object_new (GTK_TYPE_CELL_AREA_BOX_CONTEXT, + "area", box, NULL); + + gtk_cell_area_box_init_groups (copy, + context->priv->base_widths->len, + context->priv->expand); + + /* Copy the base arrays */ + copy_size_array (context->priv->base_widths, + copy->priv->base_widths); + copy_size_array (context->priv->base_heights, + copy->priv->base_heights); + + /* Copy each for size */ + g_hash_table_foreach (context->priv->heights, + (GHFunc)for_size_copy, copy->priv->heights); + g_hash_table_foreach (context->priv->widths, + (GHFunc)for_size_copy, copy->priv->widths); + + /* Copy any active allocation */ + copy->priv->n_orientation_allocs = + context->priv->n_orientation_allocs; + + if (copy->priv->n_orientation_allocs) + copy->priv->orientation_allocs = + g_memdup (context->priv->orientation_allocs, + copy->priv->n_orientation_allocs * sizeof (GtkCellAreaBoxAllocation)); + + return copy; +} + +void +gtk_cell_area_box_init_groups (GtkCellAreaBoxContext *box_context, + guint n_groups, + gboolean *expand_groups) +{ + GtkCellAreaBoxContextPrivate *priv; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context)); + g_return_if_fail (n_groups == 0 || expand_groups != NULL); + + /* When the group dimensions change, all info must be reset + * Note this already clears the min/nat values on the CachedSizes + */ + gtk_cell_area_context_reset (GTK_CELL_AREA_CONTEXT (box_context)); + + priv = box_context->priv; + g_array_set_size (priv->base_widths, n_groups); + g_array_set_size (priv->base_heights, n_groups); + + g_free (priv->expand); + priv->expand = g_memdup (expand_groups, n_groups * sizeof (gboolean)); +} + +void +gtk_cell_area_box_context_push_group_width (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint minimum_width, + gint natural_width) +{ + GtkCellAreaBoxContextPrivate *priv; + CachedSize *size; + gboolean grew = FALSE; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context)); + + priv = box_context->priv; + g_return_if_fail (group_idx < priv->base_widths->len); + + size = &g_array_index (priv->base_widths, CachedSize, group_idx); + if (minimum_width > size->min_size) + { + size->min_size = minimum_width; + grew = TRUE; + } + if (natural_width > size->nat_size) + { + size->nat_size = natural_width; + grew = TRUE; + } + + if (grew) + gtk_cell_area_box_context_sum (box_context, GTK_ORIENTATION_HORIZONTAL, -1, NULL, NULL); +} + +void +gtk_cell_area_box_context_push_group_height_for_width (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint for_width, + gint minimum_height, + gint natural_height) +{ + GtkCellAreaBoxContextPrivate *priv; + GArray *group_array; + CachedSize *size; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context)); + + priv = box_context->priv; + g_return_if_fail (group_idx < priv->base_widths->len); + + group_array = g_hash_table_lookup (priv->heights, GINT_TO_POINTER (for_width)); + if (!group_array) + { + group_array = group_array_new (box_context); + g_hash_table_insert (priv->heights, GINT_TO_POINTER (for_width), group_array); + } + + size = &g_array_index (group_array, CachedSize, group_idx); + size->min_size = MAX (size->min_size, minimum_height); + size->nat_size = MAX (size->nat_size, natural_height); +} + +void +gtk_cell_area_box_context_push_group_height (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint minimum_height, + gint natural_height) +{ + GtkCellAreaBoxContextPrivate *priv; + CachedSize *size; + gboolean grew = FALSE; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context)); + + priv = box_context->priv; + g_return_if_fail (group_idx < priv->base_heights->len); + + size = &g_array_index (priv->base_heights, CachedSize, group_idx); + if (minimum_height > size->min_size) + { + size->min_size = minimum_height; + grew = TRUE; + } + if (natural_height > size->nat_size) + { + size->nat_size = natural_height; + grew = TRUE; + } + + if (grew) + gtk_cell_area_box_context_sum (box_context, GTK_ORIENTATION_VERTICAL, -1, NULL, NULL); +} + +void +gtk_cell_area_box_context_push_group_width_for_height (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint for_height, + gint minimum_width, + gint natural_width) +{ + GtkCellAreaBoxContextPrivate *priv; + GArray *group_array; + CachedSize *size; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context)); + + priv = box_context->priv; + g_return_if_fail (group_idx < priv->base_widths->len); + + group_array = g_hash_table_lookup (priv->widths, GINT_TO_POINTER (for_height)); + if (!group_array) + { + group_array = group_array_new (box_context); + g_hash_table_insert (priv->widths, GINT_TO_POINTER (for_height), group_array); + } + + size = &g_array_index (group_array, CachedSize, group_idx); + size->min_size = MAX (size->min_size, minimum_width); + size->nat_size = MAX (size->nat_size, natural_width); +} + +void +gtk_cell_area_box_context_get_group_width (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint *minimum_width, + gint *natural_width) +{ + GtkCellAreaBoxContextPrivate *priv; + CachedSize *size; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context)); + + priv = box_context->priv; + g_return_if_fail (group_idx < priv->base_widths->len); + + size = &g_array_index (priv->base_widths, CachedSize, group_idx); + + if (minimum_width) + *minimum_width = size->min_size; + + if (natural_width) + *natural_width = size->nat_size; +} + +void +gtk_cell_area_box_context_get_group_height_for_width (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint for_width, + gint *minimum_height, + gint *natural_height) +{ + GtkCellAreaBoxContextPrivate *priv; + GArray *group_array; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context)); + + priv = box_context->priv; + g_return_if_fail (group_idx < priv->base_widths->len); + + group_array = g_hash_table_lookup (priv->heights, GINT_TO_POINTER (for_width)); + + if (group_array) + { + CachedSize *size = &g_array_index (group_array, CachedSize, group_idx); + + if (minimum_height) + *minimum_height = size->min_size; + + if (natural_height) + *natural_height = size->nat_size; + } + else + { + if (minimum_height) + *minimum_height = -1; + + if (natural_height) + *natural_height = -1; + } +} + +void +gtk_cell_area_box_context_get_group_height (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint *minimum_height, + gint *natural_height) +{ + GtkCellAreaBoxContextPrivate *priv; + CachedSize *size; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context)); + + priv = box_context->priv; + g_return_if_fail (group_idx < priv->base_heights->len); + + size = &g_array_index (priv->base_heights, CachedSize, group_idx); + + if (minimum_height) + *minimum_height = size->min_size; + + if (natural_height) + *natural_height = size->nat_size; +} + +void +gtk_cell_area_box_context_get_group_width_for_height (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint for_height, + gint *minimum_width, + gint *natural_width) +{ + GtkCellAreaBoxContextPrivate *priv; + GArray *group_array; + + g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context)); + + priv = box_context->priv; + g_return_if_fail (group_idx < priv->base_widths->len); + + group_array = g_hash_table_lookup (priv->widths, GINT_TO_POINTER (for_height)); + + if (group_array) + { + CachedSize *size = &g_array_index (group_array, CachedSize, group_idx); + + if (minimum_width) + *minimum_width = size->min_size; + + if (natural_width) + *natural_width = size->nat_size; + } + else + { + if (minimum_width) + *minimum_width = -1; + + if (natural_width) + *natural_width = -1; + } +} + +GtkRequestedSize * +gtk_cell_area_box_context_get_widths (GtkCellAreaBoxContext *box_context, + gint *n_widths) +{ + return gtk_cell_area_box_context_get_requests (box_context, GTK_ORIENTATION_HORIZONTAL, -1, n_widths); +} + +GtkRequestedSize * +gtk_cell_area_box_context_get_heights (GtkCellAreaBoxContext *box_context, + gint *n_heights) +{ + return gtk_cell_area_box_context_get_requests (box_context, GTK_ORIENTATION_VERTICAL, -1, n_heights); +} + +GtkCellAreaBoxAllocation * +gtk_cell_area_box_context_get_orientation_allocs (GtkCellAreaBoxContext *context, + gint *n_allocs) +{ + GtkCellAreaBoxContextPrivate *priv; + + g_return_val_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (context), NULL); + + priv = context->priv; + + *n_allocs = priv->n_orientation_allocs; + + return priv->orientation_allocs; +} diff --git a/gtk/gtkcellareaboxcontext.h b/gtk/gtkcellareaboxcontext.h new file mode 100644 index 0000000000..1161d38b2f --- /dev/null +++ b/gtk/gtkcellareaboxcontext.h @@ -0,0 +1,138 @@ +/* gtkcellareaboxcontext.h + * + * Copyright (C) 2010 Openismus GmbH + * + * Authors: + * Tristan Van Berkom <tristanvb@openismus.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only <gtk/gtk.h> can be included directly." +#endif + +#ifndef __GTK_CELL_AREA_BOX_CONTEXT_H__ +#define __GTK_CELL_AREA_BOX_CONTEXT_H__ + +#include <gtk/gtkcellareacontext.h> +#include <gtk/gtkcellareabox.h> +#include <gtk/gtkcellrenderer.h> +#include <gtk/gtksizerequest.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_CELL_AREA_BOX_CONTEXT (gtk_cell_area_box_context_get_type ()) +#define GTK_CELL_AREA_BOX_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CELL_AREA_BOX_CONTEXT, GtkCellAreaBoxContext)) +#define GTK_CELL_AREA_BOX_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_CELL_AREA_BOX_CONTEXT, GtkCellAreaBoxContextClass)) +#define GTK_IS_CELL_AREA_BOX_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CELL_AREA_BOX_CONTEXT)) +#define GTK_IS_CELL_AREA_BOX_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_CELL_AREA_BOX_CONTEXT)) +#define GTK_CELL_AREA_BOX_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_CELL_AREA_BOX_CONTEXT, GtkCellAreaBoxContextClass)) + +typedef struct _GtkCellAreaBoxContext GtkCellAreaBoxContext; +typedef struct _GtkCellAreaBoxContextClass GtkCellAreaBoxContextClass; +typedef struct _GtkCellAreaBoxContextPrivate GtkCellAreaBoxContextPrivate; + +struct _GtkCellAreaBoxContext +{ + GtkCellAreaContext parent_instance; + + GtkCellAreaBoxContextPrivate *priv; +}; + +struct _GtkCellAreaBoxContextClass +{ + GtkCellAreaContextClass parent_class; + +}; + +GType gtk_cell_area_box_context_get_type (void) G_GNUC_CONST; + + +/* Create a duplicate of the context */ +GtkCellAreaBoxContext *gtk_cell_area_box_context_copy (GtkCellAreaBox *box, + GtkCellAreaBoxContext *box_context); + +/* Initialize group array dimensions */ +void gtk_cell_area_box_init_groups (GtkCellAreaBoxContext *box_context, + guint n_groups, + gboolean *expand_groups); + +/* Update cell-group sizes */ +void gtk_cell_area_box_context_push_group_width (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint minimum_width, + gint natural_width); + +void gtk_cell_area_box_context_push_group_height_for_width (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint for_width, + gint minimum_height, + gint natural_height); + +void gtk_cell_area_box_context_push_group_height (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint minimum_height, + gint natural_height); + +void gtk_cell_area_box_context_push_group_width_for_height (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint for_height, + gint minimum_width, + gint natural_width); + +/* Fetch cell-group sizes */ +void gtk_cell_area_box_context_get_group_width (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint *minimum_width, + gint *natural_width); + +void gtk_cell_area_box_context_get_group_height_for_width (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint for_width, + gint *minimum_height, + gint *natural_height); + +void gtk_cell_area_box_context_get_group_height (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint *minimum_height, + gint *natural_height); + +void gtk_cell_area_box_context_get_group_width_for_height (GtkCellAreaBoxContext *box_context, + gint group_idx, + gint for_height, + gint *minimum_width, + gint *natural_width); + +GtkRequestedSize *gtk_cell_area_box_context_get_widths (GtkCellAreaBoxContext *box_context, + gint *n_widths); +GtkRequestedSize *gtk_cell_area_box_context_get_heights (GtkCellAreaBoxContext *box_context, + gint *n_heights); + +/* Private context/area interaction */ +typedef struct { + gint group_idx; /* Groups containing only invisible cells are not allocated */ + gint position; /* Relative group allocation position in the orientation of the box */ + gint size; /* Full allocated size of the cells in this group spacing inclusive */ +} GtkCellAreaBoxAllocation; + +GtkCellAreaBoxAllocation * +gtk_cell_area_box_context_get_orientation_allocs (GtkCellAreaBoxContext *context, + gint *n_allocs); + +G_END_DECLS + +#endif /* __GTK_CELL_AREA_BOX_CONTEXT_H__ */ diff --git a/gtk/gtkcellareacontext.c b/gtk/gtkcellareacontext.c new file mode 100644 index 0000000000..f3c14b00dd --- /dev/null +++ b/gtk/gtkcellareacontext.c @@ -0,0 +1,674 @@ +/* gtkcellareacontext.c + * + * Copyright (C) 2010 Openismus GmbH + * + * Authors: + * Tristan Van Berkom <tristanvb@openismus.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:gtkcellareacontext + * @Short_Description: An object for a #GtkCellArea to store geometrical information for a series of rows. + * @Title: GtkCellAreaContext + * + * The #GtkCellAreaContext object is created by a given #GtkCellArea implementation via it's + * #GtkCellAreaClass.create_context() virtual method and is used to store cell sizes and alignments + * for a series of #GtkTreeModel rows that are requested and rendered in the same context. + * + * #GtkCellLayout widgets can create any number of contexts in which to request and render + * groups of data rows. However its important that the same context which was used to + * request sizes for a given #GtkTreeModel row also be used for the same row when calling + * other #GtkCellArea apis such as gtk_cell_area_render() and gtk_cell_area_event(). + */ +#include "config.h" +#include "gtkintl.h" +#include "gtkmarshalers.h" +#include "gtkcellareacontext.h" +#include "gtkprivate.h" + +/* GObjectClass */ +static void gtk_cell_area_context_dispose (GObject *object); +static void gtk_cell_area_context_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void gtk_cell_area_context_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); + +/* GtkCellAreaContextClass */ +static void gtk_cell_area_context_real_reset (GtkCellAreaContext *context); +static void gtk_cell_area_context_real_allocate (GtkCellAreaContext *context, + gint width, + gint height); + +struct _GtkCellAreaContextPrivate +{ + GtkCellArea *cell_area; + + gint min_width; + gint nat_width; + gint min_height; + gint nat_height; + gint alloc_width; + gint alloc_height; +}; + +enum { + PROP_0, + PROP_CELL_AREA, + PROP_MIN_WIDTH, + PROP_NAT_WIDTH, + PROP_MIN_HEIGHT, + PROP_NAT_HEIGHT +}; + +G_DEFINE_TYPE (GtkCellAreaContext, gtk_cell_area_context, G_TYPE_OBJECT); + +static void +gtk_cell_area_context_init (GtkCellAreaContext *context) +{ + GtkCellAreaContextPrivate *priv; + + context->priv = G_TYPE_INSTANCE_GET_PRIVATE (context, + GTK_TYPE_CELL_AREA_CONTEXT, + GtkCellAreaContextPrivate); + priv = context->priv; + + priv->min_width = -1; + priv->nat_width = -1; + priv->min_height = -1; + priv->nat_height = -1; +} + +static void +gtk_cell_area_context_class_init (GtkCellAreaContextClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + /* GObjectClass */ + object_class->dispose = gtk_cell_area_context_dispose; + object_class->get_property = gtk_cell_area_context_get_property; + object_class->set_property = gtk_cell_area_context_set_property; + + /* GtkCellAreaContextClass */ + class->reset = gtk_cell_area_context_real_reset; + class->allocate = gtk_cell_area_context_real_allocate; + + /** + * GtkCellAreaContext:area: + * + * The #GtkCellArea this context was created by + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_CELL_AREA, + g_param_spec_object ("area", + P_("Area"), + P_("The Cell Area this context was created for"), + GTK_TYPE_CELL_AREA, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + /** + * GtkCellAreaContext:minimum-width: + * + * The minimum width for the #GtkCellArea in this context + * for all #GtkTreeModel rows that this context was requested + * for using gtk_cell_area_get_preferred_width(). + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_MIN_WIDTH, + g_param_spec_int ("minimum-width", + P_("Minimum Width"), + P_("Minimum cached width"), + -1, + G_MAXINT, + -1, + G_PARAM_READABLE)); + + /** + * GtkCellAreaContext:natural-width: + * + * The natural width for the #GtkCellArea in this context + * for all #GtkTreeModel rows that this context was requested + * for using gtk_cell_area_get_preferred_width(). + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_NAT_WIDTH, + g_param_spec_int ("natural-width", + P_("Minimum Width"), + P_("Minimum cached width"), + -1, + G_MAXINT, + -1, + G_PARAM_READABLE)); + + /** + * GtkCellAreaContext:minimum-height: + * + * The minimum height for the #GtkCellArea in this context + * for all #GtkTreeModel rows that this context was requested + * for using gtk_cell_area_get_preferred_height(). + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_MIN_HEIGHT, + g_param_spec_int ("minimum-height", + P_("Minimum Height"), + P_("Minimum cached height"), + -1, + G_MAXINT, + -1, + G_PARAM_READABLE)); + + /** + * GtkCellAreaContext:natural-height: + * + * The natural height for the #GtkCellArea in this context + * for all #GtkTreeModel rows that this context was requested + * for using gtk_cell_area_get_preferred_height(). + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_NAT_HEIGHT, + g_param_spec_int ("natural-height", + P_("Minimum Height"), + P_("Minimum cached height"), + -1, + G_MAXINT, + -1, + G_PARAM_READABLE)); + + g_type_class_add_private (object_class, sizeof (GtkCellAreaContextPrivate)); +} + +/************************************************************* + * GObjectClass * + *************************************************************/ +static void +gtk_cell_area_context_dispose (GObject *object) +{ + GtkCellAreaContext *context = GTK_CELL_AREA_CONTEXT (object); + GtkCellAreaContextPrivate *priv = context->priv; + + if (priv->cell_area) + { + g_object_unref (priv->cell_area); + + priv->cell_area = NULL; + } + + G_OBJECT_CLASS (gtk_cell_area_context_parent_class)->dispose (object); +} + +static void +gtk_cell_area_context_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkCellAreaContext *context = GTK_CELL_AREA_CONTEXT (object); + GtkCellAreaContextPrivate *priv = context->priv; + + switch (prop_id) + { + case PROP_CELL_AREA: + priv->cell_area = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_cell_area_context_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkCellAreaContext *context = GTK_CELL_AREA_CONTEXT (object); + GtkCellAreaContextPrivate *priv = context->priv; + + switch (prop_id) + { + case PROP_CELL_AREA: + g_value_set_object (value, priv->cell_area); + break; + case PROP_MIN_WIDTH: + g_value_set_int (value, priv->min_width); + break; + case PROP_NAT_WIDTH: + g_value_set_int (value, priv->nat_width); + break; + case PROP_MIN_HEIGHT: + g_value_set_int (value, priv->min_height); + break; + case PROP_NAT_HEIGHT: + g_value_set_int (value, priv->nat_height); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/************************************************************* + * GtkCellAreaContextClass * + *************************************************************/ +static void +gtk_cell_area_context_real_reset (GtkCellAreaContext *context) +{ + GtkCellAreaContextPrivate *priv = context->priv; + + g_object_freeze_notify (G_OBJECT (context)); + + if (priv->min_width != -1) + { + priv->min_width = -1; + g_object_notify (G_OBJECT (context), "minimum-width"); + } + + if (priv->nat_width != -1) + { + priv->nat_width = -1; + g_object_notify (G_OBJECT (context), "natural-width"); + } + + if (priv->min_height != -1) + { + priv->min_height = -1; + g_object_notify (G_OBJECT (context), "minimum-height"); + } + + if (priv->nat_height != -1) + { + priv->nat_height = -1; + g_object_notify (G_OBJECT (context), "natural-height"); + } + + priv->alloc_width = 0; + priv->alloc_height = 0; + + g_object_thaw_notify (G_OBJECT (context)); +} + +static void +gtk_cell_area_context_real_allocate (GtkCellAreaContext *context, + gint width, + gint height) +{ + GtkCellAreaContextPrivate *priv = context->priv; + + priv->alloc_width = width; + priv->alloc_height = height; +} + +/************************************************************* + * API * + *************************************************************/ +/** + * gtk_cell_area_context_get_area: + * @context: a #GtkCellAreaContext + * + * Fetches the #GtkCellArea this @context was created by. + * + * This is generally unneeded by layouting widgets however + * its important for the context implementation itself to + * fetch information about the area it is being used for. + * + * For instance at #GtkCellAreaContextClass.allocate() time + * it's important to know details about any cell spacing + * that the #GtkCellArea is configured with in order to + * compute a proper allocation. + * + * Return value: the #GtkCellArea this context was created by. + * + * Since: 3.0 + */ +GtkCellArea * +gtk_cell_area_context_get_area (GtkCellAreaContext *context) +{ + GtkCellAreaContextPrivate *priv; + + g_return_val_if_fail (GTK_IS_CELL_AREA_CONTEXT (context), NULL); + + priv = context->priv; + + return priv->cell_area; +} + +/** + * gtk_cell_area_context_reset: + * @context: a #GtkCellAreaContext + * + * Resets any previously cached request and allocation + * data. + * + * When underlying #GtkTreeModel data changes it's + * important to reset the context if the content + * size is allowed to shrink. If the content size + * is only allowed to grow (this is usually an option + * for views rendering large data stores as a measure + * of optimization), then only the row that changed + * or was inserted needs to be (re)requested with + * gtk_cell_area_get_preferred_width(). + * + * When the new overall size of the context requires + * that the allocated size changes (or whenever this + * allocation changes at all), the variable row + * sizes need to be re-requested for every row. + * + * For instance, if the rows are displayed all with + * the same width from top to bottom then a change + * in the allocated width necessitates a recalculation + * of all the displayed row heights using + * gtk_cell_area_get_preferred_height_for_width(). + * + * Since 3.0 + */ +void +gtk_cell_area_context_reset (GtkCellAreaContext *context) +{ + g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context)); + + GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->reset (context); +} + +/** + * gtk_cell_area_context_allocate: + * @context: a #GtkCellAreaContext + * @width: the allocated width for all #GtkTreeModel rows rendered with @context, or -1. + * @height: the allocated height for all #GtkTreeModel rows rendered with @context, or -1. + * + * Allocates a width and/or a height for all rows which are to be rendered with @context. + * + * Usually allocation is performed only horizontally or sometimes vertically since + * a group of rows are usually rendered side by side vertically or horizontally and + * share either the same width or the same hieght. Sometimes they are allocated in + * both horizontal and vertical orientations producing a homogenious effect of the + * rows. This is generally the case for #GtkTreeView when #GtkTreeView:fixed-height-mode + * is enabled. + * + * Since 3.0 + */ +void +gtk_cell_area_context_allocate (GtkCellAreaContext *context, + gint width, + gint height) +{ + g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context)); + + GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->allocate (context, width, height); +} + +/** + * gtk_cell_area_context_get_preferred_width: + * @context: a #GtkCellAreaContext + * @minimum_width: (out) (allow-none): location to store the minimum width, or %NULL + * @natural_width: (out) (allow-none): location to store the natural width, or %NULL + * + * Gets the accumulative preferred width for all rows which have been requested + * with this context. + * + * After gtk_cell_area_context_reset() is called and/or before ever requesting + * the size of a #GtkCellArea, the returned values are -1. + * + * Since: 3.0 + */ +void +gtk_cell_area_context_get_preferred_width (GtkCellAreaContext *context, + gint *minimum_width, + gint *natural_width) +{ + GtkCellAreaContextPrivate *priv; + + g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context)); + + priv = context->priv; + + if (minimum_width) + *minimum_width = priv->min_width; + + if (natural_width) + *natural_width = priv->nat_width; +} + +/** + * gtk_cell_area_context_get_preferred_height: + * @context: a #GtkCellAreaContext + * @minimum_height: (out) (allow-none): location to store the minimum height, or %NULL + * @natural_height: (out) (allow-none): location to store the natural height, or %NULL + * + * Gets the accumulative preferred height for all rows which have been requested + * with this context. + * + * After gtk_cell_area_context_reset() is called and/or before ever requesting + * the size of a #GtkCellArea, the returned values are -1. + * + * Since: 3.0 + */ +void +gtk_cell_area_context_get_preferred_height (GtkCellAreaContext *context, + gint *minimum_height, + gint *natural_height) +{ + GtkCellAreaContextPrivate *priv; + + g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context)); + + priv = context->priv; + + if (minimum_height) + *minimum_height = priv->min_height; + + if (natural_height) + *natural_height = priv->nat_height; +} + +/** + * gtk_cell_area_context_get_preferred_height_for_width: + * @context: a #GtkCellAreaContext + * @width: a proposed width for allocation + * @minimum_height: (out) (allow-none): location to store the minimum height, or %NULL + * @natural_height: (out) (allow-none): location to store the natural height, or %NULL + * + * Gets the accumulative preferred height for @width for all rows which have been + * requested for the same said @width with this context. + * + * After gtk_cell_area_context_reset() is called and/or before ever requesting + * the size of a #GtkCellArea, the returned values are -1. + * + * Since: 3.0 + */ +void +gtk_cell_area_context_get_preferred_height_for_width (GtkCellAreaContext *context, + gint width, + gint *minimum_height, + gint *natural_height) +{ + g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context)); + + if (GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->get_preferred_height_for_width) + GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->get_preferred_height_for_width (context, + width, + minimum_height, + natural_height); +} + +/** + * gtk_cell_area_context_get_preferred_width_for_height: + * @context: a #GtkCellAreaContext + * @height: a proposed height for allocation + * @minimum_width: (out) (allow-none): location to store the minimum width, or %NULL + * @natural_width: (out) (allow-none): location to store the natural width, or %NULL + * + * Gets the accumulative preferred width for @height for all rows which have + * been requested for the same said @height with this context. + * + * After gtk_cell_area_context_reset() is called and/or before ever requesting + * the size of a #GtkCellArea, the returned values are -1. + * + * Since: 3.0 + */ +void +gtk_cell_area_context_get_preferred_width_for_height (GtkCellAreaContext *context, + gint height, + gint *minimum_width, + gint *natural_width) +{ + g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context)); + + if (GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->get_preferred_width_for_height) + GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->get_preferred_width_for_height (context, + height, + minimum_width, + natural_width); +} + +/** + * gtk_cell_area_context_get_allocation: + * @context: a #GtkCellAreaContext + * @width: (out) (allow-none): location to store the allocated width, or %NULL. + * @height: (out) (allow-none): location to store the allocated height, or %NULL. + * + * Fetches the current allocation size for @context. + * + * If the context was not allocated in width or height, or if the + * context was recently reset with gtk_cell_area_context_reset(), + * the returned value will be -1. + * + * Since: 3.0 + */ +void +gtk_cell_area_context_get_allocation (GtkCellAreaContext *context, + gint *width, + gint *height) +{ + GtkCellAreaContextPrivate *priv; + + g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context)); + + priv = context->priv; + + if (width) + *width = priv->alloc_width; + + if (height) + *height = priv->alloc_height; +} + +/** + * gtk_cell_area_context_push_preferred_width: + * @context: a #GtkCellAreaContext + * @minimum_width: the proposed new minimum width for @context. + * @natural_width: the proposed new natural width for @context. + * + * Causes the minimum and/or natural width to grow if the new + * proposed sizes exceed the current minimum and natural width. + * + * This is used by #GtkCellAreaContext implementations during + * the request process over a series of #GtkTreeModel rows to + * progressively push the requested width over a series of + * gtk_cell_area_get_preferred_width() requests. + * + * Since: 3.0 + */ +void +gtk_cell_area_context_push_preferred_width (GtkCellAreaContext *context, + gint minimum_width, + gint natural_width) +{ + GtkCellAreaContextPrivate *priv; + + g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context)); + + priv = context->priv; + + g_object_freeze_notify (G_OBJECT (context)); + + if (minimum_width > priv->min_width) + { + priv->min_width = minimum_width; + + g_object_notify (G_OBJECT (context), "minimum-width"); + } + + if (natural_width > priv->nat_width) + { + priv->nat_width = natural_width; + + g_object_notify (G_OBJECT (context), "natural-width"); + } + + g_object_thaw_notify (G_OBJECT (context)); +} + +/** + * gtk_cell_area_context_push_preferred_height: + * @context: a #GtkCellAreaContext + * @minimum_height: the proposed new minimum height for @context. + * @natural_height: the proposed new natural height for @context. + * + * Causes the minimum and/or natural height to grow if the new + * proposed sizes exceed the current minimum and natural height. + * + * This is used by #GtkCellAreaContext implementations during + * the request process over a series of #GtkTreeModel rows to + * progressively push the requested height over a series of + * gtk_cell_area_get_preferred_height() requests. + * + * Since: 3.0 + */ +void +gtk_cell_area_context_push_preferred_height (GtkCellAreaContext *context, + gint minimum_height, + gint natural_height) +{ + GtkCellAreaContextPrivate *priv; + + g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context)); + + priv = context->priv; + + g_object_freeze_notify (G_OBJECT (context)); + + if (minimum_height > priv->min_height) + { + priv->min_height = minimum_height; + + g_object_notify (G_OBJECT (context), "minimum-height"); + } + + if (natural_height > priv->nat_height) + { + priv->nat_height = natural_height; + + g_object_notify (G_OBJECT (context), "natural-height"); + } + + g_object_thaw_notify (G_OBJECT (context)); +} diff --git a/gtk/gtkcellareacontext.h b/gtk/gtkcellareacontext.h new file mode 100644 index 0000000000..3c3522bc59 --- /dev/null +++ b/gtk/gtkcellareacontext.h @@ -0,0 +1,133 @@ +/* gtkcellareacontext.h + * + * Copyright (C) 2010 Openismus GmbH + * + * Authors: + * Tristan Van Berkom <tristanvb@openismus.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only <gtk/gtk.h> can be included directly." +#endif + +#ifndef __GTK_CELL_AREA_CONTEXT_H__ +#define __GTK_CELL_AREA_CONTEXT_H__ + +#include <gtk/gtkcellarea.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_CELL_AREA_CONTEXT (gtk_cell_area_context_get_type ()) +#define GTK_CELL_AREA_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CELL_AREA_CONTEXT, GtkCellAreaContext)) +#define GTK_CELL_AREA_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_CELL_AREA_CONTEXT, GtkCellAreaContextClass)) +#define GTK_IS_CELL_AREA_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CELL_AREA_CONTEXT)) +#define GTK_IS_CELL_AREA_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_CELL_AREA_CONTEXT)) +#define GTK_CELL_AREA_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_CELL_AREA_CONTEXT, GtkCellAreaContextClass)) + +typedef struct _GtkCellAreaContextPrivate GtkCellAreaContextPrivate; +typedef struct _GtkCellAreaContextClass GtkCellAreaContextClass; + +struct _GtkCellAreaContext +{ + /*< private >*/ + GObject parent_instance; + + GtkCellAreaContextPrivate *priv; +}; + +/** + * GtkCellAreaContextClass: + * @allocate: This tells the context that an allocation width or height (or both) + * have been decided for a group of rows. The context should store any allocations + * for internally aligned cells at this point so that they dont need to be + * recalculated at gtk_cell_area_render() time. + * @reset: Clear any previously stored information about requested and allocated + * sizes for the context. + * @get_preferred_height_for_width: Returns the aligned height for the given width + * that context must store while collecting sizes for it's rows. + * @get_preferred_width_for_height: Returns the aligned width for the given height + * that context must store while collecting sizes for it's rows. + */ +struct _GtkCellAreaContextClass +{ + /*< private >*/ + GObjectClass parent_class; + + /*< public >*/ + void (* allocate) (GtkCellAreaContext *context, + gint width, + gint height); + void (* reset) (GtkCellAreaContext *context); + void (* get_preferred_height_for_width) (GtkCellAreaContext *context, + gint width, + gint *minimum_height, + gint *natural_height); + void (* get_preferred_width_for_height) (GtkCellAreaContext *context, + gint height, + gint *minimum_width, + gint *natural_width); + + /*< private >*/ + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); + void (*_gtk_reserved5) (void); + void (*_gtk_reserved6) (void); +}; + +GType gtk_cell_area_context_get_type (void) G_GNUC_CONST; + +/* Main apis */ +GtkCellArea *gtk_cell_area_context_get_area (GtkCellAreaContext *context); +void gtk_cell_area_context_allocate (GtkCellAreaContext *context, + gint width, + gint height); +void gtk_cell_area_context_reset (GtkCellAreaContext *context); + +/* Apis for GtkCellArea clients to consult cached values for a series of GtkTreeModel rows */ +void gtk_cell_area_context_get_preferred_width (GtkCellAreaContext *context, + gint *minimum_width, + gint *natural_width); +void gtk_cell_area_context_get_preferred_height (GtkCellAreaContext *context, + gint *minimum_height, + gint *natural_height); +void gtk_cell_area_context_get_preferred_height_for_width (GtkCellAreaContext *context, + gint width, + gint *minimum_height, + gint *natural_height); +void gtk_cell_area_context_get_preferred_width_for_height (GtkCellAreaContext *context, + gint height, + gint *minimum_width, + gint *natural_width); +void gtk_cell_area_context_get_allocation (GtkCellAreaContext *context, + gint *width, + gint *height); + +/* Apis for GtkCellArea implementations to update cached values for multiple GtkTreeModel rows */ +void gtk_cell_area_context_push_preferred_width (GtkCellAreaContext *context, + gint minimum_width, + gint natural_width); +void gtk_cell_area_context_push_preferred_height (GtkCellAreaContext *context, + gint minimum_height, + gint natural_height); + +G_END_DECLS + +#endif /* __GTK_CELL_AREA_CONTEXT_H__ */ diff --git a/gtk/gtkcelllayout.c b/gtk/gtkcelllayout.c index 8388b5f7d5..b2182d276e 100644 --- a/gtk/gtkcelllayout.c +++ b/gtk/gtkcelllayout.c @@ -17,11 +17,80 @@ * Boston, MA 02111-1307, USA. */ +/** + * SECTION:gtkcelllayout + * @Short_Description: An interface for packing cells + * @Title: GtkCellLayout + * + * #GtkCellLayout is an interface to be implemented by all objects which + * want to provide a #GtkTreeViewColumn-like API for packing cells, setting + * attributes and data funcs. + * + * One of the notable features provided by implementations of GtkCellLayout + * are <emphasis>attributes</emphasis>. Attributes let you set the properties + * in flexible ways. They can just be set to constant values like regular + * properties. But they can also be mapped to a column of the underlying + * tree model with gtk_cell_layout_set_attributes(), which means that the value + * of the attribute can change from cell to cell as they are rendered by the + * cell renderer. Finally, it is possible to specify a function with + * gtk_cell_layout_set_cell_data_func() that is called to determine the value + * of the attribute for each cell that is rendered. + * + * <refsect2 id="GtkCellLayout-BUILDER-UI"> + * <title>GtkCellLayouts as GtkBuildable</title> + * <para> + * Implementations of GtkCellLayout which also implement the GtkBuildable + * interface (#GtkCellView, #GtkIconView, #GtkComboBox, #GtkComboBoxEntry, + * #GtkEntryCompletion, #GtkTreeViewColumn) accept GtkCellRenderer objects + * as <child> elements in UI definitions. They support a custom + * <attributes> element for their children, which can contain + * multiple <attribute> elements. Each <attribute> element has + * a name attribute which specifies a property of the cell renderer; the + * content of the element is the attribute value. + * + * <example> + * <title>A UI definition fragment specifying attributes</title> + * <programlisting><![CDATA[ + * <object class="GtkCellView"> + * <child> + * <object class="GtkCellRendererText"/> + * <attributes> + * <attribute name="text">0</attribute> + * </attributes> + * </child>" + * </object> + * ]]></programlisting> + * </example> + * + * Furthermore for implementations of GtkCellLayout that use a #GtkCellArea + * to lay out cells (most, of not all GtkCellLayouts in GTK+ use a GtkCellArea) + * <link linkend="cell-properties">cell properties</link> can also be defined + * in the format by specifying the custom <cell-packing> attribute which + * can contain multiple <property> elements defined in the normal way. + * <example> + * <title>A UI definition fragment specifying cell properties</title> + * <programlisting><![CDATA[ + * <object class="GtkTreeViewColumn"> + * <child> + * <object class="GtkCellRendererText"/> + * <cell-packing> + * <property name="align">True</property> + * <property name="expand">False</property> + * </cell-packing> + * </child>" + * </object> + * ]]></programlisting> + * </example> + * </para> + * </refsect2> + */ + #include "config.h" #include <string.h> #include <stdlib.h> #include <errno.h> #include "gtkcelllayout.h" +#include "gtkbuilderprivate.h" #include "gtkintl.h" @@ -53,12 +122,23 @@ gtk_cell_layout_pack_start (GtkCellLayout *cell_layout, GtkCellRenderer *cell, gboolean expand) { + GtkCellLayoutIface *iface; + GtkCellArea *area; + g_return_if_fail (GTK_IS_CELL_LAYOUT (cell_layout)); g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); - (* GTK_CELL_LAYOUT_GET_IFACE (cell_layout)->pack_start) (cell_layout, - cell, - expand); + iface = GTK_CELL_LAYOUT_GET_IFACE (cell_layout); + + if (iface->pack_start) + iface->pack_start (cell_layout, cell, expand); + else + { + area = iface->get_area (cell_layout); + + if (area) + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (area), cell, expand); + } } /** @@ -80,12 +160,23 @@ gtk_cell_layout_pack_end (GtkCellLayout *cell_layout, GtkCellRenderer *cell, gboolean expand) { + GtkCellLayoutIface *iface; + GtkCellArea *area; + g_return_if_fail (GTK_IS_CELL_LAYOUT (cell_layout)); g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); - (* GTK_CELL_LAYOUT_GET_IFACE (cell_layout)->pack_end) (cell_layout, - cell, - expand); + iface = GTK_CELL_LAYOUT_GET_IFACE (cell_layout); + + if (iface->pack_end) + iface->pack_end (cell_layout, cell, expand); + else + { + area = iface->get_area (cell_layout); + + if (area) + gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (area), cell, expand); + } } /** @@ -100,9 +191,22 @@ gtk_cell_layout_pack_end (GtkCellLayout *cell_layout, void gtk_cell_layout_clear (GtkCellLayout *cell_layout) { + GtkCellLayoutIface *iface; + GtkCellArea *area; + g_return_if_fail (GTK_IS_CELL_LAYOUT (cell_layout)); - (* GTK_CELL_LAYOUT_GET_IFACE (cell_layout)->clear) (cell_layout); + iface = GTK_CELL_LAYOUT_GET_IFACE (cell_layout); + + if (iface->clear) + iface->clear (cell_layout); + else + { + area = iface->get_area (cell_layout); + + if (area) + gtk_cell_layout_clear (GTK_CELL_LAYOUT (area)); + } } static void @@ -113,17 +217,28 @@ gtk_cell_layout_set_attributesv (GtkCellLayout *cell_layout, gchar *attribute; gint column; GtkCellLayoutIface *iface; + GtkCellArea *area; attribute = va_arg (args, gchar *); iface = GTK_CELL_LAYOUT_GET_IFACE (cell_layout); - (* iface->clear_attributes) (cell_layout, cell); + if (iface->get_area) + area = iface->get_area (cell_layout); + + if (iface->clear_attributes) + iface->clear_attributes (cell_layout, cell); + else if (area) + gtk_cell_layout_clear_attributes (GTK_CELL_LAYOUT (area), cell); while (attribute != NULL) { column = va_arg (args, gint); - (* iface->add_attribute) (cell_layout, cell, attribute, column); + if (iface->add_attribute) + iface->add_attribute (cell_layout, cell, attribute, column); + else if (area) + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (area), cell, attribute, column); + attribute = va_arg (args, gchar *); } } @@ -177,15 +292,28 @@ gtk_cell_layout_add_attribute (GtkCellLayout *cell_layout, const gchar *attribute, gint column) { + GtkCellLayoutIface *iface; + GtkCellArea *area; + g_return_if_fail (GTK_IS_CELL_LAYOUT (cell_layout)); g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); g_return_if_fail (attribute != NULL); g_return_if_fail (column >= 0); - (* GTK_CELL_LAYOUT_GET_IFACE (cell_layout)->add_attribute) (cell_layout, - cell, - attribute, - column); + iface = GTK_CELL_LAYOUT_GET_IFACE (cell_layout); + + if (iface->add_attribute) + iface->add_attribute (cell_layout, + cell, + attribute, + column); + else + { + area = iface->get_area (cell_layout); + + if (area) + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (area), cell, attribute, column); + } } /** @@ -210,14 +338,27 @@ gtk_cell_layout_set_cell_data_func (GtkCellLayout *cell_layout, gpointer func_data, GDestroyNotify destroy) { + GtkCellLayoutIface *iface; + GtkCellArea *area; + g_return_if_fail (GTK_IS_CELL_LAYOUT (cell_layout)); g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); - (* GTK_CELL_LAYOUT_GET_IFACE (cell_layout)->set_cell_data_func) (cell_layout, - cell, - func, - func_data, - destroy); + iface = GTK_CELL_LAYOUT_GET_IFACE (cell_layout); + + if (iface->set_cell_data_func) + iface->set_cell_data_func (cell_layout, + cell, + func, + func_data, + destroy); + else + { + area = iface->get_area (cell_layout); + + if (area) + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (area), cell, func, func_data, destroy); + } } /** @@ -234,11 +375,23 @@ void gtk_cell_layout_clear_attributes (GtkCellLayout *cell_layout, GtkCellRenderer *cell) { + GtkCellLayoutIface *iface; + GtkCellArea *area; + g_return_if_fail (GTK_IS_CELL_LAYOUT (cell_layout)); g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); - (* GTK_CELL_LAYOUT_GET_IFACE (cell_layout)->clear_attributes) (cell_layout, - cell); + iface = GTK_CELL_LAYOUT_GET_IFACE (cell_layout); + + if (iface->clear_attributes) + iface->clear_attributes (cell_layout, cell); + else + { + area = iface->get_area (cell_layout); + + if (area) + gtk_cell_layout_clear_attributes (GTK_CELL_LAYOUT (area), cell); + } } /** @@ -257,12 +410,23 @@ gtk_cell_layout_reorder (GtkCellLayout *cell_layout, GtkCellRenderer *cell, gint position) { + GtkCellLayoutIface *iface; + GtkCellArea *area; + g_return_if_fail (GTK_IS_CELL_LAYOUT (cell_layout)); g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); - (* GTK_CELL_LAYOUT_GET_IFACE (cell_layout)->reorder) (cell_layout, - cell, - position); + iface = GTK_CELL_LAYOUT_GET_IFACE (cell_layout); + + if (iface->reorder) + iface->reorder (cell_layout, cell, position); + else + { + area = iface->get_area (cell_layout); + + if (area) + gtk_cell_layout_reorder (GTK_CELL_LAYOUT (area), cell, position); + } } /** @@ -281,16 +445,52 @@ GList * gtk_cell_layout_get_cells (GtkCellLayout *cell_layout) { GtkCellLayoutIface *iface; + GtkCellArea *area; g_return_val_if_fail (GTK_IS_CELL_LAYOUT (cell_layout), NULL); iface = GTK_CELL_LAYOUT_GET_IFACE (cell_layout); if (iface->get_cells) return iface->get_cells (cell_layout); + else + { + area = iface->get_area (cell_layout); + + if (area) + return gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area)); + } return NULL; } +/** + * gtk_cell_layout_get_area: + * @cell_layout: a #GtkCellLayout + * + * Returns the underlying #GtkCellArea which might be @cell_layout if called on a #GtkCellArea or + * might be %NULL if no #GtkCellArea is used by @cell_layout. + * + * Return value: (transfer none): a list of cell renderers. The list, but not the + * renderers has been newly allocated and should be freed with + * g_list_free() when no longer needed. + * + * Since: 3.0 + */ +GtkCellArea * +gtk_cell_layout_get_area (GtkCellLayout *cell_layout) +{ + GtkCellLayoutIface *iface; + + g_return_val_if_fail (GTK_IS_CELL_LAYOUT (cell_layout), NULL); + + iface = GTK_CELL_LAYOUT_GET_IFACE (cell_layout); + if (iface->get_area) + return iface->get_area (cell_layout); + + return NULL; +} + +/* Attribute parsing */ typedef struct { GtkCellLayout *cell_layout; GtkCellRenderer *renderer; @@ -364,6 +564,147 @@ static const GMarkupParser attributes_parser = attributes_text_element, }; + +/* Cell packing parsing */ +static void +gtk_cell_layout_buildable_set_cell_property (GtkCellArea *area, + GtkBuilder *builder, + GtkCellRenderer *cell, + gchar *name, + const gchar *value) +{ + GParamSpec *pspec; + GValue gvalue = { 0, }; + GError *error = NULL; + + pspec = gtk_cell_area_class_find_cell_property (GTK_CELL_AREA_GET_CLASS (area), name); + if (!pspec) + { + g_warning ("%s does not have a property called %s", + g_type_name (G_OBJECT_TYPE (area)), name); + return; + } + + if (!gtk_builder_value_from_string (builder, pspec, value, &gvalue, &error)) + { + g_warning ("Could not read property %s:%s with value %s of type %s: %s", + g_type_name (G_OBJECT_TYPE (area)), + name, + value, + g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)), + error->message); + g_error_free (error); + return; + } + + gtk_cell_area_cell_set_property (area, cell, name, &gvalue); + g_value_unset (&gvalue); +} + +typedef struct { + GtkBuilder *builder; + GtkCellLayout *cell_layout; + GtkCellRenderer *renderer; + gchar *cell_prop_name; + gchar *context; + gboolean translatable; +} CellPackingSubParserData; + +static void +cell_packing_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **names, + const gchar **values, + gpointer user_data, + GError **error) +{ + CellPackingSubParserData *parser_data = (CellPackingSubParserData*)user_data; + guint i; + + if (strcmp (element_name, "property") == 0) + { + for (i = 0; names[i]; i++) + if (strcmp (names[i], "name") == 0) + parser_data->cell_prop_name = g_strdup (values[i]); + else if (strcmp (names[i], "translatable") == 0) + { + if (!_gtk_builder_boolean_from_string (values[i], + &parser_data->translatable, + error)) + return; + } + else if (strcmp (names[i], "comments") == 0) + ; /* for translators */ + else if (strcmp (names[i], "context") == 0) + parser_data->context = g_strdup (values[i]); + else + g_warning ("Unsupported attribute for GtkCellLayout Cell " + "property: %s\n", names[i]); + } + else if (strcmp (element_name, "cell-packing") == 0) + return; + else + g_warning ("Unsupported tag for GtkCellLayout: %s\n", element_name); +} + +static void +cell_packing_text_element (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + CellPackingSubParserData *parser_data = (CellPackingSubParserData*)user_data; + GtkCellArea *area; + gchar* value; + + if (!parser_data->cell_prop_name) + return; + + if (parser_data->translatable && text_len) + { + const gchar* domain; + domain = gtk_builder_get_translation_domain (parser_data->builder); + + value = _gtk_builder_parser_translate (domain, + parser_data->context, + text); + } + else + { + value = g_strdup (text); + } + + area = gtk_cell_layout_get_area (parser_data->cell_layout); + + if (!area) + { + g_warning ("%s does not have an internal GtkCellArea class and cannot apply child cell properties", + g_type_name (G_OBJECT_TYPE (parser_data->cell_layout))); + return; + } + + gtk_cell_layout_buildable_set_cell_property (area, + parser_data->builder, + parser_data->renderer, + parser_data->cell_prop_name, + value); + + g_free (parser_data->cell_prop_name); + g_free (parser_data->context); + g_free (value); + parser_data->cell_prop_name = NULL; + parser_data->context = NULL; + parser_data->translatable = FALSE; +} + +static const GMarkupParser cell_packing_parser = + { + cell_packing_start_element, + NULL, + cell_packing_text_element, + }; + gboolean _gtk_cell_layout_buildable_custom_tag_start (GtkBuildable *buildable, GtkBuilder *builder, @@ -372,38 +713,60 @@ _gtk_cell_layout_buildable_custom_tag_start (GtkBuildable *buildable, GMarkupParser *parser, gpointer *data) { - AttributesSubParserData *parser_data; + AttributesSubParserData *attr_data; + CellPackingSubParserData *packing_data; if (!child) return FALSE; if (strcmp (tagname, "attributes") == 0) { - parser_data = g_slice_new0 (AttributesSubParserData); - parser_data->cell_layout = GTK_CELL_LAYOUT (buildable); - parser_data->renderer = GTK_CELL_RENDERER (child); - parser_data->attr_name = NULL; + attr_data = g_slice_new0 (AttributesSubParserData); + attr_data->cell_layout = GTK_CELL_LAYOUT (buildable); + attr_data->renderer = GTK_CELL_RENDERER (child); + attr_data->attr_name = NULL; *parser = attributes_parser; - *data = parser_data; + *data = attr_data; + return TRUE; + } + else if (strcmp (tagname, "cell-packing") == 0) + { + packing_data = g_slice_new0 (CellPackingSubParserData); + packing_data->builder = builder; + packing_data->cell_layout = GTK_CELL_LAYOUT (buildable); + packing_data->renderer = GTK_CELL_RENDERER (child); + + *parser = cell_packing_parser; + *data = packing_data; return TRUE; } return FALSE; } -void +gboolean _gtk_cell_layout_buildable_custom_tag_end (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *tagname, gpointer *data) { - AttributesSubParserData *parser_data; + AttributesSubParserData *attr_data; - parser_data = (AttributesSubParserData*)data; - g_assert (!parser_data->attr_name); - g_slice_free (AttributesSubParserData, parser_data); + if (strcmp (tagname, "attributes") == 0) + { + attr_data = (AttributesSubParserData*)data; + g_assert (!attr_data->attr_name); + g_slice_free (AttributesSubParserData, attr_data); + return TRUE; + } + else if (strcmp (tagname, "cell-packing") == 0) + { + g_slice_free (CellPackingSubParserData, (gpointer)data); + return TRUE; + } + return FALSE; } void @@ -412,12 +775,8 @@ _gtk_cell_layout_buildable_add_child (GtkBuildable *buildable, GObject *child, const gchar *type) { - GtkCellLayoutIface *iface; - g_return_if_fail (GTK_IS_CELL_LAYOUT (buildable)); g_return_if_fail (GTK_IS_CELL_RENDERER (child)); - iface = GTK_CELL_LAYOUT_GET_IFACE (buildable); - g_return_if_fail (iface->pack_start != NULL); - iface->pack_start (GTK_CELL_LAYOUT (buildable), GTK_CELL_RENDERER (child), FALSE); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (buildable), GTK_CELL_RENDERER (child), FALSE); } diff --git a/gtk/gtkcelllayout.h b/gtk/gtkcelllayout.h index 04e4745ee9..6ab3844542 100644 --- a/gtk/gtkcelllayout.h +++ b/gtk/gtkcelllayout.h @@ -25,6 +25,7 @@ #define __GTK_CELL_LAYOUT_H__ #include <gtk/gtkcellrenderer.h> +#include <gtk/gtkcellarea.h> #include <gtk/gtktreeviewcolumn.h> #include <gtk/gtkbuildable.h> #include <gtk/gtkbuilder.h> @@ -40,6 +41,17 @@ typedef struct _GtkCellLayout GtkCellLayout; /* dummy typedef */ typedef struct _GtkCellLayoutIface GtkCellLayoutIface; /* keep in sync with GtkTreeCellDataFunc */ +/** + * GtkCellLayoutDataFunc: + * @cell_layout: a #GtkCellLayout + * @cell: the cell renderer whose value is to be set + * @tree_model: the model + * @iter: a #GtkTreeIter indicating the row to set the value for + * @data: user data passed to gtk_cell_layout_set_cell_data_func() + * + * A function which should set the value of @cell_layout's cell renderer(s) + * as appropriate. + */ typedef void (* GtkCellLayoutDataFunc) (GtkCellLayout *cell_layout, GtkCellRenderer *cell, GtkTreeModel *tree_model, @@ -73,6 +85,8 @@ struct _GtkCellLayoutIface GtkCellRenderer *cell, gint position); GList* (* get_cells) (GtkCellLayout *cell_layout); + + GtkCellArea *(* get_area) (GtkCellLayout *cell_layout); }; GType gtk_cell_layout_get_type (void) G_GNUC_CONST; @@ -101,13 +115,15 @@ void gtk_cell_layout_clear_attributes (GtkCellLayout *cell_layout, void gtk_cell_layout_reorder (GtkCellLayout *cell_layout, GtkCellRenderer *cell, gint position); +GtkCellArea *gtk_cell_layout_get_area (GtkCellLayout *cell_layout); + gboolean _gtk_cell_layout_buildable_custom_tag_start (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *tagname, GMarkupParser *parser, gpointer *data); -void _gtk_cell_layout_buildable_custom_tag_end (GtkBuildable *buildable, +gboolean _gtk_cell_layout_buildable_custom_tag_end (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const gchar *tagname, diff --git a/gtk/gtkcellrenderer.c b/gtk/gtkcellrenderer.c index 05e60689d1..e15323e830 100644 --- a/gtk/gtkcellrenderer.c +++ b/gtk/gtkcellrenderer.c @@ -97,7 +97,11 @@ static void gtk_cell_renderer_real_get_preferred_width_for_height(GtkCellRendere gint height, gint *minimum_width, gint *natural_width); - +static void gtk_cell_renderer_real_get_aligned_area (GtkCellRenderer *cell, + GtkWidget *widget, + GtkCellRendererState flags, + const GdkRectangle *cell_area, + GdkRectangle *aligned_area); struct _GtkCellRendererPrivate @@ -192,6 +196,7 @@ gtk_cell_renderer_class_init (GtkCellRendererClass *class) class->get_preferred_height = gtk_cell_renderer_real_get_preferred_height; class->get_preferred_width_for_height = gtk_cell_renderer_real_get_preferred_width_for_height; class->get_preferred_height_for_width = gtk_cell_renderer_real_get_preferred_height_for_width; + class->get_aligned_area = gtk_cell_renderer_real_get_aligned_area; /** * GtkCellRenderer::editing-canceled: @@ -693,6 +698,9 @@ gtk_cell_renderer_render (GtkCellRenderer *cell, cairo_fill (cr); } + gdk_cairo_rectangle (cr, background_area); + cairo_clip (cr); + GTK_CELL_RENDERER_GET_CLASS (cell)->render (cell, cr, widget, @@ -1097,6 +1105,32 @@ gtk_cell_renderer_get_sensitive (GtkCellRenderer *cell) return cell->priv->sensitive; } + +/** + * gtk_cell_renderer_is_activatable: + * @cell: A #GtkCellRenderer + * + * Checks whether the cell renderer can do something when activated. + * + * Returns: %TRUE if the cell renderer can do anything when activated. + * + * Since: 3.0 + */ +gboolean +gtk_cell_renderer_is_activatable (GtkCellRenderer *cell) +{ + GtkCellRendererPrivate *priv; + + g_return_val_if_fail (GTK_IS_CELL_RENDERER (cell), FALSE); + + priv = cell->priv; + + return (cell->priv->visible && + (cell->priv->mode == GTK_CELL_RENDERER_MODE_EDITABLE || + cell->priv->mode == GTK_CELL_RENDERER_MODE_ACTIVATABLE)); +} + + /** * gtk_cell_renderer_stop_editing: * @cell: A #GtkCellRenderer @@ -1210,6 +1244,67 @@ gtk_cell_renderer_real_get_preferred_width_for_height (GtkCellRenderer *cell, gtk_cell_renderer_get_preferred_width (cell, widget, minimum_width, natural_width); } + +/* Default implementation assumes that a cell renderer will never use more + * space than it's natural size (this is fine for toggles and pixbufs etc + * but needs to be overridden from wrapping/ellipsizing text renderers) */ +static void +gtk_cell_renderer_real_get_aligned_area (GtkCellRenderer *cell, + GtkWidget *widget, + GtkCellRendererState flags, + const GdkRectangle *cell_area, + GdkRectangle *aligned_area) +{ + gint opposite_size, x_offset, y_offset; + gint natural_size; + + g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (cell_area != NULL); + g_return_if_fail (aligned_area != NULL); + + *aligned_area = *cell_area; + + /* Trim up the aligned size */ + if (gtk_cell_renderer_get_request_mode (cell) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH) + { + gtk_cell_renderer_get_preferred_width (cell, widget, + NULL, &natural_size); + + aligned_area->width = MIN (aligned_area->width, natural_size); + + gtk_cell_renderer_get_preferred_height_for_width (cell, widget, + aligned_area->width, + NULL, &opposite_size); + + aligned_area->height = MIN (opposite_size, aligned_area->height); + } + else + { + gtk_cell_renderer_get_preferred_height (cell, widget, + NULL, &natural_size); + + aligned_area->height = MIN (aligned_area->width, natural_size); + + gtk_cell_renderer_get_preferred_width_for_height (cell, widget, + aligned_area->height, + NULL, &opposite_size); + + aligned_area->width = MIN (opposite_size, aligned_area->width); + } + + /* offset the cell position */ + _gtk_cell_renderer_calc_offset (cell, cell_area, + gtk_widget_get_direction (widget), + aligned_area->width, + aligned_area->height, + &x_offset, &y_offset); + + aligned_area->x += x_offset; + aligned_area->y += y_offset; +} + + /* An internal convenience function for some containers to peek at the * cell alignment in a target allocation (used to draw focus and align * cells in the icon view). @@ -1535,3 +1630,35 @@ gtk_cell_renderer_get_preferred_size (GtkCellRenderer *cell, } } } + +/** + * gtk_cell_renderer_get_aligned_area: + * @cell: a #GtkCellRenderer instance + * @widget: the #GtkWidget this cell will be rendering to + * @flags: render flags + * @cell_area: cell area which would be passed to gtk_cell_renderer_render() + * @aligned_area: the return location for the space inside @cell_area that + * would acually be used to render. + * + * Gets the aligned area used by @cell inside @cell_area. Used for finding + * the appropriate edit and focus rectangle. + * + * Since: 3.0 + */ +void +gtk_cell_renderer_get_aligned_area (GtkCellRenderer *cell, + GtkWidget *widget, + GtkCellRendererState flags, + const GdkRectangle *cell_area, + GdkRectangle *aligned_area) +{ + GtkCellRendererClass *klass; + + g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (cell_area != NULL); + g_return_if_fail (aligned_area != NULL); + + klass = GTK_CELL_RENDERER_GET_CLASS (cell); + klass->get_aligned_area (cell, widget, flags, cell_area, aligned_area); +} diff --git a/gtk/gtkcellrenderer.h b/gtk/gtkcellrenderer.h index d2abd128bd..22e86379cf 100644 --- a/gtk/gtkcellrenderer.h +++ b/gtk/gtkcellrenderer.h @@ -111,6 +111,11 @@ struct _GtkCellRendererClass gint height, gint *minimum_width, gint *natural_width); + void (* get_aligned_area) (GtkCellRenderer *cell, + GtkWidget *widget, + GtkCellRendererState flags, + const GdkRectangle *cell_area, + GdkRectangle *aligned_area); void (* get_size) (GtkCellRenderer *cell, GtkWidget *widget, const GdkRectangle *cell_area, @@ -177,6 +182,12 @@ void gtk_cell_renderer_get_preferred_size (GtkCellRend GtkWidget *widget, GtkRequisition *minimum_size, GtkRequisition *natural_size); +void gtk_cell_renderer_get_aligned_area (GtkCellRenderer *cell, + GtkWidget *widget, + GtkCellRendererState flags, + const GdkRectangle *cell_area, + GdkRectangle *aligned_area); + #ifndef GTK_DISABLE_DEPRECATED void gtk_cell_renderer_get_size (GtkCellRenderer *cell, GtkWidget *widget, @@ -236,6 +247,8 @@ void gtk_cell_renderer_set_sensitive (GtkCellRenderer *cell, gboolean sensitive); gboolean gtk_cell_renderer_get_sensitive (GtkCellRenderer *cell); +gboolean gtk_cell_renderer_is_activatable (GtkCellRenderer *cell); + /* For use by cell renderer implementations only */ void gtk_cell_renderer_stop_editing (GtkCellRenderer *cell, gboolean canceled); diff --git a/gtk/gtkcellrendererspinner.c b/gtk/gtkcellrendererspinner.c index 5d50f8dfdd..bc63b5ea90 100644 --- a/gtk/gtkcellrendererspinner.c +++ b/gtk/gtkcellrendererspinner.c @@ -372,6 +372,11 @@ gtk_cell_renderer_spinner_render (GtkCellRenderer *cellr, state = GTK_STATE_PRELIGHT; } + cairo_save (cr); + + gdk_cairo_rectangle (cr, cell_area); + cairo_clip (cr); + gtk_paint_spinner (gtk_widget_get_style (widget), cr, state, @@ -380,4 +385,6 @@ gtk_cell_renderer_spinner_render (GtkCellRenderer *cellr, priv->pulse, draw_rect.x, draw_rect.y, draw_rect.width, draw_rect.height); + + cairo_restore (cr); } diff --git a/gtk/gtkcellrenderertext.c b/gtk/gtkcellrenderertext.c index cf58d4dacb..2cff3a47c3 100644 --- a/gtk/gtkcellrenderertext.c +++ b/gtk/gtkcellrenderertext.c @@ -84,6 +84,13 @@ static void gtk_cell_renderer_text_get_preferred_height_for_width (GtkCell gint width, gint *minimum_height, gint *natural_height); +static void gtk_cell_renderer_text_get_aligned_area (GtkCellRenderer *cell, + GtkWidget *widget, + GtkCellRendererState flags, + const GdkRectangle *cell_area, + GdkRectangle *aligned_area); + + enum { EDITED, @@ -240,6 +247,7 @@ gtk_cell_renderer_text_class_init (GtkCellRendererTextClass *class) cell_class->get_preferred_width = gtk_cell_renderer_text_get_preferred_width; cell_class->get_preferred_height = gtk_cell_renderer_text_get_preferred_height; cell_class->get_preferred_height_for_width = gtk_cell_renderer_text_get_preferred_height_for_width; + cell_class->get_aligned_area = gtk_cell_renderer_text_get_aligned_area; g_object_class_install_property (object_class, PROP_TEXT, @@ -1731,30 +1739,8 @@ get_size (GtkCellRenderer *cell, if (height) *height = ypad * 2 + rect.height; - /* The minimum size for ellipsized labels is ~ 3 chars */ if (width) - { - if (priv->ellipsize || priv->width_chars > 0) - { - PangoContext *context; - PangoFontMetrics *metrics; - gint char_width; - - context = pango_layout_get_context (layout); - metrics = pango_context_get_metrics (context, - gtk_widget_get_style (widget)->font_desc, - pango_context_get_language (context)); - - char_width = pango_font_metrics_get_approximate_char_width (metrics); - pango_font_metrics_unref (metrics); - - *width = xpad * 2 + (PANGO_PIXELS (char_width) * MAX (priv->width_chars, 3)); - } - else - { - *width = xpad * 2 + rect.x + rect.width; - } - } + *width = xpad * 2 + rect.x + rect.width; if (cell_area) { @@ -1847,6 +1833,11 @@ gtk_cell_renderer_text_render (GtkCellRenderer *cell, else if (priv->wrap_width == -1) pango_layout_set_width (layout, -1); + cairo_save (cr); + + gdk_cairo_rectangle (cr, cell_area); + cairo_clip (cr); + gtk_paint_layout (gtk_widget_get_style (widget), cr, state, @@ -1857,6 +1848,8 @@ gtk_cell_renderer_text_render (GtkCellRenderer *cell, cell_area->y + y_offset + ypad, layout); + cairo_restore (cr); + g_object_unref (layout); } @@ -2117,7 +2110,7 @@ gtk_cell_renderer_text_get_preferred_width (GtkCellRenderer *cell, PangoContext *context; PangoFontMetrics *metrics; PangoRectangle rect; - gint char_width, digit_width, char_pixels, text_width, ellipsize_chars, guess_width, xpad; + gint char_width, digit_width, char_pixels, text_width, ellipsize_chars, xpad; gint min_width, nat_width; /* "width-chars" Hard-coded minimum width: @@ -2138,10 +2131,6 @@ gtk_cell_renderer_text_get_preferred_width (GtkCellRenderer *cell, layout = get_layout (celltext, widget, NULL, 0); - /* Get the layout with the text possibly wrapping at wrap_width */ - pango_layout_get_pixel_extents (layout, NULL, &rect); - guess_width = rect.width; - /* Fetch the length of the complete unwrapped text */ pango_layout_set_width (layout, -1); pango_layout_get_extents (layout, NULL, &rect); @@ -2167,12 +2156,14 @@ gtk_cell_renderer_text_get_preferred_width (GtkCellRenderer *cell, if ((priv->ellipsize_set && priv->ellipsize != PANGO_ELLIPSIZE_NONE) || priv->width_chars > 0) min_width = - xpad * 2 + (PANGO_PIXELS (char_width) * MAX (priv->width_chars, ellipsize_chars)); + xpad * 2 + + MIN (PANGO_PIXELS (text_width), + (PANGO_PIXELS (char_width) * MAX (priv->width_chars, ellipsize_chars))); /* If no width-chars set, minimum for wrapping text will be the wrap-width */ else if (priv->wrap_width > -1) - min_width = xpad * 2 + rect.x + priv->wrap_width; + min_width = xpad * 2 + rect.x + MIN (PANGO_PIXELS (text_width), priv->wrap_width); else - min_width = xpad * 2 + rect.x + guess_width; + min_width = xpad * 2 + rect.x + PANGO_PIXELS (text_width); if (priv->width_chars > 0) nat_width = xpad * 2 + @@ -2180,6 +2171,7 @@ gtk_cell_renderer_text_get_preferred_width (GtkCellRenderer *cell, else nat_width = xpad * 2 + PANGO_PIXELS (text_width); + nat_width = MAX (nat_width, min_width); if (priv->max_width_chars > 0) @@ -2195,7 +2187,6 @@ gtk_cell_renderer_text_get_preferred_width (GtkCellRenderer *cell, if (natural_size) *natural_size = nat_width; - } static void @@ -2250,3 +2241,24 @@ gtk_cell_renderer_text_get_preferred_height (GtkCellRenderer *cell, minimum_size, natural_size); } +static void +gtk_cell_renderer_text_get_aligned_area (GtkCellRenderer *cell, + GtkWidget *widget, + GtkCellRendererState flags, + const GdkRectangle *cell_area, + GdkRectangle *aligned_area) +{ + GtkCellRendererText *celltext = GTK_CELL_RENDERER_TEXT (cell); + PangoLayout *layout; + gint x_offset = 0; + gint y_offset = 0; + + layout = get_layout (celltext, widget, cell_area, flags); + get_size (cell, widget, cell_area, layout, &x_offset, &y_offset, + &aligned_area->width, &aligned_area->height); + + aligned_area->x = cell_area->x + x_offset; + aligned_area->y = cell_area->y + y_offset; + + g_object_unref (layout); +} diff --git a/gtk/gtkcellrenderertoggle.c b/gtk/gtkcellrenderertoggle.c index e1aa78e03f..177cafd32a 100644 --- a/gtk/gtkcellrenderertoggle.c +++ b/gtk/gtkcellrenderertoggle.c @@ -372,6 +372,11 @@ gtk_cell_renderer_toggle_render (GtkCellRenderer *cell, state = GTK_STATE_INSENSITIVE; } + cairo_save (cr); + + gdk_cairo_rectangle (cr, cell_area); + cairo_clip (cr); + if (priv->radio) { gtk_paint_option (gtk_widget_get_style (widget), @@ -392,6 +397,8 @@ gtk_cell_renderer_toggle_render (GtkCellRenderer *cell, cell_area->y + y_offset + ypad, width, height); } + + cairo_restore (cr); } static gint diff --git a/gtk/gtkcombobox.c b/gtk/gtkcombobox.c index 4775fe930d..15c8df31f6 100644 --- a/gtk/gtkcombobox.c +++ b/gtk/gtkcombobox.c @@ -4415,10 +4415,12 @@ gtk_combo_box_list_select_func (GtkTreeSelection *selection, gboolean path_currently_selected, gpointer data) { - GList *list; + GList *list, *columns; gboolean sensitive = FALSE; - for (list = selection->tree_view->priv->columns; list && !sensitive; list = list->next) + columns = gtk_tree_view_get_columns (selection->tree_view); + + for (list = columns; list && !sensitive; list = list->next) { GList *cells, *cell; gboolean cell_sensitive, cell_visible; @@ -4450,6 +4452,8 @@ gtk_combo_box_list_select_func (GtkTreeSelection *selection, sensitive = cell_sensitive; } + g_list_free (columns); + return sensitive; } diff --git a/gtk/gtkentrycompletion.c b/gtk/gtkentrycompletion.c index a14303588b..0808c1059f 100644 --- a/gtk/gtkentrycompletion.c +++ b/gtk/gtkentrycompletion.c @@ -23,6 +23,7 @@ #include "gtkentryprivate.h" #include "gtkcelllayout.h" +#include "gtkcellareabox.h" #include "gtkintl.h" #include "gtkcellrenderertext.h" @@ -63,11 +64,17 @@ enum PROP_POPUP_COMPLETION, PROP_POPUP_SET_WIDTH, PROP_POPUP_SINGLE_MATCH, - PROP_INLINE_SELECTION + PROP_INLINE_SELECTION, + PROP_CELL_AREA }; -static void gtk_entry_completion_cell_layout_init (GtkCellLayoutIface *iface); +static void gtk_entry_completion_cell_layout_init (GtkCellLayoutIface *iface); +static GtkCellArea* gtk_entry_completion_get_area (GtkCellLayout *cell_layout); + +static GObject *gtk_entry_completion_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties); static void gtk_entry_completion_set_property (GObject *object, guint prop_id, const GValue *value, @@ -76,30 +83,8 @@ static void gtk_entry_completion_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); -static void gtk_entry_completion_finalize (GObject *object); - -static void gtk_entry_completion_pack_start (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gboolean expand); -static void gtk_entry_completion_pack_end (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gboolean expand); -static void gtk_entry_completion_clear (GtkCellLayout *cell_layout); -static void gtk_entry_completion_add_attribute (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - const char *attribute, - gint column); -static void gtk_entry_completion_set_cell_data_func (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - GtkCellLayoutDataFunc func, - gpointer func_data, - GDestroyNotify destroy); -static void gtk_entry_completion_clear_attributes (GtkCellLayout *cell_layout, - GtkCellRenderer *cell); -static void gtk_entry_completion_reorder (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gint position); -static GList * gtk_entry_completion_get_cells (GtkCellLayout *cell_layout); +static void gtk_entry_completion_finalize (GObject *object); +static void gtk_entry_completion_dispose (GObject *object); static gboolean gtk_entry_completion_visible_func (GtkTreeModel *model, GtkTreeIter *iter, @@ -167,8 +152,10 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) object_class = (GObjectClass *)klass; + object_class->constructor = gtk_entry_completion_constructor; object_class->set_property = gtk_entry_completion_set_property; object_class->get_property = gtk_entry_completion_get_property; + object_class->dispose = gtk_entry_completion_dispose; object_class->finalize = gtk_entry_completion_finalize; klass->match_selected = gtk_entry_completion_match_selected; @@ -391,37 +378,55 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) FALSE, GTK_PARAM_READWRITE)); + + /** + * GtkEntryCompletion:cell-area: + * + * The #GtkCellArea used to layout cell renderers in the treeview column. + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_CELL_AREA, + g_param_spec_object ("cell-area", + P_("Cell Area"), + P_("The GtkCellArea used to layout cells"), + GTK_TYPE_CELL_AREA, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_type_class_add_private (object_class, sizeof (GtkEntryCompletionPrivate)); } + +static void +gtk_entry_completion_buildable_custom_tag_end (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + gpointer *data) +{ + /* Just ignore the boolean return from here */ + _gtk_cell_layout_buildable_custom_tag_end (buildable, builder, child, tagname, data); +} + static void gtk_entry_completion_buildable_init (GtkBuildableIface *iface) { iface->add_child = _gtk_cell_layout_buildable_add_child; iface->custom_tag_start = _gtk_cell_layout_buildable_custom_tag_start; - iface->custom_tag_end = _gtk_cell_layout_buildable_custom_tag_end; + iface->custom_tag_end = gtk_entry_completion_buildable_custom_tag_end; } static void gtk_entry_completion_cell_layout_init (GtkCellLayoutIface *iface) { - iface->pack_start = gtk_entry_completion_pack_start; - iface->pack_end = gtk_entry_completion_pack_end; - iface->clear = gtk_entry_completion_clear; - iface->add_attribute = gtk_entry_completion_add_attribute; - iface->set_cell_data_func = gtk_entry_completion_set_cell_data_func; - iface->clear_attributes = gtk_entry_completion_clear_attributes; - iface->reorder = gtk_entry_completion_reorder; - iface->get_cells = gtk_entry_completion_get_cells; + iface->get_area = gtk_entry_completion_get_area; } static void gtk_entry_completion_init (GtkEntryCompletion *completion) { - GtkCellRenderer *cell; - GtkTreeSelection *sel; GtkEntryCompletionPrivate *priv; - GtkWidget *popup_frame; /* yes, also priv, need to keep the code readable */ completion->priv = G_TYPE_INSTANCE_GET_PRIVATE (completion, @@ -438,9 +443,34 @@ gtk_entry_completion_init (GtkEntryCompletion *completion) priv->popup_single_match = TRUE; priv->inline_selection = FALSE; - /* completions */ priv->filter_model = NULL; +} + +static GObject * +gtk_entry_completion_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GtkEntryCompletion *completion; + GtkEntryCompletionPrivate *priv; + GObject *object; + GtkCellRenderer *cell; + GtkTreeSelection *sel; + GtkWidget *popup_frame; + + object = G_OBJECT_CLASS (gtk_entry_completion_parent_class)->constructor + (type, n_construct_properties, construct_properties); + + completion = (GtkEntryCompletion *) object; + priv = completion->priv; + + if (!priv->cell_area) + { + priv->cell_area = gtk_cell_area_box_new (); + g_object_ref_sink (priv->cell_area); + } + /* completions */ priv->tree_view = gtk_tree_view_new (); g_signal_connect (priv->tree_view, "button-press-event", G_CALLBACK (gtk_entry_completion_list_button_press), @@ -463,7 +493,7 @@ gtk_entry_completion_init (GtkEntryCompletion *completion) completion); priv->first_sel_changed = TRUE; - priv->column = gtk_tree_view_column_new (); + priv->column = gtk_tree_view_column_new_with_area (priv->cell_area); gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view), priv->column); priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL); @@ -539,8 +569,11 @@ gtk_entry_completion_init (GtkEntryCompletion *completion) * been inserted, so we pack the action treeview after the first * action has been added */ + + return object; } + static void gtk_entry_completion_set_property (GObject *object, guint prop_id, @@ -549,6 +582,7 @@ gtk_entry_completion_set_property (GObject *object, { GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object); GtkEntryCompletionPrivate *priv = completion->priv; + GtkCellArea *area; switch (prop_id) { @@ -585,6 +619,14 @@ gtk_entry_completion_set_property (GObject *object, case PROP_INLINE_SELECTION: priv->inline_selection = g_value_get_boolean (value); break; + + case PROP_CELL_AREA: + /* Construct-only, can only be assigned once */ + area = g_value_get_object (value); + + if (area) + priv->cell_area = g_object_ref_sink (area); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -635,6 +677,10 @@ gtk_entry_completion_get_property (GObject *object, g_value_set_boolean (value, gtk_entry_completion_get_inline_selection (completion)); break; + case PROP_CELL_AREA: + g_value_set_object (value, completion->priv->cell_area); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -647,123 +693,66 @@ gtk_entry_completion_finalize (GObject *object) GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object); GtkEntryCompletionPrivate *priv = completion->priv; - if (priv->tree_view) - gtk_widget_destroy (priv->tree_view); - - if (priv->entry) - gtk_entry_set_completion (GTK_ENTRY (priv->entry), NULL); - - if (priv->actions) - g_object_unref (priv->actions); - if (priv->action_view) - g_object_unref (priv->action_view); - g_free (priv->case_normalized_key); g_free (priv->completion_prefix); - if (priv->popup_window) - gtk_widget_destroy (priv->popup_window); - if (priv->match_notify) (* priv->match_notify) (priv->match_data); G_OBJECT_CLASS (gtk_entry_completion_parent_class)->finalize (object); } -/* implement cell layout interface */ -static void -gtk_entry_completion_pack_start (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gboolean expand) -{ - GtkEntryCompletionPrivate *priv; - - priv = GTK_ENTRY_COMPLETION (cell_layout)->priv; - - gtk_tree_view_column_pack_start (priv->column, cell, expand); -} - -static void -gtk_entry_completion_pack_end (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gboolean expand) -{ - GtkEntryCompletionPrivate *priv; - - priv = GTK_ENTRY_COMPLETION (cell_layout)->priv; - - gtk_tree_view_column_pack_end (priv->column, cell, expand); -} - -static void -gtk_entry_completion_clear (GtkCellLayout *cell_layout) -{ - GtkEntryCompletionPrivate *priv; - - priv = GTK_ENTRY_COMPLETION (cell_layout)->priv; - - gtk_tree_view_column_clear (priv->column); -} - static void -gtk_entry_completion_add_attribute (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - const gchar *attribute, - gint column) +gtk_entry_completion_dispose (GObject *object) { - GtkEntryCompletionPrivate *priv; - - priv = GTK_ENTRY_COMPLETION (cell_layout)->priv; - - gtk_tree_view_column_add_attribute (priv->column, cell, attribute, column); -} - -static void -gtk_entry_completion_set_cell_data_func (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - GtkCellLayoutDataFunc func, - gpointer func_data, - GDestroyNotify destroy) -{ - GtkEntryCompletionPrivate *priv; - - priv = GTK_ENTRY_COMPLETION (cell_layout)->priv; + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object); + GtkEntryCompletionPrivate *priv = completion->priv; - gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->column), - cell, func, func_data, destroy); -} + if (priv->tree_view) + { + gtk_widget_destroy (priv->tree_view); + priv->tree_view = NULL; + } -static void -gtk_entry_completion_clear_attributes (GtkCellLayout *cell_layout, - GtkCellRenderer *cell) -{ - GtkEntryCompletionPrivate *priv; + if (priv->entry) + gtk_entry_set_completion (GTK_ENTRY (priv->entry), NULL); - priv = GTK_ENTRY_COMPLETION (cell_layout)->priv; + if (priv->actions) + { + g_object_unref (priv->actions); + priv->actions = NULL; + } - gtk_tree_view_column_clear_attributes (priv->column, cell); -} + if (priv->action_view) + { + g_object_unref (priv->action_view); + priv->action_view = NULL; + } -static void -gtk_entry_completion_reorder (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gint position) -{ - GtkEntryCompletionPrivate *priv; + if (priv->popup_window) + { + gtk_widget_destroy (priv->popup_window); + priv->popup_window = NULL; + } - priv = GTK_ENTRY_COMPLETION (cell_layout)->priv; + if (priv->cell_area) + { + g_object_unref (priv->cell_area); + priv->cell_area = NULL; + } - gtk_cell_layout_reorder (GTK_CELL_LAYOUT (priv->column), cell, position); + G_OBJECT_CLASS (gtk_entry_completion_parent_class)->dispose (object); } -static GList * -gtk_entry_completion_get_cells (GtkCellLayout *cell_layout) +/* implement cell layout interface (only need to return the underlying cell area) */ +static GtkCellArea* +gtk_entry_completion_get_area (GtkCellLayout *cell_layout) { GtkEntryCompletionPrivate *priv; priv = GTK_ENTRY_COMPLETION (cell_layout)->priv; - return gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->column)); + return priv->cell_area; } /* all those callbacks */ @@ -1007,6 +996,28 @@ gtk_entry_completion_new (void) } /** + * gtk_entry_completion_new_with_area: + * @area: the #GtkCellArea used to layout cells + * + * Creates a new #GtkEntryCompletion object using the + * specified @area to layout cells in the underlying + * #GtkTreeViewColumn for the drop-down menu. + * + * Return value: A newly created #GtkEntryCompletion object. + * + * Since: 3.0 + */ +GtkEntryCompletion * +gtk_entry_completion_new_with_area (GtkCellArea *area) +{ + GtkEntryCompletion *completion; + + completion = g_object_new (GTK_TYPE_ENTRY_COMPLETION, "cell-area", area, NULL); + + return completion; +} + +/** * gtk_entry_completion_get_entry: * @completion: A #GtkEntryCompletion. * diff --git a/gtk/gtkentrycompletion.h b/gtk/gtkentrycompletion.h index f120c3a679..a5ba4226c9 100644 --- a/gtk/gtkentrycompletion.h +++ b/gtk/gtkentrycompletion.h @@ -26,6 +26,7 @@ #include <gtk/gtktreemodel.h> #include <gtk/gtkliststore.h> +#include <gtk/gtkcellarea.h> #include <gtk/gtktreeviewcolumn.h> #include <gtk/gtktreemodelfilter.h> @@ -81,6 +82,7 @@ struct _GtkEntryCompletionClass /* core */ GType gtk_entry_completion_get_type (void) G_GNUC_CONST; GtkEntryCompletion *gtk_entry_completion_new (void); +GtkEntryCompletion *gtk_entry_completion_new_with_area (GtkCellArea *area); GtkWidget *gtk_entry_completion_get_entry (GtkEntryCompletion *completion); diff --git a/gtk/gtkentryprivate.h b/gtk/gtkentryprivate.h index 01c2c7d014..59f24e6e5e 100644 --- a/gtk/gtkentryprivate.h +++ b/gtk/gtkentryprivate.h @@ -37,6 +37,7 @@ struct _GtkEntryCompletionPrivate GtkTreeModelFilter *filter_model; GtkListStore *actions; gboolean first_sel_changed; + GtkCellArea *cell_area; GtkEntryCompletionMatchFunc match_func; gpointer match_data; diff --git a/gtk/gtkfilechooserdefault.c b/gtk/gtkfilechooserdefault.c index 360793b122..ef98afef50 100644 --- a/gtk/gtkfilechooserdefault.c +++ b/gtk/gtkfilechooserdefault.c @@ -2998,14 +2998,18 @@ shortcuts_compute_drop_position (GtkFileChooserDefault *impl, GdkRectangle cell; int row; int bookmarks_index; + int header_height = 0; tree_view = GTK_TREE_VIEW (impl->browse_shortcuts_tree_view); + if (gtk_tree_view_get_headers_visible (tree_view)) + header_height = _gtk_tree_view_get_header_height (tree_view); + bookmarks_index = shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS); if (!gtk_tree_view_get_path_at_pos (tree_view, x, - y - TREE_VIEW_HEADER_HEIGHT (tree_view), + y - header_height, path, &column, NULL, diff --git a/gtk/gtkmarshalers.list b/gtk/gtkmarshalers.list index 10f0dfd689..fa60a7c91d 100644 --- a/gtk/gtkmarshalers.list +++ b/gtk/gtkmarshalers.list @@ -69,6 +69,7 @@ VOID:ENUM,FLOAT,BOOLEAN VOID:ENUM,INT VOID:ENUM,INT,BOOLEAN VOID:ENUM,BOXED +VOID:ENUM,STRING VOID:FLAGS VOID:INT VOID:INT,BOOLEAN @@ -79,6 +80,7 @@ VOID:OBJECT VOID:OBJECT,BOOLEAN VOID:OBJECT,BOXED,BOXED VOID:OBJECT,BOXED,UINT,UINT +VOID:OBJECT,BOXED,BOOLEAN,BOOLEAN VOID:OBJECT,INT VOID:OBJECT,INT,OBJECT VOID:OBJECT,INT,INT @@ -90,7 +92,9 @@ VOID:OBJECT,STRING,STRING VOID:OBJECT,UINT VOID:OBJECT,UINT,FLAGS VOID:OBJECT,STRING +VOID:OBJECT,OBJECT,STRING VOID:OBJECT,OBJECT,OBJECT +VOID:OBJECT,OBJECT,BOXED,STRING VOID:POINTER VOID:POINTER,INT VOID:POINTER,BOOLEAN diff --git a/gtk/gtktreeprivate.h b/gtk/gtktreeprivate.h index 2304f69ab7..9ee61f1ef9 100644 --- a/gtk/gtktreeprivate.h +++ b/gtk/gtktreeprivate.h @@ -32,353 +32,11 @@ G_BEGIN_DECLS typedef enum { - GTK_TREE_VIEW_IS_LIST = 1 << 0, - GTK_TREE_VIEW_SHOW_EXPANDERS = 1 << 1, - GTK_TREE_VIEW_IN_COLUMN_RESIZE = 1 << 2, - GTK_TREE_VIEW_ARROW_PRELIT = 1 << 3, - GTK_TREE_VIEW_HEADERS_VISIBLE = 1 << 4, - GTK_TREE_VIEW_DRAW_KEYFOCUS = 1 << 5, - GTK_TREE_VIEW_MODEL_SETUP = 1 << 6, - GTK_TREE_VIEW_IN_COLUMN_DRAG = 1 << 7 -} GtkTreeViewFlags; - -typedef enum -{ GTK_TREE_SELECT_MODE_TOGGLE = 1 << 0, GTK_TREE_SELECT_MODE_EXTEND = 1 << 1 } GtkTreeSelectMode; -enum -{ - DRAG_COLUMN_WINDOW_STATE_UNSET = 0, - DRAG_COLUMN_WINDOW_STATE_ORIGINAL = 1, - DRAG_COLUMN_WINDOW_STATE_ARROW = 2, - DRAG_COLUMN_WINDOW_STATE_ARROW_LEFT = 3, - DRAG_COLUMN_WINDOW_STATE_ARROW_RIGHT = 4 -}; - -enum -{ - RUBBER_BAND_OFF = 0, - RUBBER_BAND_MAYBE_START = 1, - RUBBER_BAND_ACTIVE = 2 -}; - -#define GTK_TREE_VIEW_SET_FLAG(tree_view, flag) G_STMT_START{ (tree_view->priv->flags|=flag); }G_STMT_END -#define GTK_TREE_VIEW_UNSET_FLAG(tree_view, flag) G_STMT_START{ (tree_view->priv->flags&=~(flag)); }G_STMT_END -#define GTK_TREE_VIEW_FLAG_SET(tree_view, flag) ((tree_view->priv->flags&flag)==flag) -#define TREE_VIEW_HEADER_HEIGHT(tree_view) (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_HEADERS_VISIBLE)?tree_view->priv->header_height:0) -#define TREE_VIEW_COLUMN_REQUESTED_WIDTH(column) (CLAMP (column->requested_width, (column->min_width!=-1)?column->min_width:column->requested_width, (column->max_width!=-1)?column->max_width:column->requested_width)) -#define TREE_VIEW_DRAW_EXPANDERS(tree_view) (!GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IS_LIST)&>K_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_SHOW_EXPANDERS)) - - /* This lovely little value is used to determine how far away from the title bar - * you can move the mouse and still have a column drag work. - */ -#define TREE_VIEW_COLUMN_DRAG_DEAD_MULTIPLIER(tree_view) (10*TREE_VIEW_HEADER_HEIGHT(tree_view)) - -typedef struct _GtkTreeViewColumnReorder GtkTreeViewColumnReorder; -struct _GtkTreeViewColumnReorder -{ - gint left_align; - gint right_align; - GtkTreeViewColumn *left_column; - GtkTreeViewColumn *right_column; -}; - -struct _GtkTreeViewPrivate -{ - GtkTreeModel *model; - - guint flags; - /* tree information */ - GtkRBTree *tree; - - /* Container info */ - GList *children; - gint width; - gint height; - - /* Adjustments */ - GtkAdjustment *hadjustment; - GtkAdjustment *vadjustment; - gint min_display_width; - gint min_display_height; - - /* Sub windows */ - GdkWindow *bin_window; - GdkWindow *header_window; - - /* Scroll position state keeping */ - GtkTreeRowReference *top_row; - gint top_row_dy; - /* dy == y pos of top_row + top_row_dy */ - /* we cache it for simplicity of the code */ - gint dy; - - guint presize_handler_timer; - guint validate_rows_timer; - guint scroll_sync_timer; - - /* Indentation and expander layout */ - gint expander_size; - GtkTreeViewColumn *expander_column; - - gint level_indentation; - - /* Key navigation (focus), selection */ - gint cursor_offset; - - GtkTreeRowReference *anchor; - GtkTreeRowReference *cursor; - - GtkTreeViewColumn *focus_column; - - /* Current pressed node, previously pressed, prelight */ - GtkRBNode *button_pressed_node; - GtkRBTree *button_pressed_tree; - - gint pressed_button; - gint press_start_x; - gint press_start_y; - - gint event_last_x; - gint event_last_y; - - guint last_button_time; - gint last_button_x; - gint last_button_y; - - GtkRBNode *prelight_node; - GtkRBTree *prelight_tree; - - /* Cell Editing */ - GtkTreeViewColumn *edited_column; - - /* The node that's currently being collapsed or expanded */ - GtkRBNode *expanded_collapsed_node; - GtkRBTree *expanded_collapsed_tree; - guint expand_collapse_timeout; - - /* Auto expand/collapse timeout in hover mode */ - guint auto_expand_timeout; - - /* Selection information */ - GtkTreeSelection *selection; - - /* Header information */ - gint n_columns; - GList *columns; - gint header_height; - - GtkTreeViewColumnDropFunc column_drop_func; - gpointer column_drop_func_data; - GDestroyNotify column_drop_func_data_destroy; - GList *column_drag_info; - GtkTreeViewColumnReorder *cur_reorder; - - gint prev_width_before_expander; - - /* Interactive Header reordering */ - GdkWindow *drag_window; - GdkWindow *drag_highlight_window; - GtkTreeViewColumn *drag_column; - gint drag_column_x; - - /* Interactive Header Resizing */ - gint drag_pos; - gint x_drag; - - /* Non-interactive Header Resizing, expand flag support */ - gint prev_width; - - gint last_extra_space; - gint last_extra_space_per_column; - gint last_number_of_expand_columns; - - /* ATK Hack */ - GtkTreeDestroyCountFunc destroy_count_func; - gpointer destroy_count_data; - GDestroyNotify destroy_count_destroy; - - /* Scroll timeout (e.g. during dnd, rubber banding) */ - guint scroll_timeout; - - /* Row drag-and-drop */ - GtkTreeRowReference *drag_dest_row; - GtkTreeViewDropPosition drag_dest_pos; - guint open_dest_timeout; - - /* Rubber banding */ - gint rubber_band_status; - gint rubber_band_x; - gint rubber_band_y; - gint rubber_band_shift; - gint rubber_band_ctrl; - - GtkRBNode *rubber_band_start_node; - GtkRBTree *rubber_band_start_tree; - - GtkRBNode *rubber_band_end_node; - GtkRBTree *rubber_band_end_tree; - - /* fixed height */ - gint fixed_height; - - /* Scroll-to functionality when unrealized */ - GtkTreeRowReference *scroll_to_path; - GtkTreeViewColumn *scroll_to_column; - gfloat scroll_to_row_align; - gfloat scroll_to_col_align; - - /* Interactive search */ - gint selected_iter; - gint search_column; - GtkTreeViewSearchPositionFunc search_position_func; - GtkTreeViewSearchEqualFunc search_equal_func; - gpointer search_user_data; - GDestroyNotify search_destroy; - gpointer search_position_user_data; - GDestroyNotify search_position_destroy; - GtkWidget *search_window; - GtkWidget *search_entry; - gulong search_entry_changed_id; - guint typeselect_flush_timeout; - - /* Grid and tree lines */ - GtkTreeViewGridLines grid_lines; - double grid_line_dashes[2]; - int grid_line_width; - - gboolean tree_lines_enabled; - double tree_line_dashes[2]; - int tree_line_width; - - /* Row separators */ - GtkTreeViewRowSeparatorFunc row_separator_func; - gpointer row_separator_data; - GDestroyNotify row_separator_destroy; - - /* Tooltip support */ - gint tooltip_column; - - /* Here comes the bitfield */ - guint scroll_to_use_align : 1; - - guint fixed_height_mode : 1; - guint fixed_height_check : 1; - - guint reorderable : 1; - guint header_has_focus : 1; - guint drag_column_window_state : 3; - /* hint to display rows in alternating colors */ - guint has_rules : 1; - guint mark_rows_col_dirty : 1; - - /* for DnD */ - guint empty_view_drop : 1; - - guint ctrl_pressed : 1; - guint shift_pressed : 1; - - guint init_hadjust_value : 1; - - guint in_top_row_to_dy : 1; - - /* interactive search */ - guint enable_search : 1; - guint disable_popdown : 1; - guint search_custom_entry_set : 1; - - guint hover_selection : 1; - guint hover_expand : 1; - guint imcontext_changed : 1; - - guint rubber_banding_enable : 1; - - guint in_grab : 1; - - guint post_validation_flag : 1; - - /* Whether our key press handler is to avoid sending an unhandled binding to the search entry */ - guint search_entry_avoid_unhandled_binding : 1; - - /* GtkScrollablePolicy needs to be checked when - * driving the scrollable adjustment values */ - guint hscroll_policy : 1; - guint vscroll_policy : 1; -}; - -#ifdef __GNUC__ - -#define TREE_VIEW_INTERNAL_ASSERT(expr, ret) G_STMT_START{ \ - if (!(expr)) \ - { \ - g_log (G_LOG_DOMAIN, \ - G_LOG_LEVEL_CRITICAL, \ - "%s (%s): assertion `%s' failed.\n" \ - "There is a disparity between the internal view of the GtkTreeView,\n" \ - "and the GtkTreeModel. This generally means that the model has changed\n"\ - "without letting the view know. Any display from now on is likely to\n" \ - "be incorrect.\n", \ - G_STRLOC, \ - G_STRFUNC, \ - #expr); \ - return ret; \ - }; }G_STMT_END - -#define TREE_VIEW_INTERNAL_ASSERT_VOID(expr) G_STMT_START{ \ - if (!(expr)) \ - { \ - g_log (G_LOG_DOMAIN, \ - G_LOG_LEVEL_CRITICAL, \ - "%s (%s): assertion `%s' failed.\n" \ - "There is a disparity between the internal view of the GtkTreeView,\n" \ - "and the GtkTreeModel. This generally means that the model has changed\n"\ - "without letting the view know. Any display from now on is likely to\n" \ - "be incorrect.\n", \ - G_STRLOC, \ - G_STRFUNC, \ - #expr); \ - return; \ - }; }G_STMT_END - -#else - -#define TREE_VIEW_INTERNAL_ASSERT(expr, ret) G_STMT_START{ \ - if (!(expr)) \ - { \ - g_log (G_LOG_DOMAIN, \ - G_LOG_LEVEL_CRITICAL, \ - "file %s: line %d: assertion `%s' failed.\n" \ - "There is a disparity between the internal view of the GtkTreeView,\n" \ - "and the GtkTreeModel. This generally means that the model has changed\n"\ - "without letting the view know. Any display from now on is likely to\n" \ - "be incorrect.\n", \ - __FILE__, \ - __LINE__, \ - #expr); \ - return ret; \ - }; }G_STMT_END - -#define TREE_VIEW_INTERNAL_ASSERT_VOID(expr) G_STMT_START{ \ - if (!(expr)) \ - { \ - g_log (G_LOG_DOMAIN, \ - G_LOG_LEVEL_CRITICAL, \ - "file %s: line %d: assertion '%s' failed.\n" \ - "There is a disparity between the internal view of the GtkTreeView,\n" \ - "and the GtkTreeModel. This generally means that the model has changed\n"\ - "without letting the view know. Any display from now on is likely to\n" \ - "be incorrect.\n", \ - __FILE__, \ - __LINE__, \ - #expr); \ - return; \ - }; }G_STMT_END -#endif - - /* functions that shouldn't be exported */ void _gtk_tree_selection_internal_select_node (GtkTreeSelection *selection, GtkRBNode *node, @@ -405,71 +63,92 @@ void _gtk_tree_view_queue_draw_node (GtkTreeView *tree_v GtkRBNode *node, const GdkRectangle *clip_rect); +void _gtk_tree_view_add_editable (GtkTreeView *tree_view, + GtkTreeViewColumn *column, + GtkTreePath *path, + GtkCellEditable *cell_editable, + GdkRectangle *cell_area); +void _gtk_tree_view_remove_editable (GtkTreeView *tree_view, + GtkTreeViewColumn *column, + GtkCellEditable *cell_editable); + +void _gtk_tree_view_install_mark_rows_col_dirty (GtkTreeView *tree_view, + gboolean install_handler); +void _gtk_tree_view_column_autosize (GtkTreeView *tree_view, + GtkTreeViewColumn *column); +gint _gtk_tree_view_get_header_height (GtkTreeView *tree_view); + +void _gtk_tree_view_get_row_separator_func (GtkTreeView *tree_view, + GtkTreeViewRowSeparatorFunc *func, + gpointer *data); +GtkTreePath *_gtk_tree_view_get_anchor_path (GtkTreeView *tree_view); +void _gtk_tree_view_set_anchor_path (GtkTreeView *tree_view, + GtkTreePath *anchor_path); +GtkRBTree * _gtk_tree_view_get_rbtree (GtkTreeView *tree_view); + +void _gtk_tree_view_set_focus_column (GtkTreeView *tree_view, + GtkTreeViewColumn *column); +GdkWindow *_gtk_tree_view_get_header_window (GtkTreeView *tree_view); + + +GtkTreeSelection* _gtk_tree_selection_new (void); +GtkTreeSelection* _gtk_tree_selection_new_with_tree_view (GtkTreeView *tree_view); +void _gtk_tree_selection_set_tree_view (GtkTreeSelection *selection, + GtkTreeView *tree_view); +gboolean _gtk_tree_selection_row_is_selectable (GtkTreeSelection *selection, + GtkRBNode *node, + GtkTreePath *path); + + void _gtk_tree_view_column_realize_button (GtkTreeViewColumn *column); void _gtk_tree_view_column_unrealize_button (GtkTreeViewColumn *column); + void _gtk_tree_view_column_set_tree_view (GtkTreeViewColumn *column, GtkTreeView *tree_view); +gint _gtk_tree_view_column_request_width (GtkTreeViewColumn *tree_column); +void _gtk_tree_view_column_allocate (GtkTreeViewColumn *tree_column, + int x_offset, + int width); void _gtk_tree_view_column_unset_model (GtkTreeViewColumn *column, GtkTreeModel *old_model); void _gtk_tree_view_column_unset_tree_view (GtkTreeViewColumn *column); -void _gtk_tree_view_column_set_width (GtkTreeViewColumn *column, - gint width); void _gtk_tree_view_column_start_drag (GtkTreeView *tree_view, GtkTreeViewColumn *column, GdkDevice *device); gboolean _gtk_tree_view_column_cell_event (GtkTreeViewColumn *tree_column, - GtkCellEditable **editable_widget, GdkEvent *event, - gchar *path_string, - const GdkRectangle *background_area, const GdkRectangle *cell_area, guint flags); -void _gtk_tree_view_column_start_editing (GtkTreeViewColumn *tree_column, - GtkCellEditable *editable_widget); -void _gtk_tree_view_column_stop_editing (GtkTreeViewColumn *tree_column); -void _gtk_tree_view_install_mark_rows_col_dirty (GtkTreeView *tree_view); -void _gtk_tree_view_column_autosize (GtkTreeView *tree_view, - GtkTreeViewColumn *column); - -gboolean _gtk_tree_view_column_has_editable_cell (GtkTreeViewColumn *column); -GtkCellRenderer *_gtk_tree_view_column_get_edited_cell (GtkTreeViewColumn *column); -gint _gtk_tree_view_column_count_special_cells (GtkTreeViewColumn *column); -GtkCellRenderer *_gtk_tree_view_column_get_cell_at_pos (GtkTreeViewColumn *column, - gint x); - -GtkTreeSelection* _gtk_tree_selection_new (void); -GtkTreeSelection* _gtk_tree_selection_new_with_tree_view (GtkTreeView *tree_view); -void _gtk_tree_selection_set_tree_view (GtkTreeSelection *selection, - GtkTreeView *tree_view); -gboolean _gtk_tree_selection_row_is_selectable (GtkTreeSelection *selection, - GtkRBNode *node, - GtkTreePath *path); +gboolean _gtk_tree_view_column_has_editable_cell(GtkTreeViewColumn *column); +GtkCellRenderer *_gtk_tree_view_column_get_edited_cell (GtkTreeViewColumn *column); +GtkCellRenderer *_gtk_tree_view_column_get_cell_at_pos (GtkTreeViewColumn *column, + GdkRectangle *cell_area, + GdkRectangle *background_area, + gint x, + gint y); void _gtk_tree_view_column_cell_render (GtkTreeViewColumn *tree_column, cairo_t *cr, const GdkRectangle *background_area, const GdkRectangle *cell_area, - guint flags); -void _gtk_tree_view_column_get_focus_area (GtkTreeViewColumn *tree_column, - const GdkRectangle *background_area, - const GdkRectangle *cell_area, - GdkRectangle *focus_area); -gboolean _gtk_tree_view_column_cell_focus (GtkTreeViewColumn *tree_column, - gint direction, - gboolean left, - gboolean right); -void _gtk_tree_view_column_cell_draw_focus (GtkTreeViewColumn *tree_column, - cairo_t *cr, - const GdkRectangle *background_area, - const GdkRectangle *cell_area, - guint flags); + guint flags, + gboolean draw_focus); void _gtk_tree_view_column_cell_set_dirty (GtkTreeViewColumn *tree_column, gboolean install_handler); -void _gtk_tree_view_column_get_neighbor_sizes (GtkTreeViewColumn *column, - GtkCellRenderer *cell, - gint *left, - gint *right); - +gboolean _gtk_tree_view_column_cell_get_dirty (GtkTreeViewColumn *tree_column); +GdkWindow *_gtk_tree_view_column_get_window (GtkTreeViewColumn *column); + +void _gtk_tree_view_column_push_padding (GtkTreeViewColumn *column, + gint padding); +gint _gtk_tree_view_column_get_requested_width (GtkTreeViewColumn *column); +void _gtk_tree_view_column_set_resized_width (GtkTreeViewColumn *column, + gint width); +gint _gtk_tree_view_column_get_resized_width (GtkTreeViewColumn *column); +void _gtk_tree_view_column_set_use_resized_width (GtkTreeViewColumn *column, + gboolean use_resized_width); +gboolean _gtk_tree_view_column_get_use_resized_width (GtkTreeViewColumn *column); +gint _gtk_tree_view_column_get_drag_x (GtkTreeViewColumn *column); +GtkCellAreaContext *_gtk_tree_view_column_get_context (GtkTreeViewColumn *column); G_END_DECLS diff --git a/gtk/gtktreeselection.c b/gtk/gtktreeselection.c index 5ae0866146..8d2a164541 100644 --- a/gtk/gtktreeselection.c +++ b/gtk/gtktreeselection.c @@ -214,8 +214,7 @@ gtk_tree_selection_set_mode (GtkTreeSelection *selection, gtk_tree_selection_unselect_all (selection); selection->user_func = tmp_func; - gtk_tree_row_reference_free (selection->tree_view->priv->anchor); - selection->tree_view->priv->anchor = NULL; + _gtk_tree_view_set_anchor_path (selection->tree_view, NULL); } else if (type == GTK_SELECTION_SINGLE || type == GTK_SELECTION_BROWSE) @@ -225,20 +224,17 @@ gtk_tree_selection_set_mode (GtkTreeSelection *selection, gint selected = FALSE; GtkTreePath *anchor_path = NULL; - if (selection->tree_view->priv->anchor) + anchor_path = _gtk_tree_view_get_anchor_path (selection->tree_view); + + if (anchor_path) { - anchor_path = gtk_tree_row_reference_get_path (selection->tree_view->priv->anchor); - - if (anchor_path) - { - _gtk_tree_view_find_node (selection->tree_view, - anchor_path, - &tree, - &node); - - if (node && GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_SELECTED)) - selected = TRUE; - } + _gtk_tree_view_find_node (selection->tree_view, + anchor_path, + &tree, + &node); + + if (node && GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_SELECTED)) + selected = TRUE; } /* We do this so that we unconditionally unset all rows @@ -393,12 +389,9 @@ gtk_tree_selection_get_selected (GtkTreeSelection *selection, memset (iter, 0, sizeof (GtkTreeIter)); if (model) - *model = selection->tree_view->priv->model; - - if (selection->tree_view->priv->anchor == NULL) - return FALSE; + *model = gtk_tree_view_get_model (selection->tree_view); - anchor_path = gtk_tree_row_reference_get_path (selection->tree_view->priv->anchor); + anchor_path = _gtk_tree_view_get_anchor_path (selection->tree_view); if (anchor_path == NULL) return FALSE; @@ -418,7 +411,7 @@ gtk_tree_selection_get_selected (GtkTreeSelection *selection, if (iter == NULL) retval = TRUE; else - retval = gtk_tree_model_get_iter (selection->tree_view->priv->model, + retval = gtk_tree_model_get_iter (gtk_tree_view_get_model (selection->tree_view), iter, anchor_path); } @@ -467,10 +460,11 @@ gtk_tree_selection_get_selected_rows (GtkTreeSelection *selection, g_return_val_if_fail (selection->tree_view != NULL, NULL); if (model) - *model = selection->tree_view->priv->model; + *model = gtk_tree_view_get_model (selection->tree_view); - if (selection->tree_view->priv->tree == NULL || - selection->tree_view->priv->tree->root == NULL) + tree = _gtk_tree_view_get_rbtree (selection->tree_view); + + if (tree == NULL || tree->root == NULL) return NULL; if (selection->type == GTK_SELECTION_NONE) @@ -483,7 +477,7 @@ gtk_tree_selection_get_selected_rows (GtkTreeSelection *selection, { GtkTreePath *path; - path = gtk_tree_model_get_path (selection->tree_view->priv->model, &iter); + path = gtk_tree_model_get_path (gtk_tree_view_get_model (selection->tree_view), &iter); list = g_list_append (list, path); return list; @@ -492,8 +486,7 @@ gtk_tree_selection_get_selected_rows (GtkTreeSelection *selection, return NULL; } - tree = selection->tree_view->priv->tree; - node = selection->tree_view->priv->tree->root; + node = tree->root; while (node->left != tree->nil) node = node->left; @@ -582,12 +575,14 @@ gint gtk_tree_selection_count_selected_rows (GtkTreeSelection *selection) { gint count = 0; + GtkRBTree *tree; g_return_val_if_fail (GTK_IS_TREE_SELECTION (selection), 0); g_return_val_if_fail (selection->tree_view != NULL, 0); - if (selection->tree_view->priv->tree == NULL || - selection->tree_view->priv->tree->root == NULL) + tree = _gtk_tree_view_get_rbtree (selection->tree_view); + + if (tree == NULL || tree->root == NULL) return 0; if (selection->type == GTK_SELECTION_SINGLE || @@ -599,8 +594,7 @@ gtk_tree_selection_count_selected_rows (GtkTreeSelection *selection) return 0; } - _gtk_rbtree_traverse (selection->tree_view->priv->tree, - selection->tree_view->priv->tree->root, + _gtk_rbtree_traverse (tree, tree->root, G_PRE_ORDER, gtk_tree_selection_count_selected_rows_helper, &count); @@ -644,31 +638,32 @@ gtk_tree_selection_selected_foreach (GtkTreeSelection *selection, g_return_if_fail (GTK_IS_TREE_SELECTION (selection)); g_return_if_fail (selection->tree_view != NULL); - if (func == NULL || - selection->tree_view->priv->tree == NULL || - selection->tree_view->priv->tree->root == NULL) + tree = _gtk_tree_view_get_rbtree (selection->tree_view); + + if (func == NULL || tree == NULL || tree->root == NULL) return; + model = gtk_tree_view_get_model (selection->tree_view); + if (selection->type == GTK_SELECTION_SINGLE || selection->type == GTK_SELECTION_BROWSE) { - if (gtk_tree_row_reference_valid (selection->tree_view->priv->anchor)) + path = _gtk_tree_view_get_anchor_path (selection->tree_view); + + if (path) { - path = gtk_tree_row_reference_get_path (selection->tree_view->priv->anchor); - gtk_tree_model_get_iter (selection->tree_view->priv->model, &iter, path); - (* func) (selection->tree_view->priv->model, path, &iter, data); + gtk_tree_model_get_iter (model, &iter, path); + (* func) (model, path, &iter, data); gtk_tree_path_free (path); } return; } - tree = selection->tree_view->priv->tree; - node = selection->tree_view->priv->tree->root; + node = tree->root; while (node->left != tree->nil) node = node->left; - model = selection->tree_view->priv->model; g_object_ref (model); /* connect to signals to monitor changes in treemodel */ @@ -848,14 +843,16 @@ gtk_tree_selection_select_iter (GtkTreeSelection *selection, GtkTreeIter *iter) { GtkTreePath *path; + GtkTreeModel *model; g_return_if_fail (GTK_IS_TREE_SELECTION (selection)); g_return_if_fail (selection->tree_view != NULL); - g_return_if_fail (selection->tree_view->priv->model != NULL); + + model = gtk_tree_view_get_model (selection->tree_view); + g_return_if_fail (model != NULL); g_return_if_fail (iter != NULL); - path = gtk_tree_model_get_path (selection->tree_view->priv->model, - iter); + path = gtk_tree_model_get_path (model, iter); if (path == NULL) return; @@ -877,14 +874,16 @@ gtk_tree_selection_unselect_iter (GtkTreeSelection *selection, GtkTreeIter *iter) { GtkTreePath *path; + GtkTreeModel *model; g_return_if_fail (GTK_IS_TREE_SELECTION (selection)); g_return_if_fail (selection->tree_view != NULL); - g_return_if_fail (selection->tree_view->priv->model != NULL); + + model = gtk_tree_view_get_model (selection->tree_view); + g_return_if_fail (model != NULL); g_return_if_fail (iter != NULL); - path = gtk_tree_model_get_path (selection->tree_view->priv->model, - iter); + path = gtk_tree_model_get_path (model, iter); if (path == NULL) return; @@ -915,7 +914,7 @@ gtk_tree_selection_path_is_selected (GtkTreeSelection *selection, g_return_val_if_fail (path != NULL, FALSE); g_return_val_if_fail (selection->tree_view != NULL, FALSE); - if (selection->tree_view->priv->model == NULL) + if (gtk_tree_view_get_model (selection->tree_view) == NULL) return FALSE; ret = _gtk_tree_view_find_node (selection->tree_view, @@ -944,14 +943,17 @@ gtk_tree_selection_iter_is_selected (GtkTreeSelection *selection, GtkTreeIter *iter) { GtkTreePath *path; + GtkTreeModel *model; gboolean retval; g_return_val_if_fail (GTK_IS_TREE_SELECTION (selection), FALSE); g_return_val_if_fail (iter != NULL, FALSE); g_return_val_if_fail (selection->tree_view != NULL, FALSE); - g_return_val_if_fail (selection->tree_view->priv->model != NULL, FALSE); - path = gtk_tree_model_get_path (selection->tree_view->priv->model, iter); + model = gtk_tree_view_get_model (selection->tree_view); + g_return_val_if_fail (model != NULL, FALSE); + + path = gtk_tree_model_get_path (model, iter); if (path == NULL) return FALSE; @@ -995,8 +997,11 @@ static gint gtk_tree_selection_real_select_all (GtkTreeSelection *selection) { struct _TempTuple *tuple; + GtkRBTree *tree; - if (selection->tree_view->priv->tree == NULL) + tree = _gtk_tree_view_get_rbtree (selection->tree_view); + + if (tree == NULL) return FALSE; /* Mark all nodes selected */ @@ -1004,8 +1009,7 @@ gtk_tree_selection_real_select_all (GtkTreeSelection *selection) tuple->selection = selection; tuple->dirty = FALSE; - _gtk_rbtree_traverse (selection->tree_view->priv->tree, - selection->tree_view->priv->tree->root, + _gtk_rbtree_traverse (tree, tree->root, G_PRE_ORDER, select_all_helper, tuple); @@ -1031,7 +1035,8 @@ gtk_tree_selection_select_all (GtkTreeSelection *selection) g_return_if_fail (GTK_IS_TREE_SELECTION (selection)); g_return_if_fail (selection->tree_view != NULL); - if (selection->tree_view->priv->tree == NULL || selection->tree_view->priv->model == NULL) + if (_gtk_tree_view_get_rbtree (selection->tree_view) == NULL || + gtk_tree_view_get_model (selection->tree_view) == NULL) return; g_return_if_fail (selection->type == GTK_SELECTION_MULTIPLE); @@ -1071,10 +1076,7 @@ gtk_tree_selection_real_unselect_all (GtkTreeSelection *selection) GtkRBNode *node = NULL; GtkTreePath *anchor_path; - if (selection->tree_view->priv->anchor == NULL) - return FALSE; - - anchor_path = gtk_tree_row_reference_get_path (selection->tree_view->priv->anchor); + anchor_path = _gtk_tree_view_get_anchor_path (selection->tree_view); if (anchor_path == NULL) return FALSE; @@ -1093,8 +1095,7 @@ gtk_tree_selection_real_unselect_all (GtkTreeSelection *selection) { if (gtk_tree_selection_real_select_node (selection, tree, node, FALSE)) { - gtk_tree_row_reference_free (selection->tree_view->priv->anchor); - selection->tree_view->priv->anchor = NULL; + _gtk_tree_view_set_anchor_path (selection->tree_view, NULL); return TRUE; } } @@ -1102,12 +1103,14 @@ gtk_tree_selection_real_unselect_all (GtkTreeSelection *selection) } else { + GtkRBTree *tree; + tuple = g_new (struct _TempTuple, 1); tuple->selection = selection; tuple->dirty = FALSE; - _gtk_rbtree_traverse (selection->tree_view->priv->tree, - selection->tree_view->priv->tree->root, + tree = _gtk_tree_view_get_rbtree (selection->tree_view); + _gtk_rbtree_traverse (tree, tree->root, G_PRE_ORDER, unselect_all_helper, tuple); @@ -1134,7 +1137,8 @@ gtk_tree_selection_unselect_all (GtkTreeSelection *selection) g_return_if_fail (GTK_IS_TREE_SELECTION (selection)); g_return_if_fail (selection->tree_view != NULL); - if (selection->tree_view->priv->tree == NULL || selection->tree_view->priv->model == NULL) + if (_gtk_tree_view_get_rbtree (selection->tree_view) == NULL || + gtk_tree_view_get_model (selection->tree_view) == NULL) return; if (gtk_tree_selection_real_unselect_all (selection)) @@ -1197,15 +1201,7 @@ gtk_tree_selection_real_modify_range (GtkTreeSelection *selection, g_return_val_if_fail (end_node != NULL, FALSE); if (anchor_path) - { - if (selection->tree_view->priv->anchor) - gtk_tree_row_reference_free (selection->tree_view->priv->anchor); - - selection->tree_view->priv->anchor = - gtk_tree_row_reference_new_proxy (G_OBJECT (selection->tree_view), - selection->tree_view->priv->model, - anchor_path); - } + _gtk_tree_view_set_anchor_path (selection->tree_view, anchor_path); do { @@ -1254,7 +1250,7 @@ gtk_tree_selection_select_range (GtkTreeSelection *selection, g_return_if_fail (GTK_IS_TREE_SELECTION (selection)); g_return_if_fail (selection->tree_view != NULL); g_return_if_fail (selection->type == GTK_SELECTION_MULTIPLE); - g_return_if_fail (selection->tree_view->priv->model != NULL); + g_return_if_fail (gtk_tree_view_get_model (selection->tree_view) != NULL); if (gtk_tree_selection_real_modify_range (selection, RANGE_SELECT, start_path, end_path)) g_signal_emit (selection, tree_selection_signals[CHANGED], 0); @@ -1278,7 +1274,7 @@ gtk_tree_selection_unselect_range (GtkTreeSelection *selection, { g_return_if_fail (GTK_IS_TREE_SELECTION (selection)); g_return_if_fail (selection->tree_view != NULL); - g_return_if_fail (selection->tree_view->priv->model != NULL); + g_return_if_fail (gtk_tree_view_get_model (selection->tree_view) != NULL); if (gtk_tree_selection_real_modify_range (selection, RANGE_UNSELECT, start_path, end_path)) g_signal_emit (selection, tree_selection_signals[CHANGED], 0); @@ -1290,22 +1286,28 @@ _gtk_tree_selection_row_is_selectable (GtkTreeSelection *selection, GtkTreePath *path) { GtkTreeIter iter; + GtkTreeModel *model; + GtkTreeViewRowSeparatorFunc separator_func; + gpointer separator_data; gboolean sensitive = FALSE; - if (!gtk_tree_model_get_iter (selection->tree_view->priv->model, &iter, path)) + model = gtk_tree_view_get_model (selection->tree_view); + + _gtk_tree_view_get_row_separator_func (selection->tree_view, + &separator_func, &separator_data); + + if (!gtk_tree_model_get_iter (model, &iter, path)) sensitive = TRUE; - if (!sensitive && selection->tree_view->priv->row_separator_func) + if (!sensitive && separator_func) { /* never allow separators to be selected */ - if ((* selection->tree_view->priv->row_separator_func) (selection->tree_view->priv->model, - &iter, - selection->tree_view->priv->row_separator_data)) + if ((* separator_func) (model, &iter, separator_data)) return FALSE; } if (selection->user_func) - return (*selection->user_func) (selection, selection->tree_view->priv->model, path, + return (*selection->user_func) (selection, model, path, GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_SELECTED), selection->user_data); else @@ -1336,8 +1338,7 @@ _gtk_tree_selection_internal_select_node (GtkTreeSelection *selection, if (selection->type == GTK_SELECTION_NONE) return; - if (selection->tree_view->priv->anchor) - anchor_path = gtk_tree_row_reference_get_path (selection->tree_view->priv->anchor); + anchor_path = _gtk_tree_view_get_anchor_path (selection->tree_view); if (selection->type == GTK_SELECTION_SINGLE || selection->type == GTK_SELECTION_BROWSE) @@ -1374,17 +1375,11 @@ _gtk_tree_selection_internal_select_node (GtkTreeSelection *selection, * old one, and can then select the new one */ if (dirty) { - if (selection->tree_view->priv->anchor) - { - gtk_tree_row_reference_free (selection->tree_view->priv->anchor); - selection->tree_view->priv->anchor = NULL; - } + + _gtk_tree_view_set_anchor_path (selection->tree_view, NULL); if (gtk_tree_selection_real_select_node (selection, tree, node, TRUE)) - { - selection->tree_view->priv->anchor = - gtk_tree_row_reference_new_proxy (G_OBJECT (selection->tree_view), selection->tree_view->priv->model, path); - } + _gtk_tree_view_set_anchor_path (selection->tree_view, path); } } else @@ -1392,11 +1387,8 @@ _gtk_tree_selection_internal_select_node (GtkTreeSelection *selection, if (gtk_tree_selection_real_select_node (selection, tree, node, TRUE)) { dirty = TRUE; - if (selection->tree_view->priv->anchor) - gtk_tree_row_reference_free (selection->tree_view->priv->anchor); - selection->tree_view->priv->anchor = - gtk_tree_row_reference_new_proxy (G_OBJECT (selection->tree_view), selection->tree_view->priv->model, path); + _gtk_tree_view_set_anchor_path (selection->tree_view, path); } } } @@ -1406,11 +1398,8 @@ _gtk_tree_selection_internal_select_node (GtkTreeSelection *selection, if ((mode & GTK_TREE_SELECT_MODE_EXTEND) == GTK_TREE_SELECT_MODE_EXTEND && (anchor_path == NULL)) { - if (selection->tree_view->priv->anchor) - gtk_tree_row_reference_free (selection->tree_view->priv->anchor); + _gtk_tree_view_set_anchor_path (selection->tree_view, path); - selection->tree_view->priv->anchor = - gtk_tree_row_reference_new_proxy (G_OBJECT (selection->tree_view), selection->tree_view->priv->model, path); dirty = gtk_tree_selection_real_select_node (selection, tree, node, TRUE); } else if ((mode & (GTK_TREE_SELECT_MODE_EXTEND | GTK_TREE_SELECT_MODE_TOGGLE)) == (GTK_TREE_SELECT_MODE_EXTEND | GTK_TREE_SELECT_MODE_TOGGLE)) @@ -1422,11 +1411,8 @@ _gtk_tree_selection_internal_select_node (GtkTreeSelection *selection, else if ((mode & GTK_TREE_SELECT_MODE_TOGGLE) == GTK_TREE_SELECT_MODE_TOGGLE) { flags = node->flags; - if (selection->tree_view->priv->anchor) - gtk_tree_row_reference_free (selection->tree_view->priv->anchor); - selection->tree_view->priv->anchor = - gtk_tree_row_reference_new_proxy (G_OBJECT (selection->tree_view), selection->tree_view->priv->model, path); + _gtk_tree_view_set_anchor_path (selection->tree_view, path); if ((flags & GTK_RBNODE_IS_SELECTED) == GTK_RBNODE_IS_SELECTED) dirty |= gtk_tree_selection_real_select_node (selection, tree, node, FALSE); @@ -1445,11 +1431,7 @@ _gtk_tree_selection_internal_select_node (GtkTreeSelection *selection, { dirty = gtk_tree_selection_real_unselect_all (selection); - if (selection->tree_view->priv->anchor) - gtk_tree_row_reference_free (selection->tree_view->priv->anchor); - - selection->tree_view->priv->anchor = - gtk_tree_row_reference_new_proxy (G_OBJECT (selection->tree_view), selection->tree_view->priv->model, path); + _gtk_tree_view_set_anchor_path (selection->tree_view, path); dirty |= gtk_tree_selection_real_select_node (selection, tree, node, TRUE); } diff --git a/gtk/gtktreeview.c b/gtk/gtktreeview.c index 457c4facdd..d92aa2ff17 100644 --- a/gtk/gtktreeview.c +++ b/gtk/gtktreeview.c @@ -45,6 +45,7 @@ #include "gtktreemodelsort.h" #include "gtktooltip.h" #include "gtkscrollable.h" +#include "gtkcelllayout.h" #include "gtkprivate.h" #include "gtkwidgetprivate.h" #include "gtkentryprivate.h" @@ -133,6 +134,95 @@ * </refsect2> */ +enum +{ + DRAG_COLUMN_WINDOW_STATE_UNSET = 0, + DRAG_COLUMN_WINDOW_STATE_ORIGINAL = 1, + DRAG_COLUMN_WINDOW_STATE_ARROW = 2, + DRAG_COLUMN_WINDOW_STATE_ARROW_LEFT = 3, + DRAG_COLUMN_WINDOW_STATE_ARROW_RIGHT = 4 +}; + +enum +{ + RUBBER_BAND_OFF = 0, + RUBBER_BAND_MAYBE_START = 1, + RUBBER_BAND_ACTIVE = 2 +}; + + /* This lovely little value is used to determine how far away from the title bar + * you can move the mouse and still have a column drag work. + */ +#define TREE_VIEW_COLUMN_DRAG_DEAD_MULTIPLIER(tree_view) (10*gtk_tree_view_get_effective_header_height(tree_view)) + +#ifdef __GNUC__ + +#define TREE_VIEW_INTERNAL_ASSERT(expr, ret) G_STMT_START{ \ + if (!(expr)) \ + { \ + g_log (G_LOG_DOMAIN, \ + G_LOG_LEVEL_CRITICAL, \ + "%s (%s): assertion `%s' failed.\n" \ + "There is a disparity between the internal view of the GtkTreeView,\n" \ + "and the GtkTreeModel. This generally means that the model has changed\n"\ + "without letting the view know. Any display from now on is likely to\n" \ + "be incorrect.\n", \ + G_STRLOC, \ + G_STRFUNC, \ + #expr); \ + return ret; \ + }; }G_STMT_END + +#define TREE_VIEW_INTERNAL_ASSERT_VOID(expr) G_STMT_START{ \ + if (!(expr)) \ + { \ + g_log (G_LOG_DOMAIN, \ + G_LOG_LEVEL_CRITICAL, \ + "%s (%s): assertion `%s' failed.\n" \ + "There is a disparity between the internal view of the GtkTreeView,\n" \ + "and the GtkTreeModel. This generally means that the model has changed\n"\ + "without letting the view know. Any display from now on is likely to\n" \ + "be incorrect.\n", \ + G_STRLOC, \ + G_STRFUNC, \ + #expr); \ + return; \ + }; }G_STMT_END + +#else + +#define TREE_VIEW_INTERNAL_ASSERT(expr, ret) G_STMT_START{ \ + if (!(expr)) \ + { \ + g_log (G_LOG_DOMAIN, \ + G_LOG_LEVEL_CRITICAL, \ + "file %s: line %d: assertion `%s' failed.\n" \ + "There is a disparity between the internal view of the GtkTreeView,\n" \ + "and the GtkTreeModel. This generally means that the model has changed\n"\ + "without letting the view know. Any display from now on is likely to\n" \ + "be incorrect.\n", \ + __FILE__, \ + __LINE__, \ + #expr); \ + return ret; \ + }; }G_STMT_END + +#define TREE_VIEW_INTERNAL_ASSERT_VOID(expr) G_STMT_START{ \ + if (!(expr)) \ + { \ + g_log (G_LOG_DOMAIN, \ + G_LOG_LEVEL_CRITICAL, \ + "file %s: line %d: assertion '%s' failed.\n" \ + "There is a disparity between the internal view of the GtkTreeView,\n" \ + "and the GtkTreeModel. This generally means that the model has changed\n"\ + "without letting the view know. Any display from now on is likely to\n" \ + "be incorrect.\n", \ + __FILE__, \ + __LINE__, \ + #expr); \ + return; \ + }; }G_STMT_END +#endif #define GTK_TREE_VIEW_PRIORITY_VALIDATE (GDK_PRIORITY_REDRAW + 5) #define GTK_TREE_VIEW_PRIORITY_SCROLL_SYNC (GTK_TREE_VIEW_PRIORITY_VALIDATE + 2) @@ -142,28 +232,20 @@ #define GTK_TREE_VIEW_SEARCH_DIALOG_TIMEOUT 5000 #define AUTO_EXPAND_TIMEOUT 500 -/* The "background" areas of all rows/cells add up to cover the entire tree. - * The background includes all inter-row and inter-cell spacing. - * The "cell" areas are the cell_area passed in to gtk_cell_renderer_render(), - * i.e. just the cells, no spacing. - */ - -#define BACKGROUND_HEIGHT(node) (GTK_RBNODE_GET_HEIGHT (node)) -#define CELL_HEIGHT(node, separator) ((BACKGROUND_HEIGHT (node)) - (separator)) - /* Translate from bin_window coordinates to rbtree (tree coordinates) and * vice versa. */ #define TREE_WINDOW_Y_TO_RBTREE_Y(tree_view,y) ((y) + tree_view->priv->dy) #define RBTREE_Y_TO_TREE_WINDOW_Y(tree_view,y) ((y) - tree_view->priv->dy) -/* This is in bin_window coordinates */ -#define BACKGROUND_FIRST_PIXEL(tree_view,tree,node) (RBTREE_Y_TO_TREE_WINDOW_Y (tree_view, _gtk_rbtree_node_find_offset ((tree), (node)))) -#define CELL_FIRST_PIXEL(tree_view,tree,node,separator) (BACKGROUND_FIRST_PIXEL (tree_view,tree,node) + separator/2) - -#define ROW_HEIGHT(tree_view,height) \ - ((height > 0) ? (height) : (tree_view)->priv->expander_size) - +typedef struct _GtkTreeViewColumnReorder GtkTreeViewColumnReorder; +struct _GtkTreeViewColumnReorder +{ + gint left_align; + gint right_align; + GtkTreeViewColumn *left_column; + GtkTreeViewColumn *right_column; +}; typedef struct _GtkTreeViewChild GtkTreeViewChild; struct _GtkTreeViewChild @@ -190,6 +272,239 @@ struct _TreeViewDragInfo }; +struct _GtkTreeViewPrivate +{ + GtkTreeModel *model; + + /* tree information */ + GtkRBTree *tree; + + /* Container info */ + GList *children; + gint width; + gint height; + + /* Adjustments */ + GtkAdjustment *hadjustment; + GtkAdjustment *vadjustment; + gint min_display_width; + gint min_display_height; + + /* Sub windows */ + GdkWindow *bin_window; + GdkWindow *header_window; + + /* Scroll position state keeping */ + GtkTreeRowReference *top_row; + gint top_row_dy; + /* dy == y pos of top_row + top_row_dy */ + /* we cache it for simplicity of the code */ + gint dy; + + guint presize_handler_timer; + guint validate_rows_timer; + guint scroll_sync_timer; + + /* Indentation and expander layout */ + gint expander_size; + GtkTreeViewColumn *expander_column; + + gint level_indentation; + + /* Key navigation (focus), selection */ + gint cursor_offset; + + GtkTreeRowReference *anchor; + GtkTreeRowReference *cursor; + + GtkTreeViewColumn *focus_column; + + /* Current pressed node, previously pressed, prelight */ + GtkRBNode *button_pressed_node; + GtkRBTree *button_pressed_tree; + + gint pressed_button; + gint press_start_x; + gint press_start_y; + + gint event_last_x; + gint event_last_y; + + guint last_button_time; + gint last_button_x; + gint last_button_y; + + GtkRBNode *prelight_node; + GtkRBTree *prelight_tree; + + /* Cell Editing */ + GtkTreeViewColumn *edited_column; + + /* The node that's currently being collapsed or expanded */ + GtkRBNode *expanded_collapsed_node; + GtkRBTree *expanded_collapsed_tree; + guint expand_collapse_timeout; + + /* Auto expand/collapse timeout in hover mode */ + guint auto_expand_timeout; + + /* Selection information */ + GtkTreeSelection *selection; + + /* Header information */ + gint n_columns; + GList *columns; + gint header_height; + + GtkTreeViewColumnDropFunc column_drop_func; + gpointer column_drop_func_data; + GDestroyNotify column_drop_func_data_destroy; + GList *column_drag_info; + GtkTreeViewColumnReorder *cur_reorder; + + gint prev_width_before_expander; + + /* Interactive Header reordering */ + GdkWindow *drag_window; + GdkWindow *drag_highlight_window; + GtkTreeViewColumn *drag_column; + gint drag_column_x; + + /* Interactive Header Resizing */ + gint drag_pos; + gint x_drag; + + /* Non-interactive Header Resizing, expand flag support */ + gint prev_width; + + gint last_extra_space; + gint last_extra_space_per_column; + gint last_number_of_expand_columns; + + /* ATK Hack */ + GtkTreeDestroyCountFunc destroy_count_func; + gpointer destroy_count_data; + GDestroyNotify destroy_count_destroy; + + /* Scroll timeout (e.g. during dnd, rubber banding) */ + guint scroll_timeout; + + /* Row drag-and-drop */ + GtkTreeRowReference *drag_dest_row; + GtkTreeViewDropPosition drag_dest_pos; + guint open_dest_timeout; + + /* Rubber banding */ + gint rubber_band_status; + gint rubber_band_x; + gint rubber_band_y; + gint rubber_band_shift; + gint rubber_band_ctrl; + + GtkRBNode *rubber_band_start_node; + GtkRBTree *rubber_band_start_tree; + + GtkRBNode *rubber_band_end_node; + GtkRBTree *rubber_band_end_tree; + + /* fixed height */ + gint fixed_height; + + /* Scroll-to functionality when unrealized */ + GtkTreeRowReference *scroll_to_path; + GtkTreeViewColumn *scroll_to_column; + gfloat scroll_to_row_align; + gfloat scroll_to_col_align; + + /* Interactive search */ + gint selected_iter; + gint search_column; + GtkTreeViewSearchPositionFunc search_position_func; + GtkTreeViewSearchEqualFunc search_equal_func; + gpointer search_user_data; + GDestroyNotify search_destroy; + gpointer search_position_user_data; + GDestroyNotify search_position_destroy; + GtkWidget *search_window; + GtkWidget *search_entry; + gulong search_entry_changed_id; + guint typeselect_flush_timeout; + + /* Grid and tree lines */ + GtkTreeViewGridLines grid_lines; + double grid_line_dashes[2]; + int grid_line_width; + + gboolean tree_lines_enabled; + double tree_line_dashes[2]; + int tree_line_width; + + /* Row separators */ + GtkTreeViewRowSeparatorFunc row_separator_func; + gpointer row_separator_data; + GDestroyNotify row_separator_destroy; + + /* Tooltip support */ + gint tooltip_column; + + /* Here comes the bitfield */ + guint scroll_to_use_align : 1; + + guint fixed_height_mode : 1; + guint fixed_height_check : 1; + + guint reorderable : 1; + guint header_has_focus : 1; + guint drag_column_window_state : 3; + /* hint to display rows in alternating colors */ + guint has_rules : 1; + guint mark_rows_col_dirty : 1; + + /* for DnD */ + guint empty_view_drop : 1; + + guint ctrl_pressed : 1; + guint shift_pressed : 1; + + guint init_hadjust_value : 1; + + guint in_top_row_to_dy : 1; + + /* interactive search */ + guint enable_search : 1; + guint disable_popdown : 1; + guint search_custom_entry_set : 1; + + guint hover_selection : 1; + guint hover_expand : 1; + guint imcontext_changed : 1; + + guint rubber_banding_enable : 1; + + guint in_grab : 1; + + guint post_validation_flag : 1; + + /* Whether our key press handler is to avoid sending an unhandled binding to the search entry */ + guint search_entry_avoid_unhandled_binding : 1; + + /* GtkScrollablePolicy needs to be checked when + * driving the scrollable adjustment values */ + guint hscroll_policy : 1; + guint vscroll_policy : 1; + + /* GtkTreeView flags */ + guint is_list : 1; + guint show_expanders : 1; + guint in_column_resize : 1; + guint arrow_prelit : 1; + guint headers_visible : 1; + guint draw_keyfocus : 1; + guint model_setup : 1; + guint in_column_drag : 1; +}; + + /* Signals */ enum { @@ -260,7 +575,8 @@ static void gtk_tree_view_get_preferred_height (GtkWidget *widget, gint *minimum, gint *natural); static void gtk_tree_view_size_request (GtkWidget *widget, - GtkRequisition *requisition); + GtkRequisition *requisition, + gboolean may_validate); static void gtk_tree_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static gboolean gtk_tree_view_draw (GtkWidget *widget, @@ -406,6 +722,7 @@ static void invalidate_empty_focus (GtkTreeView *tree_view); /* Internal functions */ static gboolean gtk_tree_view_is_expander_column (GtkTreeView *tree_view, GtkTreeViewColumn *column); +static inline gboolean gtk_tree_view_draw_expanders (GtkTreeView *tree_view); static void gtk_tree_view_add_move_binding (GtkBindingSet *binding_set, guint keyval, guint modmask, @@ -472,7 +789,7 @@ static void gtk_tree_view_real_set_cursor (GtkTreeView GtkTreePath *path, gboolean clear_and_select, gboolean clamp_node); -static gboolean gtk_tree_view_has_special_cell (GtkTreeView *tree_view); +static gboolean gtk_tree_view_has_can_focus_cell (GtkTreeView *tree_view); static void column_sizing_notify (GObject *object, GParamSpec *pspec, gpointer data); @@ -489,6 +806,22 @@ static void update_prelight (GtkTreeView int x, int y); +static inline gint gtk_tree_view_get_effective_header_height (GtkTreeView *tree_view); + +static inline gint gtk_tree_view_get_cell_area_y_offset (GtkTreeView *tree_view, + GtkRBTree *tree, + GtkRBNode *node, + gint vertical_separator); +static inline gint gtk_tree_view_get_cell_area_height (GtkTreeView *tree_view, + GtkRBNode *node, + gint vertical_separator); + +static inline gint gtk_tree_view_get_row_y_offset (GtkTreeView *tree_view, + GtkRBTree *tree, + GtkRBNode *node); +static inline gint gtk_tree_view_get_row_height (GtkTreeView *tree_view, + GtkRBNode *node); + /* interactive search */ static void gtk_tree_view_ensure_interactive_directory (GtkTreeView *tree_view); static void gtk_tree_view_search_dialog_hide (GtkWidget *search_dialog, @@ -542,14 +875,8 @@ static void gtk_tree_view_put (GtkTreeView *tree_ gint width, gint height); static gboolean gtk_tree_view_start_editing (GtkTreeView *tree_view, - GtkTreePath *cursor_path); -static void gtk_tree_view_real_start_editing (GtkTreeView *tree_view, - GtkTreeViewColumn *column, - GtkTreePath *path, - GtkCellEditable *cell_editable, - GdkRectangle *cell_area, - GdkEvent *event, - guint flags); + GtkTreePath *cursor_path, + gboolean edit_only); static void gtk_tree_view_stop_editing (GtkTreeView *tree_view, gboolean cancel_editing); static gboolean gtk_tree_view_real_start_interactive_search (GtkTreeView *tree_view, @@ -1382,9 +1709,9 @@ gtk_tree_view_init (GtkTreeView *tree_view) gtk_widget_set_can_focus (GTK_WIDGET (tree_view), TRUE); gtk_widget_set_redraw_on_allocate (GTK_WIDGET (tree_view), FALSE); - tree_view->priv->flags = GTK_TREE_VIEW_SHOW_EXPANDERS - | GTK_TREE_VIEW_DRAW_KEYFOCUS - | GTK_TREE_VIEW_HEADERS_VISIBLE; + tree_view->priv->show_expanders = TRUE; + tree_view->priv->draw_keyfocus = TRUE; + tree_view->priv->headers_visible = TRUE; /* We need some padding */ tree_view->priv->dy = 0; @@ -1583,7 +1910,7 @@ gtk_tree_view_get_property (GObject *object, g_value_set_boolean (value, tree_view->priv->hover_expand); break; case PROP_SHOW_EXPANDERS: - g_value_set_boolean (value, GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_SHOW_EXPANDERS)); + g_value_set_boolean (value, tree_view->priv->show_expanders); break; case PROP_LEVEL_INDENTATION: g_value_set_int (value, tree_view->priv->level_indentation); @@ -1790,29 +2117,35 @@ gtk_tree_view_map_buttons (GtkTreeView *tree_view) g_return_if_fail (gtk_widget_get_mapped (GTK_WIDGET (tree_view))); - if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_HEADERS_VISIBLE)) + if (tree_view->priv->headers_visible) { GtkTreeViewColumn *column; + GtkWidget *button; + GdkWindow *window; for (list = tree_view->priv->columns; list; list = list->next) { column = list->data; - if (gtk_widget_get_visible (column->button) && - !gtk_widget_get_mapped (column->button)) - gtk_widget_map (column->button); + button = gtk_tree_view_column_get_button (column); + + if (gtk_widget_get_visible (button) && + !gtk_widget_get_mapped (button)) + gtk_widget_map (button); } for (list = tree_view->priv->columns; list; list = list->next) { column = list->data; if (gtk_tree_view_column_get_visible (column) == FALSE) continue; + + window = _gtk_tree_view_column_get_window (column); if (gtk_tree_view_column_get_resizable (column)) { - gdk_window_raise (column->window); - gdk_window_show (column->window); + gdk_window_raise (window); + gdk_window_show (window); } else - gdk_window_hide (column->window); + gdk_window_hide (window); } gdk_window_show (tree_view->priv->header_window); } @@ -1881,7 +2214,7 @@ gtk_tree_view_realize (GtkWidget *widget) /* Make the window for the tree */ attributes.x = 0; - attributes.y = TREE_VIEW_HEADER_HEIGHT (tree_view); + attributes.y = gtk_tree_view_get_effective_header_height (tree_view); attributes.width = MAX (tree_view->priv->width, allocation.width); attributes.height = allocation.height; attributes.event_mask = (GDK_EXPOSURE_MASK | @@ -2035,16 +2368,16 @@ gtk_tree_view_size_request_columns (GtkTreeView *tree_view) { for (list = tree_view->priv->columns; list; list = list->next) { - GtkRequisition requisition; + GtkRequisition requisition; GtkTreeViewColumn *column = list->data; + GtkWidget *button = gtk_tree_view_column_get_button (column); - if (column->button == NULL) + if (button == NULL) continue; column = list->data; - gtk_widget_get_preferred_size (column->button, &requisition, NULL); - column->button_request = requisition.width; + gtk_widget_get_preferred_size (button, &requisition, NULL); tree_view->priv->header_height = MAX (tree_view->priv->header_height, requisition.height); } } @@ -2073,39 +2406,11 @@ gtk_tree_view_update_size (GtkTreeView *tree_view) /* keep this in sync with size_allocate below */ for (list = tree_view->priv->columns, i = 0; list; list = list->next, i++) { - gint max_width, min_width; - gint real_requested_width = 0; - column = list->data; if (!gtk_tree_view_column_get_visible (column)) continue; - if (column->use_resized_width) - { - real_requested_width = column->resized_width; - } - else if (gtk_tree_view_column_get_sizing (column) == GTK_TREE_VIEW_COLUMN_FIXED) - { - real_requested_width = gtk_tree_view_column_get_fixed_width (column); - } - else if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_HEADERS_VISIBLE)) - { - real_requested_width = MAX (column->requested_width, column->button_request); - } - else - { - real_requested_width = column->requested_width; - } - - min_width = gtk_tree_view_column_get_min_width (column); - if (min_width != -1) - real_requested_width = MAX (real_requested_width, min_width); - - max_width = gtk_tree_view_column_get_max_width (column); - if (max_width != -1) - real_requested_width = MIN (real_requested_width, max_width); - - tree_view->priv->width += real_requested_width; + tree_view->priv->width += _gtk_tree_view_column_request_width (column); } if (tree_view->priv->tree == NULL) @@ -2116,20 +2421,25 @@ gtk_tree_view_update_size (GtkTreeView *tree_view) static void gtk_tree_view_size_request (GtkWidget *widget, - GtkRequisition *requisition) + GtkRequisition *requisition, + gboolean may_validate) { GtkTreeView *tree_view = GTK_TREE_VIEW (widget); GList *tmp_list; - /* we validate some rows initially just to make sure we have some size. - * In practice, with a lot of static lists, this should get a good width. - */ - do_validate_rows (tree_view, FALSE); + if (may_validate) + { + /* we validate some rows initially just to make sure we have some size. + * In practice, with a lot of static lists, this should get a good width. + */ + do_validate_rows (tree_view, FALSE); + } + gtk_tree_view_size_request_columns (tree_view); gtk_tree_view_update_size (GTK_TREE_VIEW (widget)); requisition->width = tree_view->priv->width; - requisition->height = tree_view->priv->height + TREE_VIEW_HEADER_HEIGHT (tree_view); + requisition->height = tree_view->priv->height + gtk_tree_view_get_effective_header_height (tree_view); tmp_list = tree_view->priv->children; } @@ -2141,7 +2451,7 @@ gtk_tree_view_get_preferred_width (GtkWidget *widget, { GtkRequisition requisition; - gtk_tree_view_size_request (widget, &requisition); + gtk_tree_view_size_request (widget, &requisition, TRUE); *minimum = *natural = requisition.width; } @@ -2153,7 +2463,7 @@ gtk_tree_view_get_preferred_height (GtkWidget *widget, { GtkRequisition requisition; - gtk_tree_view_size_request (widget, &requisition); + gtk_tree_view_size_request (widget, &requisition, TRUE); *minimum = *natural = requisition.height; } @@ -2235,43 +2545,6 @@ invalidate_last_column (GtkTreeView *tree_view) } } -static gint -gtk_tree_view_get_real_requested_width_from_column (GtkTreeView *tree_view, - GtkTreeViewColumn *column) -{ - gint max_width, min_width; - gint real_requested_width; - - if (column->use_resized_width) - { - real_requested_width = column->resized_width; - } - else if (gtk_tree_view_column_get_sizing (column) == GTK_TREE_VIEW_COLUMN_FIXED) - { - real_requested_width = gtk_tree_view_column_get_fixed_width (column); - } - else if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_HEADERS_VISIBLE)) - { - real_requested_width = MAX (column->requested_width, column->button_request); - } - else - { - real_requested_width = column->requested_width; - if (real_requested_width < 0) - real_requested_width = 0; - } - - min_width = gtk_tree_view_column_get_min_width (column); - if (min_width != -1) - real_requested_width = MAX (real_requested_width, min_width); - - max_width = gtk_tree_view_column_get_max_width (column); - if (max_width != -1) - real_requested_width = MIN (real_requested_width, max_width); - - return real_requested_width; -} - /* GtkWidget::size_allocate helper */ static void gtk_tree_view_size_allocate_columns (GtkWidget *widget, @@ -2280,7 +2553,6 @@ gtk_tree_view_size_allocate_columns (GtkWidget *widget, GtkTreeView *tree_view; GList *list, *first_column, *last_column; GtkTreeViewColumn *column; - GtkAllocation allocation; GtkAllocation widget_allocation; gint width = 0; gint extra, extra_per_column, extra_for_last; @@ -2306,9 +2578,6 @@ gtk_tree_view_size_allocate_columns (GtkWidget *widget, first_column = first_column->next) ; - allocation.y = 0; - allocation.height = tree_view->priv->header_height; - rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL); /* find out how many extra space and expandable columns we have */ @@ -2319,7 +2588,7 @@ gtk_tree_view_size_allocate_columns (GtkWidget *widget, if (!gtk_tree_view_column_get_visible (column)) continue; - full_requested_width += gtk_tree_view_get_real_requested_width_from_column (tree_view, column); + full_requested_width += _gtk_tree_view_column_request_width (column); if (gtk_tree_view_column_get_expand (column)) number_of_expand_columns++; @@ -2366,7 +2635,6 @@ gtk_tree_view_size_allocate_columns (GtkWidget *widget, list != (rtl ? first_column->prev : last_column->next); list = (rtl ? list->prev : list->next)) { - gint real_requested_width = 0; gint old_width, column_width; column = list->data; @@ -2380,21 +2648,20 @@ gtk_tree_view_size_allocate_columns (GtkWidget *widget, if (column == tree_view->priv->drag_column) { GtkAllocation drag_allocation; + GtkWidget *button; + + button = gtk_tree_view_column_get_button (tree_view->priv->drag_column); drag_allocation.x = 0; drag_allocation.y = 0; drag_allocation.width = gdk_window_get_width (tree_view->priv->drag_window); drag_allocation.height = gdk_window_get_height (tree_view->priv->drag_window); - gtk_widget_size_allocate (tree_view->priv->drag_column->button, - &drag_allocation); + gtk_widget_size_allocate (button, &drag_allocation); width += drag_allocation.width; continue; } - real_requested_width = gtk_tree_view_get_real_requested_width_from_column (tree_view, column); - - allocation.x = width; - column->width = real_requested_width; + column_width = _gtk_tree_view_column_request_width (column); if (gtk_tree_view_column_get_expand (column)) { @@ -2402,11 +2669,11 @@ gtk_tree_view_size_allocate_columns (GtkWidget *widget, { /* We add the remander to the last column as * */ - column->width += extra; + column_width += extra; } else { - column->width += extra_per_column; + column_width += extra_per_column; extra -= extra_per_column; number_of_expand_columns --; } @@ -2414,31 +2681,21 @@ gtk_tree_view_size_allocate_columns (GtkWidget *widget, else if (number_of_expand_columns == 0 && list == last_column) { - column->width += extra; + column_width += extra; } /* In addition to expand, the last column can get even more * extra space so all available space is filled up. */ if (extra_for_last > 0 && list == last_column) - column->width += extra_for_last; + column_width += extra_for_last; - g_object_notify (G_OBJECT (column), "width"); + _gtk_tree_view_column_allocate (column, width, column_width); - column_width = gtk_tree_view_column_get_width (column); - allocation.width = column_width; width += column_width; if (column_width > old_width) column_changed = TRUE; - - gtk_widget_size_allocate (column->button, &allocation); - - if (column->window) - gdk_window_move_resize (column->window, - allocation.x + (rtl ? 0 : allocation.width) - TREE_VIEW_DRAG_WIDTH/2, - allocation.y, - TREE_VIEW_DRAG_WIDTH, allocation.height); } /* We change the width here. The user might have been resizing columns, @@ -2544,7 +2801,7 @@ gtk_tree_view_size_allocate (GtkWidget *widget, g_object_freeze_notify (G_OBJECT (tree_view->priv->vadjustment)); gtk_adjustment_set_page_size (tree_view->priv->vadjustment, allocation->height - - TREE_VIEW_HEADER_HEIGHT (tree_view)); + gtk_tree_view_get_effective_header_height (tree_view)); gtk_adjustment_set_step_increment (tree_view->priv->vadjustment, tree_view->priv->vadjustment->page_size * 0.1); gtk_adjustment_set_page_increment (tree_view->priv->vadjustment, @@ -2578,9 +2835,9 @@ gtk_tree_view_size_allocate (GtkWidget *widget, tree_view->priv->header_height); gdk_window_move_resize (tree_view->priv->bin_window, - (gint) tree_view->priv->hadjustment->value, - TREE_VIEW_HEADER_HEIGHT (tree_view), + gtk_tree_view_get_effective_header_height (tree_view), MAX (tree_view->priv->width, allocation->width), - allocation->height - TREE_VIEW_HEADER_HEIGHT (tree_view)); + allocation->height - gtk_tree_view_get_effective_header_height (tree_view)); } if (tree_view->priv->tree == NULL) @@ -2639,7 +2896,7 @@ grab_focus_and_unset_draw_keyfocus (GtkTreeView *tree_view) if (gtk_widget_get_can_focus (widget) && !gtk_widget_has_focus (widget)) gtk_widget_grab_focus (widget); - GTK_TREE_VIEW_UNSET_FLAG (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS); + tree_view->priv->draw_keyfocus = 0; } static inline gboolean @@ -2698,14 +2955,12 @@ gtk_tree_view_button_press (GtkWidget *widget, GtkRBNode *node; GtkRBTree *tree; GtkTreePath *path; - gchar *path_string; gint depth; gint new_y; gint y_offset; gint dval; gint pre_val, aft_val; GtkTreeViewColumn *column = NULL; - GtkCellRenderer *focus_cell = NULL; gint column_handled_click = FALSE; gboolean row_double_click = FALSE; gboolean rtl; @@ -2720,8 +2975,8 @@ gtk_tree_view_button_press (GtkWidget *widget, /* are we in an arrow? */ if (tree_view->priv->prelight_node && - GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_ARROW_PRELIT) && - TREE_VIEW_DRAW_EXPANDERS (tree_view)) + tree_view->priv->arrow_prelit && + gtk_tree_view_draw_expanders (tree_view)) { if (event->button == 1) { @@ -2763,7 +3018,7 @@ gtk_tree_view_button_press (GtkWidget *widget, depth = gtk_tree_path_get_depth (path); background_area.y = y_offset + event->y; - background_area.height = ROW_HEIGHT (tree_view, GTK_RBNODE_GET_HEIGHT (node)); + background_area.height = gtk_tree_view_get_row_height (tree_view, node); background_area.x = 0; @@ -2777,7 +3032,7 @@ gtk_tree_view_button_press (GtkWidget *widget, if (!gtk_tree_view_column_get_visible (candidate)) continue; - background_area.width = candidate->width; + background_area.width = gtk_tree_view_column_get_width (candidate); if ((background_area.x > (gint) event->x) || (background_area.x + background_area.width <= (gint) event->x)) { @@ -2798,7 +3053,7 @@ gtk_tree_view_button_press (GtkWidget *widget, cell_area.x += (depth - 1) * tree_view->priv->level_indentation; cell_area.width -= (depth - 1) * tree_view->priv->level_indentation; - if (TREE_VIEW_DRAW_EXPANDERS (tree_view)) + if (gtk_tree_view_draw_expanders (tree_view)) { if (!rtl) cell_area.x += depth * tree_view->priv->expander_size; @@ -2844,41 +3099,21 @@ gtk_tree_view_button_press (GtkWidget *widget, /* FIXME: get the right flags */ guint flags = 0; - path_string = gtk_tree_path_to_string (path); - if (_gtk_tree_view_column_cell_event (column, - &cell_editable, (GdkEvent *)event, - path_string, - &background_area, &cell_area, flags)) { + GtkCellArea *area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (column)); + cell_editable = gtk_cell_area_get_edit_widget (area); + if (cell_editable != NULL) { - gint left, right; - GdkRectangle area; - - area = cell_area; - _gtk_tree_view_column_get_neighbor_sizes (column, _gtk_tree_view_column_get_edited_cell (column), &left, &right); - - area.x += left; - area.width -= right + left; - - gtk_tree_view_real_start_editing (tree_view, - column, - path, - cell_editable, - &area, - (GdkEvent *)event, - flags); - g_free (path_string); gtk_tree_path_free (path); gtk_tree_path_free (anchor); return TRUE; } column_handled_click = TRUE; } - g_free (path_string); } if (anchor) gtk_tree_path_free (anchor); @@ -2892,12 +3127,24 @@ gtk_tree_view_button_press (GtkWidget *widget, */ if (event->type == GDK_BUTTON_PRESS) { + GtkCellRenderer *focus_cell; + if ((event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) tree_view->priv->ctrl_pressed = TRUE; if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) tree_view->priv->shift_pressed = TRUE; - focus_cell = _gtk_tree_view_column_get_cell_at_pos (column, event->x - background_area.x); + /* We update the focus cell here, this is also needed if the + * column does not contain an editable cell. In this case, + * GtkCellArea did not receive the event for processing (and + * could not update the focus cell). + */ + focus_cell = _gtk_tree_view_column_get_cell_at_pos (column, + &cell_area, + &background_area, + event->x, + event->y); + if (focus_cell) gtk_tree_view_column_focus_cell (column, focus_cell); @@ -3016,22 +3263,23 @@ gtk_tree_view_button_press (GtkWidget *widget, for (i = 0, list = tree_view->priv->columns; list; list = list->next, i++) { column = list->data; - if (event->window == column->window && + if (event->window == _gtk_tree_view_column_get_window (column) && gtk_tree_view_column_get_resizable (column) && - column->window) + _gtk_tree_view_column_get_window (column)) { + GtkWidget *button; GtkAllocation button_allocation; gpointer drag_data; if (event->type == GDK_2BUTTON_PRESS && gtk_tree_view_column_get_sizing (column) != GTK_TREE_VIEW_COLUMN_AUTOSIZE) { - column->use_resized_width = FALSE; + _gtk_tree_view_column_set_use_resized_width (column, FALSE); _gtk_tree_view_column_autosize (tree_view, column); return TRUE; } - if (gdk_pointer_grab (column->window, FALSE, + if (gdk_pointer_grab (_gtk_tree_view_column_get_window (column), FALSE, GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, @@ -3039,9 +3287,10 @@ gtk_tree_view_button_press (GtkWidget *widget, return FALSE; gtk_grab_add (widget); - GTK_TREE_VIEW_SET_FLAG (tree_view, GTK_TREE_VIEW_IN_COLUMN_RESIZE); - column->resized_width = gtk_tree_view_column_get_width (column) - - tree_view->priv->last_extra_space_per_column; + tree_view->priv->in_column_resize = TRUE; + + _gtk_tree_view_column_set_resized_width (column, gtk_tree_view_column_get_width (column) - + tree_view->priv->last_extra_space_per_column); /* block attached dnd signal handler */ drag_data = g_object_get_data (G_OBJECT (widget), "gtk-site-data"); @@ -3051,7 +3300,8 @@ gtk_tree_view_button_press (GtkWidget *widget, 0, 0, NULL, NULL, drag_data); - gtk_widget_get_allocation (column->button, &button_allocation); + button = gtk_tree_view_column_get_button (column); + gtk_widget_get_allocation (button, &button_allocation); tree_view->priv->drag_pos = i; tree_view->priv->x_drag = button_allocation.x + (rtl ? 0 : button_allocation.width); @@ -3070,6 +3320,7 @@ gtk_tree_view_button_release_drag_column (GtkWidget *widget, GdkEventButton *event) { GtkTreeView *tree_view; + GtkWidget *button; GList *l; gboolean rtl; @@ -3080,21 +3331,22 @@ gtk_tree_view_button_release_drag_column (GtkWidget *widget, gdk_display_keyboard_ungrab (gtk_widget_get_display (widget), GDK_CURRENT_TIME); /* Move the button back */ - g_object_ref (tree_view->priv->drag_column->button); - gtk_container_remove (GTK_CONTAINER (tree_view), tree_view->priv->drag_column->button); - gtk_widget_set_parent_window (tree_view->priv->drag_column->button, tree_view->priv->header_window); - gtk_widget_set_parent (tree_view->priv->drag_column->button, GTK_WIDGET (tree_view)); - g_object_unref (tree_view->priv->drag_column->button); + button = gtk_tree_view_column_get_button (tree_view->priv->drag_column); + g_object_ref (button); + gtk_container_remove (GTK_CONTAINER (tree_view), button); + gtk_widget_set_parent_window (button, tree_view->priv->header_window); + gtk_widget_set_parent (button, GTK_WIDGET (tree_view)); + g_object_unref (button); gtk_widget_queue_resize (widget); if (gtk_tree_view_column_get_resizable (tree_view->priv->drag_column)) { - gdk_window_raise (tree_view->priv->drag_column->window); - gdk_window_show (tree_view->priv->drag_column->window); + gdk_window_raise (_gtk_tree_view_column_get_window (tree_view->priv->drag_column)); + gdk_window_show (_gtk_tree_view_column_get_window (tree_view->priv->drag_column)); } else - gdk_window_hide (tree_view->priv->drag_column->window); + gdk_window_hide (_gtk_tree_view_column_get_window (tree_view->priv->drag_column)); - gtk_widget_grab_focus (tree_view->priv->drag_column->button); + gtk_widget_grab_focus (button); if (rtl) { @@ -3124,7 +3376,7 @@ gtk_tree_view_button_release_drag_column (GtkWidget *widget, /* Reset our flags */ tree_view->priv->drag_column_window_state = DRAG_COLUMN_WINDOW_STATE_UNSET; - GTK_TREE_VIEW_UNSET_FLAG (tree_view, GTK_TREE_VIEW_IN_COLUMN_DRAG); + tree_view->priv->in_column_drag = FALSE; return TRUE; } @@ -3149,7 +3401,7 @@ gtk_tree_view_button_release_column_resize (GtkWidget *widget, 0, 0, NULL, NULL, drag_data); - GTK_TREE_VIEW_UNSET_FLAG (tree_view, GTK_TREE_VIEW_IN_COLUMN_RESIZE); + tree_view->priv->in_column_resize = FALSE; gtk_grab_remove (widget); gdk_display_pointer_ungrab (gdk_window_get_display (event->window), event->time); @@ -3162,7 +3414,7 @@ gtk_tree_view_button_release (GtkWidget *widget, { GtkTreeView *tree_view = GTK_TREE_VIEW (widget); - if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IN_COLUMN_DRAG)) + if (tree_view->priv->in_column_drag) return gtk_tree_view_button_release_drag_column (widget, event); if (tree_view->priv->rubber_band_status) @@ -3171,7 +3423,7 @@ gtk_tree_view_button_release (GtkWidget *widget, if (tree_view->priv->pressed_button == event->button) tree_view->priv->pressed_button = -1; - if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IN_COLUMN_RESIZE)) + if (tree_view->priv->in_column_resize) return gtk_tree_view_button_release_column_resize (widget, event); if (tree_view->priv->button_pressed_node == NULL) @@ -3181,7 +3433,7 @@ gtk_tree_view_button_release (GtkWidget *widget, { gtk_grab_remove (widget); if (tree_view->priv->button_pressed_node == tree_view->priv->prelight_node && - GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_ARROW_PRELIT)) + tree_view->priv->arrow_prelit) { GtkTreePath *path = NULL; @@ -3214,10 +3466,10 @@ gtk_tree_view_grab_broken (GtkWidget *widget, { GtkTreeView *tree_view = GTK_TREE_VIEW (widget); - if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IN_COLUMN_DRAG)) + if (tree_view->priv->in_column_drag) gtk_tree_view_button_release_drag_column (widget, (GdkEventButton *)event); - if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IN_COLUMN_RESIZE)) + if (tree_view->priv->in_column_resize) gtk_tree_view_button_release_column_resize (widget, (GdkEventButton *)event); return TRUE; @@ -3257,9 +3509,8 @@ coords_are_over_arrow (GtkTreeView *tree_view, if ((node->flags & GTK_RBNODE_IS_PARENT) == 0) return FALSE; - arrow.y = BACKGROUND_FIRST_PIXEL (tree_view, tree, node); - - arrow.height = ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node)); + arrow.y = gtk_tree_view_get_row_y_offset (tree_view, tree, node); + arrow.height = gtk_tree_view_get_row_height (tree_view, node); gtk_tree_view_get_arrow_xrange (tree_view, tree, &arrow.x, &x2); @@ -3320,23 +3571,18 @@ do_prelight (GtkTreeView *tree_view, /* We are still on the same node, but we might need to take care of the arrow */ - if (tree && node && TREE_VIEW_DRAW_EXPANDERS (tree_view)) + if (tree && node && gtk_tree_view_draw_expanders (tree_view)) { gboolean over_arrow; - gboolean flag_set; over_arrow = coords_are_over_arrow (tree_view, tree, node, x, y); - flag_set = GTK_TREE_VIEW_FLAG_SET (tree_view, - GTK_TREE_VIEW_ARROW_PRELIT); - if (over_arrow != flag_set) + if (over_arrow != tree_view->priv->arrow_prelit) { if (over_arrow) - GTK_TREE_VIEW_SET_FLAG (tree_view, - GTK_TREE_VIEW_ARROW_PRELIT); + tree_view->priv->arrow_prelit = TRUE; else - GTK_TREE_VIEW_UNSET_FLAG (tree_view, - GTK_TREE_VIEW_ARROW_PRELIT); + tree_view->priv->arrow_prelit = FALSE; gtk_tree_view_queue_draw_arrow (tree_view, tree, node); } @@ -3352,10 +3598,10 @@ do_prelight (GtkTreeView *tree_view, GTK_RBNODE_UNSET_FLAG (tree_view->priv->prelight_node, GTK_RBNODE_IS_PRELIT); - if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_ARROW_PRELIT) - && TREE_VIEW_DRAW_EXPANDERS (tree_view)) + if (tree_view->priv->arrow_prelit + && gtk_tree_view_draw_expanders (tree_view)) { - GTK_TREE_VIEW_UNSET_FLAG (tree_view, GTK_TREE_VIEW_ARROW_PRELIT); + tree_view->priv->arrow_prelit = FALSE; gtk_tree_view_queue_draw_arrow (tree_view, tree_view->priv->prelight_tree, @@ -3381,10 +3627,10 @@ do_prelight (GtkTreeView *tree_view, /* Prelight the new node and arrow */ - if (TREE_VIEW_DRAW_EXPANDERS (tree_view) + if (gtk_tree_view_draw_expanders (tree_view) && coords_are_over_arrow (tree_view, tree, node, x, y)) { - GTK_TREE_VIEW_SET_FLAG (tree_view, GTK_TREE_VIEW_ARROW_PRELIT); + tree_view->priv->arrow_prelit = TRUE; gtk_tree_view_queue_draw_arrow (tree_view, tree, node); } @@ -3413,7 +3659,8 @@ prelight_or_select (GtkTreeView *tree_view, if (tree_view->priv->hover_selection && (mode == GTK_SELECTION_SINGLE || mode == GTK_SELECTION_BROWSE) && !(tree_view->priv->edited_column && - tree_view->priv->edited_column->editable_widget)) + gtk_cell_area_get_edit_widget + (gtk_cell_layout_get_area (GTK_CELL_LAYOUT (tree_view->priv->edited_column))))) { if (node) { @@ -3425,7 +3672,7 @@ prelight_or_select (GtkTreeView *tree_view, gtk_tree_selection_select_path (tree_view->priv->selection, path); if (GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_SELECTED)) { - GTK_TREE_VIEW_UNSET_FLAG (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS); + tree_view->priv->draw_keyfocus = FALSE; gtk_tree_view_real_set_cursor (tree_view, path, FALSE, FALSE); } gtk_tree_path_free (path); @@ -3528,16 +3775,19 @@ gtk_tree_view_motion_draw_column_motion_arrow (GtkTreeView *tree_view) { GtkAllocation left_allocation, right_allocation; GdkRectangle visible_rect; + GtkWidget *button; gtk_tree_view_get_visible_rect (tree_view, &visible_rect); if (reorder->left_column) { - gtk_widget_get_allocation (reorder->left_column->button, &left_allocation); + button = gtk_tree_view_column_get_button (reorder->left_column); + gtk_widget_get_allocation (button, &left_allocation); x = left_allocation.x + left_allocation.width; } else { - gtk_widget_get_allocation (reorder->right_column->button, &right_allocation); + button = gtk_tree_view_column_get_button (reorder->right_column); + gtk_widget_get_allocation (button, &right_allocation); x = right_allocation.x; } @@ -3555,6 +3805,7 @@ gtk_tree_view_motion_draw_column_motion_arrow (GtkTreeView *tree_view) if (tree_view->priv->drag_column_window_state != DRAG_COLUMN_WINDOW_STATE_ORIGINAL) { GtkAllocation drag_allocation; + GtkWidget *button; if (tree_view->priv->drag_highlight_window) { @@ -3563,11 +3814,12 @@ gtk_tree_view_motion_draw_column_motion_arrow (GtkTreeView *tree_view) gdk_window_destroy (tree_view->priv->drag_highlight_window); } + button = gtk_tree_view_column_get_button (tree_view->priv->drag_column); attributes.window_type = GDK_WINDOW_CHILD; attributes.wclass = GDK_INPUT_OUTPUT; attributes.x = tree_view->priv->drag_column_x; attributes.y = 0; - gtk_widget_get_allocation (tree_view->priv->drag_column->button, &drag_allocation); + gtk_widget_get_allocation (button, &drag_allocation); width = attributes.width = drag_allocation.width; height = attributes.height = drag_allocation.height; attributes.visual = gtk_widget_get_visual (GTK_WIDGET (tree_view)); @@ -3596,6 +3848,7 @@ gtk_tree_view_motion_draw_column_motion_arrow (GtkTreeView *tree_view) else if (arrow_type == DRAG_COLUMN_WINDOW_STATE_ARROW) { GtkAllocation button_allocation; + GtkWidget *button; width = tree_view->priv->expander_size; @@ -3603,13 +3856,15 @@ gtk_tree_view_motion_draw_column_motion_arrow (GtkTreeView *tree_view) gdk_window_get_origin (tree_view->priv->header_window, &x, &y); if (reorder->left_column) { - gtk_widget_get_allocation (reorder->left_column->button, &button_allocation); + button = gtk_tree_view_column_get_button (reorder->left_column); + gtk_widget_get_allocation (button, &button_allocation); x += button_allocation.x + button_allocation.width - width/2; height = button_allocation.height; } else { - gtk_widget_get_allocation (reorder->right_column->button, &button_allocation); + button = gtk_tree_view_column_get_button (reorder->right_column); + gtk_widget_get_allocation (button, &button_allocation); x += button_allocation.x - width/2; height = button_allocation.height; } @@ -3666,6 +3921,7 @@ gtk_tree_view_motion_draw_column_motion_arrow (GtkTreeView *tree_view) arrow_type == DRAG_COLUMN_WINDOW_STATE_ARROW_RIGHT) { GtkAllocation allocation; + GtkWidget *button; width = tree_view->priv->expander_size; @@ -3681,12 +3937,14 @@ gtk_tree_view_motion_draw_column_motion_arrow (GtkTreeView *tree_view) if (reorder->left_column) { - gtk_widget_get_allocation (reorder->left_column->button, &allocation); + button = gtk_tree_view_column_get_button (reorder->left_column); + gtk_widget_get_allocation (button, &allocation); height = allocation.height; } else { - gtk_widget_get_allocation (reorder->right_column->button, &allocation); + button = gtk_tree_view_column_get_button (reorder->right_column); + gtk_widget_get_allocation (button, &allocation); height = allocation.height; } @@ -3780,10 +4038,14 @@ gtk_tree_view_motion_resize_column (GtkWidget *widget, if (x != tree_view->priv->x_drag && (new_width != gtk_tree_view_column_get_fixed_width (column))) { - column->use_resized_width = TRUE; - column->resized_width = new_width; + _gtk_tree_view_column_set_use_resized_width (column, TRUE); + if (gtk_tree_view_column_get_expand (column)) - column->resized_width -= tree_view->priv->last_extra_space_per_column; + new_width -= tree_view->priv->last_extra_space_per_column; + + _gtk_tree_view_column_set_resized_width (column, new_width); + + gtk_widget_queue_resize (widget); } @@ -3874,6 +4136,7 @@ gtk_tree_view_motion_drag_column (GtkWidget *widget, GtkAllocation allocation, button_allocation; GtkTreeView *tree_view = (GtkTreeView *) widget; GtkTreeViewColumn *column = tree_view->priv->drag_column; + GtkWidget *button; gint x, y; /* Sanity Check */ @@ -3881,11 +4144,13 @@ gtk_tree_view_motion_drag_column (GtkWidget *widget, (event->window != tree_view->priv->drag_window)) return FALSE; + button = gtk_tree_view_column_get_button (column); + /* Handle moving the header */ gdk_window_get_position (tree_view->priv->drag_window, &x, &y); gtk_widget_get_allocation (widget, &allocation); - gtk_widget_get_allocation (column->button, &button_allocation); - x = CLAMP (x + (gint)event->x - column->drag_x, 0, + gtk_widget_get_allocation (button, &button_allocation); + x = CLAMP (x + (gint)event->x - _gtk_tree_view_column_get_drag_x (column), 0, MAX (tree_view->priv->width, allocation.width) - button_allocation.width); gdk_window_move (tree_view->priv->drag_window, x, y); @@ -4294,11 +4559,11 @@ gtk_tree_view_motion (GtkWidget *widget, tree_view = (GtkTreeView *) widget; /* Resizing a column */ - if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IN_COLUMN_RESIZE)) + if (tree_view->priv->in_column_resize) return gtk_tree_view_motion_resize_column (widget, event); /* Drag column */ - if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IN_COLUMN_DRAG)) + if (tree_view->priv->in_column_drag) return gtk_tree_view_motion_drag_column (widget, event); /* Sanity check it */ @@ -4476,7 +4741,7 @@ gtk_tree_view_bin_draw (GtkWidget *widget, gint horizontal_separator; gint focus_line_width; gboolean allow_rules; - gboolean has_special_cell; + gboolean has_can_focus_cell; gboolean rtl; gint n_visible_columns; gint pointer_x, pointer_y; @@ -4603,7 +4868,7 @@ gtk_tree_view_bin_draw (GtkWidget *widget, is_separator = row_is_separator (tree_view, &iter, NULL); - max_height = ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node)); + max_height = gtk_tree_view_get_row_height (tree_view, node); cell_offset = 0; highlight_x = 0; /* should match x coord of first cell */ @@ -4623,7 +4888,7 @@ gtk_tree_view_bin_draw (GtkWidget *widget, parity = _gtk_rbtree_node_find_parity (tree, node); /* we *need* to set cell data on all cells before the call - * to _has_special_cell, else _has_special_cell() does not + * to _has_can_focus_cell, else _has_can_focus_cell() does not * return a correct value. */ for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns)); @@ -4638,7 +4903,7 @@ gtk_tree_view_bin_draw (GtkWidget *widget, node->children?TRUE:FALSE); } - has_special_cell = gtk_tree_view_has_special_cell (tree_view); + has_can_focus_cell = gtk_tree_view_has_can_focus_cell (tree_view); for (list = (rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns)); list; @@ -4649,6 +4914,7 @@ gtk_tree_view_bin_draw (GtkWidget *widget, gchar new_detail[128]; gint width; GtkStateType state; + gboolean draw_focus; if (!gtk_tree_view_column_get_visible (column)) continue; @@ -4767,6 +5033,15 @@ gtk_tree_view_bin_draw (GtkWidget *widget, else state = GTK_STATE_NORMAL; + if (node == cursor && has_can_focus_cell + && ((column == tree_view->priv->focus_column + && tree_view->priv->draw_keyfocus && + gtk_widget_has_focus (widget)) + || (column == tree_view->priv->edited_column))) + draw_focus = TRUE; + else + draw_focus = FALSE; + /* Draw background */ is_first = (rtl ? !list->next : !list->prev); is_last = (rtl ? !list->prev : !list->next); @@ -4800,7 +5075,7 @@ gtk_tree_view_bin_draw (GtkWidget *widget, cell_area.x += (depth - 1) * tree_view->priv->level_indentation; cell_area.width -= (depth - 1) * tree_view->priv->level_indentation; - if (TREE_VIEW_DRAW_EXPANDERS(tree_view)) + if (gtk_tree_view_draw_expanders (tree_view)) { if (!rtl) cell_area.x += depth * tree_view->priv->expander_size; @@ -4828,8 +5103,9 @@ gtk_tree_view_bin_draw (GtkWidget *widget, cr, &background_area, &cell_area, - flags); - if (TREE_VIEW_DRAW_EXPANDERS(tree_view) + flags, + draw_focus); + if (gtk_tree_view_draw_expanders (tree_view) && (node->flags & GTK_RBNODE_IS_PARENT) == GTK_RBNODE_IS_PARENT) { if (!got_pointer) @@ -4862,7 +5138,8 @@ gtk_tree_view_bin_draw (GtkWidget *widget, cr, &background_area, &cell_area, - flags); + flags, + draw_focus); } if (draw_hgrid_lines) @@ -4954,19 +5231,6 @@ gtk_tree_view_bin_draw (GtkWidget *widget, } } - if (node == cursor && has_special_cell && - ((column == tree_view->priv->focus_column && - GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS) && - gtk_widget_has_focus (widget)) || - (column == tree_view->priv->edited_column))) - { - _gtk_tree_view_column_cell_draw_focus (column, - cr, - &background_area, - &cell_area, - flags); - } - cell_offset += gtk_tree_view_column_get_width (column); } @@ -5004,10 +5268,10 @@ gtk_tree_view_bin_draw (GtkWidget *widget, (is_first ? (is_last ? "treeview-drop-indicator" : "treeview-drop-indicator-left" ) : (is_last ? "treeview-drop-indicator-right" : "tree-view-drop-indicator-middle" )), - 0, BACKGROUND_FIRST_PIXEL (tree_view, tree, node) + 0, gtk_tree_view_get_row_y_offset (tree_view, tree, node) - focus_line_width / 2, gdk_window_get_width (tree_view->priv->bin_window), - ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node)) + gtk_tree_view_get_row_height (tree_view, node) - focus_line_width + 1); break; } @@ -5024,8 +5288,8 @@ gtk_tree_view_bin_draw (GtkWidget *widget, } /* draw the big row-spanning focus rectangle, if needed */ - if (!has_special_cell && node == cursor && - GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS) && + if (!has_can_focus_cell && node == cursor && + tree_view->priv->draw_keyfocus && gtk_widget_has_focus (widget)) { gint tmp_y, tmp_height; @@ -5039,13 +5303,13 @@ gtk_tree_view_bin_draw (GtkWidget *widget, if (draw_hgrid_lines) { - tmp_y = BACKGROUND_FIRST_PIXEL (tree_view, tree, node) + grid_line_width / 2; - tmp_height = ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node)) - grid_line_width; + tmp_y = gtk_tree_view_get_row_y_offset (tree_view, tree, node) + grid_line_width / 2; + tmp_height = gtk_tree_view_get_row_height (tree_view, node) - grid_line_width; } else { - tmp_y = BACKGROUND_FIRST_PIXEL (tree_view, tree, node); - tmp_height = ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node)); + tmp_y = gtk_tree_view_get_row_y_offset (tree_view, tree, node); + tmp_height = gtk_tree_view_get_row_height (tree_view, node); } gtk_paint_focus (style, @@ -5140,6 +5404,7 @@ gtk_tree_view_draw (GtkWidget *widget, cairo_t *cr) { GtkTreeView *tree_view = GTK_TREE_VIEW (widget); + GtkWidget *button; if (gtk_cairo_should_draw_window (cr, tree_view->priv->bin_window)) { @@ -5179,18 +5444,20 @@ gtk_tree_view_draw (GtkWidget *widget, continue; if (gtk_tree_view_column_get_visible (column)) - gtk_container_propagate_draw (GTK_CONTAINER (tree_view), - column->button, - cr); + { + button = gtk_tree_view_column_get_button (column); + gtk_container_propagate_draw (GTK_CONTAINER (tree_view), + button, cr); + } } } if (tree_view->priv->drag_window && gtk_cairo_should_draw_window (cr, tree_view->priv->drag_window)) { + button = gtk_tree_view_column_get_button (tree_view->priv->drag_column); gtk_container_propagate_draw (GTK_CONTAINER (tree_view), - tree_view->priv->drag_column->button, - cr); + button, cr); } return TRUE; @@ -5415,6 +5682,7 @@ gtk_tree_view_key_press (GtkWidget *widget, GdkEventKey *event) { GtkTreeView *tree_view = (GtkTreeView *) widget; + GtkWidget *button; if (tree_view->priv->rubber_band_status) { @@ -5424,7 +5692,7 @@ gtk_tree_view_key_press (GtkWidget *widget, return TRUE; } - if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IN_COLUMN_DRAG)) + if (tree_view->priv->in_column_drag) { if (event->keyval == GDK_KEY_Escape) { @@ -5434,7 +5702,7 @@ gtk_tree_view_key_press (GtkWidget *widget, return TRUE; } - if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_HEADERS_VISIBLE)) + if (tree_view->priv->headers_visible) { GList *focus_column; gboolean rtl; @@ -5446,8 +5714,9 @@ gtk_tree_view_key_press (GtkWidget *widget, focus_column = focus_column->next) { GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN (focus_column->data); - - if (gtk_widget_has_focus (column->button)) + + button = gtk_tree_view_column_get_button (column); + if (gtk_widget_has_focus (button)) break; } @@ -5469,54 +5738,62 @@ gtk_tree_view_key_press (GtkWidget *widget, || event->keyval == (rtl ? GDK_KEY_KP_Right : GDK_KEY_KP_Left)) { GtkRequisition button_req; - gint old_width = column->resized_width; + gint old_width = _gtk_tree_view_column_get_resized_width (column); + gint new_width; + + button = gtk_tree_view_column_get_button (column); + + gtk_widget_get_preferred_size (button, &button_req, NULL); - gtk_widget_get_preferred_size (column->button, &button_req, NULL); + new_width = MAX (old_width, gtk_tree_view_column_get_width (column)); + new_width -= 2; + if (new_width < 0) + new_width = 0; - column->resized_width = MAX (column->resized_width, - gtk_tree_view_column_get_width (column)); - column->resized_width -= 2; - if (column->resized_width < 0) - column->resized_width = 0; + _gtk_tree_view_column_set_resized_width (column, new_width); min_width = gtk_tree_view_column_get_min_width (column); if (min_width == -1) - column->resized_width = MAX (button_req.width, - column->resized_width); + new_width = MAX (button_req.width, new_width); else { - column->resized_width = MAX (min_width, - column->resized_width); + new_width = MAX (min_width, new_width); } max_width = gtk_tree_view_column_get_max_width (column); if (max_width != -1) - column->resized_width = MIN (column->resized_width, max_width); + new_width = MIN (new_width, max_width); - column->use_resized_width = TRUE; + _gtk_tree_view_column_set_use_resized_width (column, TRUE); - if (column->resized_width != old_width) - gtk_widget_queue_resize (widget); + if (new_width != old_width) + { + _gtk_tree_view_column_set_resized_width (column, new_width); + gtk_widget_queue_resize (widget); + } else gtk_widget_error_bell (widget); } else if (event->keyval == (rtl ? GDK_KEY_Left : GDK_KEY_Right) || event->keyval == (rtl ? GDK_KEY_KP_Left : GDK_KEY_KP_Right)) { - gint old_width = column->resized_width; + gint old_width = _gtk_tree_view_column_get_resized_width (column); + gint new_width; - column->resized_width = MAX (column->resized_width, - gtk_tree_view_column_get_width (column)); - column->resized_width += 2; + new_width = MAX (old_width, gtk_tree_view_column_get_width (column)); + new_width += 2; max_width = gtk_tree_view_column_get_max_width (column); if (max_width != -1) - column->resized_width = MIN (column->resized_width, max_width); + new_width = MIN (new_width, max_width); - column->use_resized_width = TRUE; + _gtk_tree_view_column_set_use_resized_width (column, TRUE); - if (column->resized_width != old_width) - gtk_widget_queue_resize (widget); + if (new_width != old_width) + { + _gtk_tree_view_column_set_resized_width (column, new_width); + gtk_widget_queue_resize (widget); + } else gtk_widget_error_bell (widget); } @@ -5770,7 +6047,7 @@ gtk_tree_view_node_queue_redraw (GtkTreeView *tree_view, y = _gtk_rbtree_node_find_offset (tree, node) - tree_view->priv->vadjustment->value - + TREE_VIEW_HEADER_HEIGHT (tree_view); + + gtk_tree_view_get_effective_header_height (tree_view); gtk_widget_get_allocation (GTK_WIDGET (tree_view), &allocation); gtk_widget_queue_draw_area (GTK_WIDGET (tree_view), @@ -5788,7 +6065,7 @@ node_is_visible (GtkTreeView *tree_view, int height; y = _gtk_rbtree_node_find_offset (tree, node); - height = ROW_HEIGHT (tree_view, GTK_RBNODE_GET_HEIGHT (node)); + height = gtk_tree_view_get_row_height (tree_view, node); if (y >= tree_view->priv->vadjustment->value && y + height <= (tree_view->priv->vadjustment->value @@ -5812,7 +6089,6 @@ validate_row (GtkTreeView *tree_view, gint height = 0; gint horizontal_separator; gint vertical_separator; - gint focus_line_width; gint depth = gtk_tree_path_get_depth (path); gboolean retval = FALSE; gboolean is_separator = FALSE; @@ -5831,7 +6107,6 @@ validate_row (GtkTreeView *tree_view, gtk_widget_style_get (GTK_WIDGET (tree_view), "focus-padding", &focus_pad, - "focus-line-width", &focus_line_width, "horizontal-separator", &horizontal_separator, "vertical-separator", &vertical_separator, "grid-line-width", &grid_line_width, @@ -5860,28 +6135,33 @@ validate_row (GtkTreeView *tree_view, for (list = tree_view->priv->columns; list; list = list->next) { - gint tmp_width; - gint tmp_height; + gint padding = 0; + gint original_width; + gint new_width; + gint row_height; column = list->data; if (!gtk_tree_view_column_get_visible (column)) continue; - if (GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_COLUMN_INVALID) && !column->dirty) + if (GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_COLUMN_INVALID) && + !_gtk_tree_view_column_cell_get_dirty (column)) continue; + original_width = _gtk_tree_view_column_get_requested_width (column); + gtk_tree_view_column_cell_set_cell_data (column, tree_view->priv->model, iter, GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_PARENT), node->children?TRUE:FALSE); gtk_tree_view_column_cell_get_size (column, NULL, NULL, NULL, - &tmp_width, &tmp_height); + NULL, &row_height); if (!is_separator) { - tmp_height += vertical_separator; - height = MAX (height, tmp_height); + row_height += vertical_separator; + height = MAX (height, row_height); height = MAX (height, tree_view->priv->expander_size); } else @@ -5894,27 +6174,28 @@ validate_row (GtkTreeView *tree_view, if (gtk_tree_view_is_expander_column (tree_view, column)) { - tmp_width = tmp_width + horizontal_separator + (depth - 1) * tree_view->priv->level_indentation; + padding += horizontal_separator + (depth - 1) * tree_view->priv->level_indentation; - if (TREE_VIEW_DRAW_EXPANDERS (tree_view)) - tmp_width += depth * tree_view->priv->expander_size; + if (gtk_tree_view_draw_expanders (tree_view)) + padding += depth * tree_view->priv->expander_size; } else - tmp_width = tmp_width + horizontal_separator; + padding += horizontal_separator; if (draw_vgrid_lines) { if (list->data == first_column || list->data == last_column) - tmp_width += grid_line_width / 2.0; + padding += grid_line_width / 2.0; else - tmp_width += grid_line_width; + padding += grid_line_width; } - if (tmp_width > column->requested_width) - { - retval = TRUE; - column->requested_width = tmp_width; - } + /* Update the padding for the column */ + _gtk_tree_view_column_push_padding (column, padding); + new_width = _gtk_tree_view_column_get_requested_width (column); + + if (new_width > original_width) + retval = TRUE; } if (draw_hgrid_lines) @@ -5955,7 +6236,7 @@ validate_visible_area (GtkTreeView *tree_view) return; gtk_widget_get_allocation (GTK_WIDGET (tree_view), &allocation); - total_height = allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view); + total_height = allocation.height - gtk_tree_view_get_effective_header_height (tree_view); if (total_height == 0) return; @@ -5979,7 +6260,7 @@ validate_visible_area (GtkTreeView *tree_view) if (tree_view->priv->scroll_to_use_align) { - gint height = ROW_HEIGHT (tree_view, GTK_RBNODE_GET_HEIGHT (node)); + gint height = gtk_tree_view_get_row_height (tree_view, node); area_above = (total_height - height) * tree_view->priv->scroll_to_row_align; area_below = total_height - area_above - height; @@ -5993,7 +6274,7 @@ validate_visible_area (GtkTreeView *tree_view) * 2) row visible */ gint dy; - gint height = ROW_HEIGHT (tree_view, GTK_RBNODE_GET_HEIGHT (node)); + gint height = gtk_tree_view_get_row_height (tree_view, node); dy = _gtk_rbtree_node_find_offset (tree, node); @@ -6090,7 +6371,7 @@ validate_visible_area (GtkTreeView *tree_view) size_changed = TRUE; } area_above = 0; - area_below = total_height - ROW_HEIGHT (tree_view, GTK_RBNODE_GET_HEIGHT (node)); + area_below = total_height - gtk_tree_view_get_row_height (tree_view, node); } above_path = gtk_tree_path_copy (path); @@ -6201,7 +6482,7 @@ validate_visible_area (GtkTreeView *tree_view) size_changed = TRUE; } - area_below -= ROW_HEIGHT (tree_view, GTK_RBNODE_GET_HEIGHT (node)); + area_below -= gtk_tree_view_get_row_height (tree_view, node); } gtk_tree_path_free (path); @@ -6241,7 +6522,7 @@ validate_visible_area (GtkTreeView *tree_view) if (validate_row (tree_view, tree, node, &iter, above_path)) size_changed = TRUE; } - area_above -= ROW_HEIGHT (tree_view, GTK_RBNODE_GET_HEIGHT (node)); + area_above -= gtk_tree_view_get_row_height (tree_view, node); } /* if we scrolled to a path, we need to set the dy here, @@ -6329,7 +6610,7 @@ initialize_fixed_height_mode (GtkTreeView *tree_view) gtk_tree_path_free (path); - tree_view->priv->fixed_height = ROW_HEIGHT (tree_view, GTK_RBNODE_GET_HEIGHT (node)); + tree_view->priv->fixed_height = gtk_tree_view_get_row_height (tree_view, node); } _gtk_rbtree_set_fixed_height (tree_view->priv->tree, @@ -6441,7 +6722,7 @@ do_validate_rows (GtkTreeView *tree_view, gboolean queue_resize) { gint height; - height = ROW_HEIGHT (tree_view, GTK_RBNODE_GET_HEIGHT (node)); + height = gtk_tree_view_get_row_height (tree_view, node); if (prev_height < 0) prev_height = height; else if (prev_height != height) @@ -6479,7 +6760,7 @@ do_validate_rows (GtkTreeView *tree_view, gboolean queue_resize) * Currently bypassing this but the real solution is to not update the scroll adjustments * untill we've recieved an allocation (never update scroll adjustments from size-requests). */ - gtk_tree_view_size_request (GTK_WIDGET (tree_view), &requisition); + gtk_tree_view_size_request (GTK_WIDGET (tree_view), &requisition, FALSE); tree_view->priv->hadjustment->upper = MAX (tree_view->priv->hadjustment->upper, (gfloat)requisition.width); tree_view->priv->vadjustment->upper = MAX (tree_view->priv->vadjustment->upper, (gfloat)requisition.height); @@ -6714,7 +6995,7 @@ gtk_tree_view_top_row_to_dy (GtkTreeView *tree_view) return; } - if (ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node)) + if (gtk_tree_view_get_row_height (tree_view, node) < tree_view->priv->top_row_dy) { /* new top row -- do NOT install the idle handler */ @@ -6737,11 +7018,13 @@ gtk_tree_view_top_row_to_dy (GtkTreeView *tree_view) void -_gtk_tree_view_install_mark_rows_col_dirty (GtkTreeView *tree_view) +_gtk_tree_view_install_mark_rows_col_dirty (GtkTreeView *tree_view, + gboolean install_handler) { tree_view->priv->mark_rows_col_dirty = TRUE; - install_presize_handler (tree_view); + if (install_handler) + install_presize_handler (tree_view); } /* @@ -7102,7 +7385,7 @@ set_destination_row (GtkTreeView *tree_view, di = get_info (tree_view); - if (di == NULL || y - TREE_VIEW_HEADER_HEIGHT (tree_view) < 0) + if (di == NULL || y - gtk_tree_view_get_effective_header_height (tree_view) < 0) { /* someone unset us as a drag dest, note that if * we return FALSE drag_leave isn't called @@ -7782,8 +8065,12 @@ gtk_tree_view_remove (GtkContainer *container, while (tmp_list) { GtkTreeViewColumn *column; + GtkWidget *button; + column = tmp_list->data; - if (column->button == widget) + button = gtk_tree_view_column_get_button (column); + + if (button == widget) { gtk_widget_unparent (widget); return; @@ -7801,6 +8088,7 @@ gtk_tree_view_forall (GtkContainer *container, GtkTreeView *tree_view = GTK_TREE_VIEW (container); GtkTreeViewChild *child = NULL; GtkTreeViewColumn *column; + GtkWidget *button; GList *tmp_list; tmp_list = tree_view->priv->children; @@ -7817,25 +8105,29 @@ gtk_tree_view_forall (GtkContainer *container, for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next) { column = tmp_list->data; + button = gtk_tree_view_column_get_button (column); - if (column->button) - (* callback) (column->button, callback_data); + if (button) + (* callback) (button, callback_data); } } -/* Returns TRUE if the treeview contains no "special" (editable or activatable) - * cells. If so we draw one big row-spanning focus rectangle. +/* Returns TRUE is any of the columns contains a cell that can-focus. + * If this is not the case, a column-spanning focus rectangle will be + * drawn. */ static gboolean -gtk_tree_view_has_special_cell (GtkTreeView *tree_view) +gtk_tree_view_has_can_focus_cell (GtkTreeView *tree_view) { GList *list; for (list = tree_view->priv->columns; list; list = list->next) { - if (!gtk_tree_view_column_get_visible (GTK_TREE_VIEW_COLUMN (list->data))) + GtkTreeViewColumn *column = list->data; + + if (!gtk_tree_view_column_get_visible (column)) continue; - if (_gtk_tree_view_column_count_special_cells (list->data)) + if (gtk_cell_area_is_activatable (gtk_cell_layout_get_area (GTK_CELL_LAYOUT (column)))) return TRUE; } @@ -7937,12 +8229,12 @@ gtk_tree_view_header_focus (GtkTreeView *tree_view, { GtkTreeViewColumn *column; GtkWidget *focus_child; - + GtkWidget *button; GList *last_column, *first_column; GList *tmp_list; gboolean rtl; - if (! GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_HEADERS_VISIBLE)) + if (! tree_view->priv->headers_visible) return FALSE; focus_child = gtk_container_get_focus_child (GTK_CONTAINER (tree_view)); @@ -7951,8 +8243,9 @@ gtk_tree_view_header_focus (GtkTreeView *tree_view, while (first_column) { column = GTK_TREE_VIEW_COLUMN (first_column->data); + button = gtk_tree_view_column_get_button (column); - if (gtk_widget_get_can_focus (column->button) && + if (gtk_widget_get_can_focus (button) && gtk_tree_view_column_get_visible (column) && (gtk_tree_view_column_get_clickable (column) || gtk_tree_view_column_get_reorderable (column))) @@ -7969,8 +8262,9 @@ gtk_tree_view_header_focus (GtkTreeView *tree_view, while (last_column) { column = GTK_TREE_VIEW_COLUMN (last_column->data); + button = gtk_tree_view_column_get_button (column); - if (gtk_widget_get_can_focus (column->button) && + if (gtk_widget_get_can_focus (button) && gtk_tree_view_column_get_visible (column) && (gtk_tree_view_column_get_clickable (column) || gtk_tree_view_column_get_reorderable (column))) @@ -7989,11 +8283,16 @@ gtk_tree_view_header_focus (GtkTreeView *tree_view, case GTK_DIR_DOWN: if (focus_child == NULL) { - if (tree_view->priv->focus_column != NULL && - gtk_widget_get_can_focus (tree_view->priv->focus_column->button)) - focus_child = tree_view->priv->focus_column->button; + if (tree_view->priv->focus_column != NULL) + button = gtk_tree_view_column_get_button (tree_view->priv->focus_column); + else + button = NULL; + + if (button && gtk_widget_get_can_focus (button)) + focus_child = button; else - focus_child = GTK_TREE_VIEW_COLUMN (first_column->data)->button; + focus_child = gtk_tree_view_column_get_button (GTK_TREE_VIEW_COLUMN (first_column->data)); + gtk_widget_grab_focus (focus_child); break; } @@ -8004,11 +8303,12 @@ gtk_tree_view_header_focus (GtkTreeView *tree_view, if (focus_child == NULL) { if (tree_view->priv->focus_column != NULL) - focus_child = tree_view->priv->focus_column->button; + focus_child = gtk_tree_view_column_get_button (tree_view->priv->focus_column); else if (dir == GTK_DIR_LEFT) - focus_child = GTK_TREE_VIEW_COLUMN (last_column->data)->button; + focus_child = gtk_tree_view_column_get_button (GTK_TREE_VIEW_COLUMN (last_column->data)); else - focus_child = GTK_TREE_VIEW_COLUMN (first_column->data)->button; + focus_child = gtk_tree_view_column_get_button (GTK_TREE_VIEW_COLUMN (first_column->data)); + gtk_widget_grab_focus (focus_child); break; } @@ -8022,7 +8322,7 @@ gtk_tree_view_header_focus (GtkTreeView *tree_view, /* We need to move the focus among the row of buttons. */ for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next) - if (GTK_TREE_VIEW_COLUMN (tmp_list->data)->button == focus_child) + if (gtk_tree_view_column_get_button (GTK_TREE_VIEW_COLUMN (tmp_list->data)) == focus_child) break; if ((tmp_list == first_column && dir == (rtl ? GTK_DIR_RIGHT : GTK_DIR_LEFT)) @@ -8035,6 +8335,7 @@ gtk_tree_view_header_focus (GtkTreeView *tree_view, while (tmp_list) { GtkTreeViewColumn *column; + GtkWidget *button; if (dir == (rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT)) tmp_list = tmp_list->next; @@ -8047,12 +8348,13 @@ gtk_tree_view_header_focus (GtkTreeView *tree_view, break; } column = tmp_list->data; - if (column->button && + button = gtk_tree_view_column_get_button (column); + if (button && gtk_tree_view_column_get_visible (column) && - gtk_widget_get_can_focus (column->button)) + gtk_widget_get_can_focus (button)) { - focus_child = column->button; - gtk_widget_grab_focus (column->button); + focus_child = button; + gtk_widget_grab_focus (button); break; } } @@ -8067,7 +8369,7 @@ gtk_tree_view_header_focus (GtkTreeView *tree_view, if (focus_child) { for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next) - if (GTK_TREE_VIEW_COLUMN (tmp_list->data)->button == focus_child) + if (gtk_tree_view_column_get_button (GTK_TREE_VIEW_COLUMN (tmp_list->data)) == focus_child) { tree_view->priv->focus_column = GTK_TREE_VIEW_COLUMN (tmp_list->data); break; @@ -8239,7 +8541,7 @@ gtk_tree_view_set_focus_child (GtkContainer *container, for (list = tree_view->priv->columns; list; list = list->next) { - if (GTK_TREE_VIEW_COLUMN (list->data)->button == child) + if (gtk_tree_view_column_get_button (GTK_TREE_VIEW_COLUMN (list->data)) == child) { tree_view->priv->focus_column = GTK_TREE_VIEW_COLUMN (list->data); break; @@ -8269,7 +8571,7 @@ gtk_tree_view_real_move_cursor (GtkTreeView *tree_view, return FALSE; gtk_tree_view_stop_editing (tree_view, FALSE); - GTK_TREE_VIEW_SET_FLAG (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS); + tree_view->priv->draw_keyfocus = TRUE; gtk_widget_grab_focus (GTK_WIDGET (tree_view)); if (gtk_get_current_event_state (&state)) @@ -8617,17 +8919,16 @@ gtk_tree_view_row_has_child_toggled (GtkTreeModel *model, else GTK_RBNODE_UNSET_FLAG (node, GTK_RBNODE_IS_PARENT); - if (has_child && GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IS_LIST)) + if (has_child && tree_view->priv->is_list) { - GTK_TREE_VIEW_UNSET_FLAG (tree_view, GTK_TREE_VIEW_IS_LIST); - if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_SHOW_EXPANDERS)) + tree_view->priv->is_list = FALSE; + if (tree_view->priv->show_expanders) { GList *list; for (list = tree_view->priv->columns; list; list = list->next) if (gtk_tree_view_column_get_visible (GTK_TREE_VIEW_COLUMN (list->data))) { - GTK_TREE_VIEW_COLUMN (list->data)->dirty = TRUE; _gtk_tree_view_column_cell_set_dirty (GTK_TREE_VIEW_COLUMN (list->data), TRUE); break; } @@ -8849,6 +9150,7 @@ gtk_tree_view_get_background_xrange (GtkTreeView *tree_view, *x2 = total_width; /* width of 0 */ } } + static void gtk_tree_view_get_arrow_xrange (GtkTreeView *tree_view, GtkRBTree *tree, @@ -8915,7 +9217,6 @@ gtk_tree_view_build_tree (GtkTreeView *tree_view, { GtkRBNode *temp = NULL; GtkTreePath *path = NULL; - gboolean is_list = GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IS_LIST); do { @@ -8931,7 +9232,7 @@ gtk_tree_view_build_tree (GtkTreeView *tree_view, } } - if (is_list) + if (tree_view->priv->is_list) continue; if (recurse) @@ -8986,7 +9287,7 @@ gtk_tree_view_clamp_node_visible (GtkTreeView *tree_view, /* just return if the node is visible, avoiding a costly expose */ node_dy = _gtk_rbtree_node_find_offset (tree, node); - height = ROW_HEIGHT (tree_view, GTK_RBNODE_GET_HEIGHT (node)); + height = gtk_tree_view_get_row_height (tree_view, node); if (! GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_INVALID) && node_dy >= tree_view->priv->vadjustment->value && node_dy + height <= (tree_view->priv->vadjustment->value @@ -9016,7 +9317,7 @@ gtk_tree_view_clamp_column_visible (GtkTreeView *tree_view, if (column == NULL) return; - gtk_widget_get_allocation (column->button, &allocation); + gtk_widget_get_allocation (gtk_tree_view_column_get_button (column), &allocation); x = allocation.x; width = allocation.width; @@ -9028,42 +9329,31 @@ gtk_tree_view_clamp_column_visible (GtkTreeView *tree_view, * focus cell is bigger than the page size, we make sure the * left-hand side of the cell is visible). * - * If the column does not have those so-called special cells, we + * If the column does not have an activatable cell, we * make sure the left-hand side of the column is visible. */ - if (focus_to_cell && gtk_tree_view_has_special_cell (tree_view)) + if (focus_to_cell && gtk_tree_view_has_can_focus_cell (tree_view)) { - GtkTreePath *cursor_path; - GdkRectangle background_area, cell_area, focus_area; + GtkCellArea *cell_area; + GtkCellRenderer *focus_cell; - cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor); + cell_area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (column)); + focus_cell = gtk_cell_area_get_focus_cell (cell_area); - gtk_tree_view_get_cell_area (tree_view, - cursor_path, column, &cell_area); - gtk_tree_view_get_background_area (tree_view, - cursor_path, column, - &background_area); - - gtk_tree_path_free (cursor_path); - - _gtk_tree_view_column_get_focus_area (column, - &background_area, - &cell_area, - &focus_area); - - x = focus_area.x; - width = focus_area.width; - - if (width < tree_view->priv->hadjustment->page_size) - { - if ((tree_view->priv->hadjustment->value + tree_view->priv->hadjustment->page_size) < (x + width)) - gtk_adjustment_set_value (tree_view->priv->hadjustment, - x + width - tree_view->priv->hadjustment->page_size); - else if (tree_view->priv->hadjustment->value > x) - gtk_adjustment_set_value (tree_view->priv->hadjustment, x); - } - } + if (gtk_tree_view_column_cell_get_position (column, focus_cell, + &x, &width)) + { + if (width < tree_view->priv->hadjustment->page_size) + { + if (tree_view->priv->hadjustment->value + tree_view->priv->hadjustment->page_size < x + width) + gtk_adjustment_set_value (tree_view->priv->hadjustment, + x + width - tree_view->priv->hadjustment->page_size); + else if (tree_view->priv->hadjustment->value > x) + gtk_adjustment_set_value (tree_view->priv->hadjustment, x); + } + } + } gtk_adjustment_set_value (tree_view->priv->hadjustment, x); } @@ -9172,7 +9462,7 @@ gtk_tree_view_is_expander_column (GtkTreeView *tree_view, { GList *list; - if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IS_LIST)) + if (tree_view->priv->is_list) return FALSE; if (tree_view->priv->expander_column != NULL) @@ -9194,6 +9484,15 @@ gtk_tree_view_is_expander_column (GtkTreeView *tree_view, return FALSE; } +static inline gboolean +gtk_tree_view_draw_expanders (GtkTreeView *tree_view) +{ + if (!tree_view->priv->is_list && tree_view->priv->show_expanders) + return TRUE; + /* else */ + return FALSE; +} + static void gtk_tree_view_add_move_binding (GtkBindingSet *binding_set, guint keyval, @@ -9380,11 +9679,16 @@ gtk_tree_view_set_column_drag_info (GtkTreeView *tree_view, if (tmp_list->next != NULL) { GtkAllocation right_allocation, left_allocation; + GtkWidget *left_button, *right_button; g_assert (tmp_list->next->data); - gtk_widget_get_allocation (reorder->right_column->button, &right_allocation); - gtk_widget_get_allocation (((GtkTreeViewColumnReorder *)tmp_list->next->data)->left_column->button, &left_allocation); + right_button = gtk_tree_view_column_get_button (reorder->right_column); + left_button = gtk_tree_view_column_get_button + (((GtkTreeViewColumnReorder *)tmp_list->next->data)->left_column); + + gtk_widget_get_allocation (right_button, &right_allocation); + gtk_widget_get_allocation (left_button, &left_allocation); left = reorder->right_align = (right_allocation.x + right_allocation.width + left_allocation.x) / 2; } else @@ -9405,6 +9709,7 @@ _gtk_tree_view_column_start_drag (GtkTreeView *tree_view, GtkAllocation button_allocation; GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (tree_view)); GdkDisplay *display = gdk_screen_get_display (screen); + GtkWidget *button; g_return_if_fail (tree_view->priv->column_drag_info == NULL); g_return_if_fail (tree_view->priv->cur_reorder == NULL); @@ -9414,12 +9719,14 @@ _gtk_tree_view_column_start_drag (GtkTreeView *tree_view, if (tree_view->priv->column_drag_info == NULL) return; + button = gtk_tree_view_column_get_button (column); + if (tree_view->priv->drag_window == NULL) { GdkWindowAttr attributes; guint attributes_mask; - gtk_widget_get_allocation (column->button, &button_allocation); + gtk_widget_get_allocation (button, &button_allocation); attributes.window_type = GDK_WINDOW_CHILD; attributes.wclass = GDK_INPUT_OUTPUT; @@ -9440,17 +9747,17 @@ _gtk_tree_view_column_start_drag (GtkTreeView *tree_view, gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME); gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME); - gtk_grab_remove (column->button); + gtk_grab_remove (button); send_event = gdk_event_new (GDK_LEAVE_NOTIFY); send_event->crossing.send_event = TRUE; - send_event->crossing.window = g_object_ref (gtk_button_get_event_window (GTK_BUTTON (column->button))); + send_event->crossing.window = g_object_ref (gtk_button_get_event_window (GTK_BUTTON (button))); send_event->crossing.subwindow = NULL; send_event->crossing.detail = GDK_NOTIFY_ANCESTOR; send_event->crossing.time = GDK_CURRENT_TIME; gdk_event_set_device (send_event, device); - gtk_propagate_event (column->button, send_event); + gtk_propagate_event (button, send_event); gdk_event_free (send_event); send_event = gdk_event_new (GDK_BUTTON_RELEASE); @@ -9466,22 +9773,22 @@ _gtk_tree_view_column_start_drag (GtkTreeView *tree_view, send_event->button.y_root = 0; gdk_event_set_device (send_event, device); - gtk_propagate_event (column->button, send_event); + gtk_propagate_event (button, send_event); gdk_event_free (send_event); /* Kids, don't try this at home */ - g_object_ref (column->button); - gtk_container_remove (GTK_CONTAINER (tree_view), column->button); - gtk_widget_set_parent_window (column->button, tree_view->priv->drag_window); - gtk_widget_set_parent (column->button, GTK_WIDGET (tree_view)); - g_object_unref (column->button); + g_object_ref (button); + gtk_container_remove (GTK_CONTAINER (tree_view), button); + gtk_widget_set_parent_window (button, tree_view->priv->drag_window); + gtk_widget_set_parent (button, GTK_WIDGET (tree_view)); + g_object_unref (button); - gtk_widget_get_allocation (column->button, &button_allocation); + gtk_widget_get_allocation (button, &button_allocation); tree_view->priv->drag_column_x = button_allocation.x; allocation = button_allocation; allocation.x = 0; - gtk_widget_size_allocate (column->button, &allocation); - gtk_widget_set_parent_window (column->button, tree_view->priv->drag_window); + gtk_widget_size_allocate (button, &allocation); + gtk_widget_set_parent_window (button, tree_view->priv->drag_window); tree_view->priv->drag_column = column; gdk_window_show (tree_view->priv->drag_window); @@ -9490,7 +9797,7 @@ _gtk_tree_view_column_start_drag (GtkTreeView *tree_view, while (gtk_events_pending ()) gtk_main_iteration (); - GTK_TREE_VIEW_SET_FLAG (tree_view, GTK_TREE_VIEW_IN_COLUMN_DRAG); + tree_view->priv->in_column_drag = TRUE; gdk_pointer_grab (tree_view->priv->drag_window, FALSE, GDK_POINTER_MOTION_MASK|GDK_BUTTON_RELEASE_MASK, @@ -9515,8 +9822,8 @@ gtk_tree_view_queue_draw_arrow (GtkTreeView *tree_view, rect.x = 0; rect.width = MAX (tree_view->priv->expander_size, MAX (tree_view->priv->width, allocation.width)); - rect.y = BACKGROUND_FIRST_PIXEL (tree_view, tree, node); - rect.height = ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node)); + rect.y = gtk_tree_view_get_row_y_offset (tree_view, tree, node); + rect.height = gtk_tree_view_get_row_height (tree_view, node); gdk_window_invalidate_rect (tree_view->priv->bin_window, &rect, TRUE); } @@ -9537,8 +9844,8 @@ _gtk_tree_view_queue_draw_node (GtkTreeView *tree_view, rect.x = 0; rect.width = MAX (tree_view->priv->width, allocation.width); - rect.y = BACKGROUND_FIRST_PIXEL (tree_view, tree, node); - rect.height = ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node)); + rect.y = gtk_tree_view_get_row_y_offset (tree_view, tree, node); + rect.height = gtk_tree_view_get_row_height (tree_view, node); if (clip_rect) { @@ -9554,6 +9861,75 @@ _gtk_tree_view_queue_draw_node (GtkTreeView *tree_view, } } +static inline gint +gtk_tree_view_get_effective_header_height (GtkTreeView *tree_view) +{ + if (tree_view->priv->headers_visible) + return tree_view->priv->header_height; + /* else */ + return 0; +} + +gint +_gtk_tree_view_get_header_height (GtkTreeView *tree_view) +{ + return tree_view->priv->header_height; +} + +void +_gtk_tree_view_get_row_separator_func (GtkTreeView *tree_view, + GtkTreeViewRowSeparatorFunc *func, + gpointer *data) +{ + *func = tree_view->priv->row_separator_func; + *data = tree_view->priv->row_separator_data; +} + +GtkTreePath * +_gtk_tree_view_get_anchor_path (GtkTreeView *tree_view) +{ + if (tree_view->priv->anchor) + return gtk_tree_row_reference_get_path (tree_view->priv->anchor); + + return NULL; +} + +void +_gtk_tree_view_set_anchor_path (GtkTreeView *tree_view, + GtkTreePath *anchor_path) +{ + if (tree_view->priv->anchor) + { + gtk_tree_row_reference_free (tree_view->priv->anchor); + tree_view->priv->anchor = NULL; + } + + if (anchor_path && tree_view->priv->model) + tree_view->priv->anchor = + gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view), + tree_view->priv->model, anchor_path); +} + +GtkRBTree * +_gtk_tree_view_get_rbtree (GtkTreeView *tree_view) +{ + return tree_view->priv->tree; +} + +GdkWindow * +_gtk_tree_view_get_header_window (GtkTreeView *tree_view) +{ + return tree_view->priv->header_window; +} + +void +_gtk_tree_view_set_focus_column (GtkTreeView *tree_view, + GtkTreeViewColumn *column) +{ + tree_view->priv->focus_column = column; +} + + static void gtk_tree_view_queue_draw_path (GtkTreeView *tree_view, GtkTreePath *path, @@ -9601,9 +9977,11 @@ gtk_tree_view_draw_arrow (GtkTreeView *tree_view, gtk_tree_view_get_arrow_xrange (tree_view, tree, &x_offset, &x2); area.x = x_offset; - area.y = CELL_FIRST_PIXEL (tree_view, tree, node, vertical_separator); + area.y = gtk_tree_view_get_cell_area_y_offset (tree_view, tree, node, + vertical_separator); area.width = expander_size + 2; - area.height = MAX (CELL_HEIGHT (node, vertical_separator), (expander_size - vertical_separator)); + area.height = gtk_tree_view_get_cell_area_height (tree_view, node, + vertical_separator); if (gtk_widget_get_state (widget) == GTK_STATE_INSENSITIVE) { @@ -9620,7 +9998,7 @@ gtk_tree_view_draw_arrow (GtkTreeView *tree_view, else { if (node == tree_view->priv->prelight_node && - GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_ARROW_PRELIT)) + tree_view->priv->arrow_prelit) state = GTK_STATE_PRELIGHT; else state = GTK_STATE_NORMAL; @@ -9698,7 +10076,7 @@ gtk_tree_view_focus_to_cursor (GtkTreeView *tree_view) if (cursor_path) { - GTK_TREE_VIEW_SET_FLAG (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS); + tree_view->priv->draw_keyfocus = TRUE; gtk_tree_view_queue_draw_path (tree_view, cursor_path, NULL); gtk_tree_path_free (cursor_path); @@ -9710,7 +10088,22 @@ gtk_tree_view_focus_to_cursor (GtkTreeView *tree_view) { if (gtk_tree_view_column_get_visible (GTK_TREE_VIEW_COLUMN (list->data))) { + GtkCellArea *cell_area; + tree_view->priv->focus_column = GTK_TREE_VIEW_COLUMN (list->data); + + /* This happens when the treeview initially grabs focus and there + * is no column in focus, here we explicitly focus into the first cell */ + cell_area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (tree_view->priv->focus_column)); + if (!gtk_cell_area_get_focus_cell (cell_area)) + { + gboolean rtl; + + rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL); + gtk_cell_area_focus (cell_area, + rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT); + } + break; } } @@ -9730,6 +10123,10 @@ gtk_tree_view_move_cursor_up_down (GtkTreeView *tree_view, GtkTreePath *cursor_path = NULL; gboolean grab_focus = TRUE; gboolean selectable; + GtkDirectionType direction; + GtkCellArea *cell_area = NULL; + GtkCellRenderer *last_focus_cell = NULL; + GtkTreeIter iter; if (! gtk_widget_has_focus (GTK_WIDGET (tree_view))) return; @@ -9747,6 +10144,30 @@ gtk_tree_view_move_cursor_up_down (GtkTreeView *tree_view, /* FIXME: we lost the cursor; should we get the first? */ return; + direction = count < 0 ? GTK_DIR_UP : GTK_DIR_DOWN; + + if (tree_view->priv->focus_column) + cell_area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (tree_view->priv->focus_column)); + + /* If focus stays in the area for this row, then just return for this round */ + if (cell_area && (count == -1 || count == 1) && + gtk_tree_model_get_iter (tree_view->priv->model, &iter, cursor_path)) + { + gtk_tree_view_column_cell_set_cell_data (tree_view->priv->focus_column, + tree_view->priv->model, + &iter, + GTK_RBNODE_FLAG_SET (cursor_node, GTK_RBNODE_IS_PARENT), + cursor_node->children?TRUE:FALSE); + + /* Save the last cell that had focus, if we hit the end of the view we'll give + * focus back to it. */ + last_focus_cell = gtk_cell_area_get_focus_cell (cell_area); + + /* If focus stays in the area, no need to change the cursor row */ + if (gtk_cell_area_focus (cell_area, direction)) + return; + } + selection_count = gtk_tree_selection_count_selected_rows (tree_view->priv->selection); selectable = _gtk_tree_selection_row_is_selectable (tree_view->priv->selection, cursor_node, @@ -9818,6 +10239,10 @@ gtk_tree_view_move_cursor_up_down (GtkTreeView *tree_view, cursor_path = _gtk_tree_view_find_path (tree_view, new_cursor_tree, new_cursor_node); gtk_tree_view_real_set_cursor (tree_view, cursor_path, TRUE, TRUE); gtk_tree_path_free (cursor_path); + + /* Give focus to the area in the new row */ + if (cell_area) + gtk_cell_area_focus (cell_area, direction); } else { @@ -9844,6 +10269,9 @@ gtk_tree_view_move_cursor_up_down (GtkTreeView *tree_view, { gtk_widget_error_bell (GTK_WIDGET (tree_view)); } + + if (cell_area) + gtk_cell_area_set_focus_cell (cell_area, last_focus_cell); } if (grab_focus) @@ -9905,11 +10333,12 @@ gtk_tree_view_move_cursor_page_up_down (GtkTreeView *tree_view, return; } - if (tree_view->priv->cursor_offset > BACKGROUND_HEIGHT (cursor_node)) + if (tree_view->priv->cursor_offset + > gtk_tree_view_get_row_height (tree_view, cursor_node)) { _gtk_rbtree_next_full (cursor_tree, cursor_node, &cursor_tree, &cursor_node); - tree_view->priv->cursor_offset -= BACKGROUND_HEIGHT (cursor_node); + tree_view->priv->cursor_offset -= gtk_tree_view_get_row_height (tree_view, cursor_node); } y -= tree_view->priv->cursor_offset; @@ -9970,6 +10399,10 @@ gtk_tree_view_move_cursor_left_right (GtkTreeView *tree_view, GList *list; gboolean found_column = FALSE; gboolean rtl; + GtkDirectionType direction; + GtkCellArea *cell_area; + GtkCellRenderer *last_focus_cell = NULL; + GtkCellArea *last_focus_area = NULL; rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL); @@ -9994,6 +10427,11 @@ gtk_tree_view_move_cursor_left_right (GtkTreeView *tree_view, list = rtl ? g_list_last (tree_view->priv->columns) : g_list_first (tree_view->priv->columns); if (tree_view->priv->focus_column) { + /* Save the cell/area we are moving focus from, if moving the cursor + * by one step hits the end we'll set focus back here */ + last_focus_area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (tree_view->priv->focus_column)); + last_focus_cell = gtk_cell_area_get_focus_cell (last_focus_area); + for (; list; list = (rtl ? list->prev : list->next)) { if (list->data == tree_view->priv->focus_column) @@ -10001,10 +10439,10 @@ gtk_tree_view_move_cursor_left_right (GtkTreeView *tree_view, } } + direction = count > 0 ? GTK_DIR_RIGHT : GTK_DIR_LEFT; + while (list) { - gboolean left, right; - column = list->data; if (gtk_tree_view_column_get_visible (column) == FALSE) goto loop_end; @@ -10015,23 +10453,14 @@ gtk_tree_view_move_cursor_left_right (GtkTreeView *tree_view, GTK_RBNODE_FLAG_SET (cursor_node, GTK_RBNODE_IS_PARENT), cursor_node->children?TRUE:FALSE); - if (rtl) - { - right = list->prev ? TRUE : FALSE; - left = list->next ? TRUE : FALSE; - } - else - { - left = list->prev ? TRUE : FALSE; - right = list->next ? TRUE : FALSE; - } - - if (_gtk_tree_view_column_cell_focus (column, count, left, right)) + cell_area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (column)); + if (gtk_cell_area_focus (cell_area, direction)) { tree_view->priv->focus_column = column; found_column = TRUE; break; } + loop_end: if (count == 1) list = rtl ? list->prev : list->next; @@ -10041,7 +10470,7 @@ gtk_tree_view_move_cursor_left_right (GtkTreeView *tree_view, if (found_column) { - if (!gtk_tree_view_has_special_cell (tree_view)) + if (!gtk_tree_view_has_can_focus_cell (tree_view)) _gtk_tree_view_queue_draw_node (tree_view, cursor_tree, cursor_node, @@ -10052,6 +10481,9 @@ gtk_tree_view_move_cursor_left_right (GtkTreeView *tree_view, else { gtk_widget_error_bell (GTK_WIDGET (tree_view)); + + if (last_focus_area) + gtk_cell_area_set_focus_cell (last_focus_area, last_focus_cell); } gtk_tree_view_clamp_column_visible (tree_view, @@ -10185,7 +10617,7 @@ gtk_tree_view_real_select_cursor_row (GtkTreeView *tree_view, if (!tree_view->priv->shift_pressed && start_editing && tree_view->priv->focus_column) { - if (gtk_tree_view_start_editing (tree_view, cursor_path)) + if (gtk_tree_view_start_editing (tree_view, cursor_path, FALSE)) { gtk_tree_path_free (cursor_path); return TRUE; @@ -10541,12 +10973,14 @@ gtk_tree_view_real_start_interactive_search (GtkTreeView *tree_view, for (list = tree_view->priv->columns; list; list = list->next) { GtkTreeViewColumn *column; + GtkWidget *button; column = list->data; if (!gtk_tree_view_column_get_visible (column)) continue; - if (gtk_widget_has_focus (column->button)) + button = gtk_tree_view_column_get_button (column); + if (gtk_widget_has_focus (button)) { found_focus = TRUE; break; @@ -10627,14 +11061,14 @@ gtk_tree_view_new_column_width (GtkTreeView *tree_view, */ rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL); column = g_list_nth (tree_view->priv->columns, i)->data; - gtk_widget_get_allocation (column->button, &allocation); + gtk_widget_get_allocation (gtk_tree_view_column_get_button (column), &allocation); width = rtl ? (allocation.x + allocation.width - *x) : (*x - allocation.x); /* Clamp down the value */ min_width = gtk_tree_view_column_get_min_width (column); if (min_width == -1) { - gtk_widget_get_preferred_size (column->button, &button_req, NULL); + gtk_widget_get_preferred_size (gtk_tree_view_column_get_button (column), &button_req, NULL); width = MAX (button_req.width, width); } else @@ -10735,7 +11169,7 @@ gtk_tree_view_adjustment_changed (GtkAdjustment *adjustment, gdk_window_move (tree_view->priv->bin_window, - tree_view->priv->hadjustment->value, - TREE_VIEW_HEADER_HEIGHT (tree_view)); + gtk_tree_view_get_effective_header_height (tree_view)); gdk_window_move (tree_view->priv->header_window, - tree_view->priv->hadjustment->value, 0); @@ -10746,25 +11180,29 @@ gtk_tree_view_adjustment_changed (GtkAdjustment *adjustment, tree_view->priv->event_last_x, tree_view->priv->event_last_y - dy); - if (tree_view->priv->edited_column && - GTK_IS_WIDGET (tree_view->priv->edited_column->editable_widget)) + if (tree_view->priv->edited_column) { GList *list; - GtkWidget *widget; GtkTreeViewChild *child = NULL; + GtkCellEditable *edit_widget; + GtkCellArea *area; - widget = GTK_WIDGET (tree_view->priv->edited_column->editable_widget); - adjust_allocation (widget, 0, dy); - - for (list = tree_view->priv->children; list; list = list->next) - { - child = (GtkTreeViewChild *)list->data; - if (child->widget == widget) - { - child->y += dy; - break; - } - } + area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (tree_view->priv->edited_column)); + edit_widget = gtk_cell_area_get_edit_widget (area); + if (GTK_IS_WIDGET (edit_widget)) + { + adjust_allocation (GTK_WIDGET (edit_widget), 0, dy); + + for (list = tree_view->priv->children; list; list = list->next) + { + child = (GtkTreeViewChild *)list->data; + if (child->widget == GTK_WIDGET (edit_widget)) + { + child->y += dy; + break; + } + } + } } } gdk_window_scroll (tree_view->priv->bin_window, 0, dy); @@ -10962,9 +11400,9 @@ gtk_tree_view_set_model (GtkTreeView *tree_view, flags = gtk_tree_model_get_flags (tree_view->priv->model); if ((flags & GTK_TREE_MODEL_LIST_ONLY) == GTK_TREE_MODEL_LIST_ONLY) - GTK_TREE_VIEW_SET_FLAG (tree_view, GTK_TREE_VIEW_IS_LIST); + tree_view->priv->is_list = TRUE; else - GTK_TREE_VIEW_UNSET_FLAG (tree_view, GTK_TREE_VIEW_IS_LIST); + tree_view->priv->is_list = FALSE; path = gtk_tree_path_new_first (); if (gtk_tree_model_get_iter (tree_view->priv->model, &iter, path)) @@ -11143,7 +11581,7 @@ gtk_tree_view_get_headers_visible (GtkTreeView *tree_view) { g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), FALSE); - return GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_HEADERS_VISIBLE); + return tree_view->priv->headers_visible; } /** @@ -11161,18 +11599,16 @@ gtk_tree_view_set_headers_visible (GtkTreeView *tree_view, GList *list; GtkTreeViewColumn *column; GtkAllocation allocation; + GtkWidget *button; g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); headers_visible = !! headers_visible; - if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_HEADERS_VISIBLE) == headers_visible) + if (tree_view->priv->headers_visible == headers_visible) return; - if (headers_visible) - GTK_TREE_VIEW_SET_FLAG (tree_view, GTK_TREE_VIEW_HEADERS_VISIBLE); - else - GTK_TREE_VIEW_UNSET_FLAG (tree_view, GTK_TREE_VIEW_HEADERS_VISIBLE); + tree_view->priv->headers_visible = headers_visible == TRUE; if (gtk_widget_get_realized (GTK_WIDGET (tree_view))) { @@ -11181,8 +11617,8 @@ gtk_tree_view_set_headers_visible (GtkTreeView *tree_view, { gtk_widget_get_allocation (GTK_WIDGET (tree_view), &allocation); gdk_window_move_resize (tree_view->priv->bin_window, - x, y + TREE_VIEW_HEADER_HEIGHT (tree_view), - tree_view->priv->width, allocation.height - + TREE_VIEW_HEADER_HEIGHT (tree_view)); + x, y + gtk_tree_view_get_effective_header_height (tree_view), + tree_view->priv->width, allocation.height - + gtk_tree_view_get_effective_header_height (tree_view)); if (gtk_widget_get_mapped (GTK_WIDGET (tree_view))) gtk_tree_view_map_buttons (tree_view); @@ -11194,15 +11630,16 @@ gtk_tree_view_set_headers_visible (GtkTreeView *tree_view, for (list = tree_view->priv->columns; list; list = list->next) { column = list->data; - gtk_widget_unmap (column->button); + button = gtk_tree_view_column_get_button (column); + gtk_widget_unmap (button); } gdk_window_hide (tree_view->priv->header_window); } } gtk_widget_get_allocation (GTK_WIDGET (tree_view), &allocation); - tree_view->priv->vadjustment->page_size = allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view); - tree_view->priv->vadjustment->page_increment = (allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view)) / 2; + tree_view->priv->vadjustment->page_size = allocation.height - gtk_tree_view_get_effective_header_height (tree_view); + tree_view->priv->vadjustment->page_increment = (allocation.height - gtk_tree_view_get_effective_header_height (tree_view)) / 2; tree_view->priv->vadjustment->lower = 0; tree_view->priv->vadjustment->upper = tree_view->priv->height; gtk_adjustment_changed (tree_view->priv->vadjustment); @@ -11359,7 +11796,7 @@ gtk_tree_view_append_column (GtkTreeView *tree_view, { g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), -1); g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (column), -1); - g_return_val_if_fail (column->tree_view == NULL, -1); + g_return_val_if_fail (gtk_tree_view_column_get_tree_view (column) == NULL, -1); return gtk_tree_view_insert_column (tree_view, column, -1); } @@ -11380,7 +11817,7 @@ gtk_tree_view_remove_column (GtkTreeView *tree_view, { g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), -1); g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (column), -1); - g_return_val_if_fail (column->tree_view == GTK_WIDGET (tree_view), -1); + g_return_val_if_fail (gtk_tree_view_column_get_tree_view (column) == GTK_WIDGET (tree_view), -1); if (tree_view->priv->focus_column == column) tree_view->priv->focus_column = NULL; @@ -11452,7 +11889,7 @@ gtk_tree_view_insert_column (GtkTreeView *tree_view, { g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), -1); g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (column), -1); - g_return_val_if_fail (column->tree_view == NULL, -1); + g_return_val_if_fail (gtk_tree_view_column_get_tree_view (column) == NULL, -1); if (tree_view->priv->fixed_height_mode) g_return_val_if_fail (gtk_tree_view_column_get_sizing (column) @@ -12481,7 +12918,7 @@ gtk_tree_view_real_collapse_row (GtkTreeView *tree_view, } g_signal_emit (tree_view, tree_view_signals[ROW_COLLAPSED], 0, &iter, path); - + if (gtk_widget_get_mapped (GTK_WIDGET (tree_view))) { /* now that we've collapsed all rows, we want to try to set the prelight @@ -12890,7 +13327,8 @@ gtk_tree_view_set_cursor_on_cell (GtkTreeView *tree_view, /* cancel the current editing, if it exists */ if (tree_view->priv->edited_column && - tree_view->priv->edited_column->editable_widget) + gtk_cell_area_get_edit_widget + (gtk_cell_layout_get_area (GTK_CELL_LAYOUT (tree_view->priv->edited_column)))) gtk_tree_view_stop_editing (tree_view, TRUE); gtk_tree_view_real_set_cursor (tree_view, path, TRUE, TRUE); @@ -12912,7 +13350,7 @@ gtk_tree_view_set_cursor_on_cell (GtkTreeView *tree_view, if (focus_cell) gtk_tree_view_column_focus_cell (focus_column, focus_cell); if (start_editing) - gtk_tree_view_start_editing (tree_view, path); + gtk_tree_view_start_editing (tree_view, path, TRUE); } } @@ -13071,6 +13509,42 @@ gtk_tree_view_get_path_at_pos (GtkTreeView *tree_view, } +static inline gint +gtk_tree_view_get_cell_area_height (GtkTreeView *tree_view, + GtkRBNode *node, + gint vertical_separator) +{ + int height; + + /* The "cell" areas are the cell_area passed in to gtk_cell_renderer_render(), + * i.e. just the cells, no spacing. + * + * The cell area height is at least expander_size - vertical_separator. + * For regular nodes, the height is then at least expander_size. We should + * be able to enforce the expander_size minimum here, because this + * function will not be called for irregular (e.g. separator) rows. + */ + height = gtk_tree_view_get_row_height (tree_view, node); + if (height < tree_view->priv->expander_size) + height = tree_view->priv->expander_size; + + return height - vertical_separator; +} + +static inline gint +gtk_tree_view_get_cell_area_y_offset (GtkTreeView *tree_view, + GtkRBTree *tree, + GtkRBNode *node, + gint vertical_separator) +{ + int offset; + + offset = gtk_tree_view_get_row_y_offset (tree_view, tree, node); + offset += vertical_separator / 2; + + return offset; +} + /** * gtk_tree_view_get_cell_area: * @tree_view: a #GtkTreeView @@ -13103,7 +13577,7 @@ gtk_tree_view_get_cell_area (GtkTreeView *tree_view, g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); g_return_if_fail (column == NULL || GTK_IS_TREE_VIEW_COLUMN (column)); g_return_if_fail (rect != NULL); - g_return_if_fail (!column || column->tree_view == (GtkWidget *) tree_view); + g_return_if_fail (!column || gtk_tree_view_column_get_tree_view (column) == (GtkWidget *) tree_view); g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (tree_view))); gtk_widget_style_get (GTK_WIDGET (tree_view), @@ -13118,7 +13592,7 @@ gtk_tree_view_get_cell_area (GtkTreeView *tree_view, if (column) { - gtk_widget_get_allocation (column->button, &allocation); + gtk_widget_get_allocation (gtk_tree_view_column_get_button (column), &allocation); rect->x = allocation.x + horizontal_separator/2; rect->width = allocation.width - horizontal_separator; } @@ -13131,8 +13605,21 @@ gtk_tree_view_get_cell_area (GtkTreeView *tree_view, if ((!ret && tree == NULL) || ret) return; - rect->y = CELL_FIRST_PIXEL (tree_view, tree, node, vertical_separator); - rect->height = MAX (CELL_HEIGHT (node, vertical_separator), tree_view->priv->expander_size - vertical_separator); + if (row_is_separator (tree_view, NULL, path)) + { + /* There isn't really a "cell area" for separator, so we + * return the y, height values for background area instead. + */ + rect->y = gtk_tree_view_get_row_y_offset (tree_view, tree, node); + rect->height = gtk_tree_view_get_row_height (tree_view, node); + } + else + { + rect->y = gtk_tree_view_get_cell_area_y_offset (tree_view, tree, node, + vertical_separator); + rect->height = gtk_tree_view_get_cell_area_height (tree_view, node, + vertical_separator); + } if (column && gtk_tree_view_is_expander_column (tree_view, column)) @@ -13146,7 +13633,7 @@ gtk_tree_view_get_cell_area (GtkTreeView *tree_view, rect->x += (depth - 1) * tree_view->priv->level_indentation; rect->width -= (depth - 1) * tree_view->priv->level_indentation; - if (TREE_VIEW_DRAW_EXPANDERS (tree_view)) + if (gtk_tree_view_draw_expanders (tree_view)) { if (!rtl) rect->x += depth * tree_view->priv->expander_size; @@ -13158,6 +13645,39 @@ gtk_tree_view_get_cell_area (GtkTreeView *tree_view, } } +static inline gint +gtk_tree_view_get_row_height (GtkTreeView *tree_view, + GtkRBNode *node) +{ + int height; + + /* The "background" areas of all rows/cells add up to cover the entire tree. + * The background includes all inter-row and inter-cell spacing. + * + * If the row pointed at by node does not have a height set, we default + * to expander_size, which is the minimum height for regular nodes. + * Non-regular nodes (e.g. separators) can have a height set smaller + * than expander_size and should not be overruled here. + */ + height = GTK_RBNODE_GET_HEIGHT (node); + if (height <= 0) + height = tree_view->priv->expander_size; + + return height; +} + +static inline gint +gtk_tree_view_get_row_y_offset (GtkTreeView *tree_view, + GtkRBTree *tree, + GtkRBNode *node) +{ + int offset; + + offset = _gtk_rbtree_node_find_offset (tree, node); + + return RBTREE_Y_TO_TREE_WINDOW_Y (tree_view, offset); +} + /** * gtk_tree_view_get_background_area: * @tree_view: a #GtkTreeView @@ -13202,9 +13722,8 @@ gtk_tree_view_get_background_area (GtkTreeView *tree_view, tree == NULL) return; - rect->y = BACKGROUND_FIRST_PIXEL (tree_view, tree, node); - - rect->height = ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node)); + rect->y = gtk_tree_view_get_row_y_offset (tree_view, tree, node); + rect->height = gtk_tree_view_get_row_height (tree_view, node); } if (column) @@ -13244,7 +13763,7 @@ gtk_tree_view_get_visible_rect (GtkTreeView *tree_view, visible_rect->x = tree_view->priv->hadjustment->value; visible_rect->y = tree_view->priv->vadjustment->value; visible_rect->width = allocation.width; - visible_rect->height = allocation.height - TREE_VIEW_HEADER_HEIGHT (tree_view); + visible_rect->height = allocation.height - gtk_tree_view_get_effective_header_height (tree_view); } } @@ -13337,7 +13856,7 @@ gtk_tree_view_convert_widget_to_bin_window_coords (GtkTreeView *tree_view, if (bx) *bx = wx + tree_view->priv->hadjustment->value; if (by) - *by = wy - TREE_VIEW_HEADER_HEIGHT (tree_view); + *by = wy - gtk_tree_view_get_effective_header_height (tree_view); } /** @@ -13365,7 +13884,7 @@ gtk_tree_view_convert_bin_window_to_widget_coords (GtkTreeView *tree_view, if (wx) *wx = bx - tree_view->priv->hadjustment->value; if (wy) - *wy = by + TREE_VIEW_HEADER_HEIGHT (tree_view); + *wy = by + gtk_tree_view_get_effective_header_height (tree_view); } /** @@ -13907,7 +14426,7 @@ gtk_tree_view_create_row_drag_icon (GtkTreeView *tree_view, cell_offset = x; background_area.y = y; - background_area.height = ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node)); + background_area.height = gtk_tree_view_get_row_height (tree_view, node); bin_window_width = gdk_window_get_width (tree_view->priv->bin_window); @@ -13955,7 +14474,7 @@ gtk_tree_view_create_row_drag_icon (GtkTreeView *tree_view, cell_area.x += (depth - 1) * tree_view->priv->level_indentation; cell_area.width -= (depth - 1) * tree_view->priv->level_indentation; - if (TREE_VIEW_DRAW_EXPANDERS(tree_view)) + if (gtk_tree_view_draw_expanders (tree_view)) { if (!rtl) cell_area.x += depth * tree_view->priv->expander_size; @@ -13979,7 +14498,7 @@ gtk_tree_view_create_row_drag_icon (GtkTreeView *tree_view, cr, &background_area, &cell_area, - 0); + 0, FALSE); } cell_offset += gtk_tree_view_column_get_width (column); } @@ -14855,25 +15374,23 @@ gtk_tree_view_search_init (GtkWidget *entry, tree_view->priv->selected_iter = 1; } -static void -gtk_tree_view_remove_widget (GtkCellEditable *cell_editable, - GtkTreeView *tree_view) +void +_gtk_tree_view_remove_editable (GtkTreeView *tree_view, + GtkTreeViewColumn *column, + GtkCellEditable *cell_editable) { if (tree_view->priv->edited_column == NULL) return; - _gtk_tree_view_column_stop_editing (tree_view->priv->edited_column); + g_return_if_fail (column == tree_view->priv->edited_column); + tree_view->priv->edited_column = NULL; if (gtk_widget_has_focus (GTK_WIDGET (cell_editable))) gtk_widget_grab_focus (GTK_WIDGET (tree_view)); - g_signal_handlers_disconnect_by_func (cell_editable, - gtk_tree_view_remove_widget, - tree_view); - gtk_container_remove (GTK_CONTAINER (tree_view), - GTK_WIDGET (cell_editable)); + GTK_WIDGET (cell_editable)); /* FIXME should only redraw a single node */ gtk_widget_queue_draw (GTK_WIDGET (tree_view)); @@ -14881,12 +15398,12 @@ gtk_tree_view_remove_widget (GtkCellEditable *cell_editable, static gboolean gtk_tree_view_start_editing (GtkTreeView *tree_view, - GtkTreePath *cursor_path) + GtkTreePath *cursor_path, + gboolean edit_only) { GtkTreeIter iter; - GdkRectangle background_area; GdkRectangle cell_area; - GtkCellEditable *editable_widget = NULL; + GtkTreeViewColumn *focus_column; gchar *path_string; guint flags = 0; /* can be 0, as the flags are primarily for rendering */ gint retval = FALSE; @@ -14894,6 +15411,7 @@ gtk_tree_view_start_editing (GtkTreeView *tree_view, GtkRBNode *cursor_node; g_assert (tree_view->priv->focus_column); + focus_column = tree_view->priv->focus_column; if (!gtk_widget_get_realized (GTK_WIDGET (tree_view))) return FALSE; @@ -14907,71 +15425,37 @@ gtk_tree_view_start_editing (GtkTreeView *tree_view, validate_row (tree_view, cursor_tree, cursor_node, &iter, cursor_path); - gtk_tree_view_column_cell_set_cell_data (tree_view->priv->focus_column, + gtk_tree_view_column_cell_set_cell_data (focus_column, tree_view->priv->model, &iter, GTK_RBNODE_FLAG_SET (cursor_node, GTK_RBNODE_IS_PARENT), cursor_node->children?TRUE:FALSE); - gtk_tree_view_get_background_area (tree_view, - cursor_path, - tree_view->priv->focus_column, - &background_area); gtk_tree_view_get_cell_area (tree_view, cursor_path, - tree_view->priv->focus_column, + focus_column, &cell_area); - if (_gtk_tree_view_column_cell_event (tree_view->priv->focus_column, - &editable_widget, - NULL, - path_string, - &background_area, - &cell_area, - flags)) - { - retval = TRUE; - if (editable_widget != NULL) - { - gint left, right; - GdkRectangle area; - GtkCellRenderer *cell; - - area = cell_area; - cell = _gtk_tree_view_column_get_edited_cell (tree_view->priv->focus_column); + if (gtk_cell_area_activate (gtk_cell_layout_get_area (GTK_CELL_LAYOUT (focus_column)), + _gtk_tree_view_column_get_context (focus_column), + GTK_WIDGET (tree_view), + &cell_area, + flags, edit_only)) + retval = TRUE; - _gtk_tree_view_column_get_neighbor_sizes (tree_view->priv->focus_column, cell, &left, &right); - - area.x += left; - area.width -= right + left; - - gtk_tree_view_real_start_editing (tree_view, - tree_view->priv->focus_column, - cursor_path, - editable_widget, - &area, - NULL, - flags); - } - - } - g_free (path_string); return retval; } -static void -gtk_tree_view_real_start_editing (GtkTreeView *tree_view, - GtkTreeViewColumn *column, - GtkTreePath *path, - GtkCellEditable *cell_editable, - GdkRectangle *cell_area, - GdkEvent *event, - guint flags) +void +_gtk_tree_view_add_editable (GtkTreeView *tree_view, + GtkTreeViewColumn *column, + GtkTreePath *path, + GtkCellEditable *cell_editable, + GdkRectangle *cell_area) { gint pre_val = tree_view->priv->vadjustment->value; GtkRequisition requisition; tree_view->priv->edited_column = column; - _gtk_tree_view_column_start_editing (column, GTK_CELL_EDITABLE (cell_editable)); gtk_tree_view_real_set_cursor (tree_view, path, FALSE, TRUE); cell_area->y += pre_val - (int)tree_view->priv->vadjustment->value; @@ -14979,7 +15463,7 @@ gtk_tree_view_real_start_editing (GtkTreeView *tree_view, gtk_widget_get_preferred_size (GTK_WIDGET (cell_editable), &requisition, NULL); - GTK_TREE_VIEW_SET_FLAG (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS); + tree_view->priv->draw_keyfocus = TRUE; if (requisition.height < cell_area->height) { @@ -14996,13 +15480,6 @@ gtk_tree_view_real_start_editing (GtkTreeView *tree_view, cell_area->x, cell_area->y, cell_area->width, cell_area->height); } - - gtk_cell_editable_start_editing (GTK_CELL_EDITABLE (cell_editable), - (GdkEvent *)event); - - gtk_widget_grab_focus (GTK_WIDGET (cell_editable)); - g_signal_connect (cell_editable, "remove-widget", - G_CALLBACK (gtk_tree_view_remove_widget), tree_view); } static void @@ -15010,7 +15487,6 @@ gtk_tree_view_stop_editing (GtkTreeView *tree_view, gboolean cancel_editing) { GtkTreeViewColumn *column; - GtkCellRenderer *cell; if (tree_view->priv->edited_column == NULL) return; @@ -15026,17 +15502,8 @@ gtk_tree_view_stop_editing (GtkTreeView *tree_view, */ column = tree_view->priv->edited_column; + gtk_cell_area_stop_editing (gtk_cell_layout_get_area (GTK_CELL_LAYOUT (column)), cancel_editing); tree_view->priv->edited_column = NULL; - - cell = _gtk_tree_view_column_get_edited_cell (column); - gtk_cell_renderer_stop_editing (cell, cancel_editing); - - if (!cancel_editing) - gtk_cell_editable_editing_done (column->editable_widget); - - tree_view->priv->edited_column = column; - - gtk_cell_editable_remove_widget (column->editable_widget); } @@ -15473,12 +15940,9 @@ gtk_tree_view_set_show_expanders (GtkTreeView *tree_view, g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); enabled = enabled != FALSE; - was_enabled = GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_SHOW_EXPANDERS); + was_enabled = tree_view->priv->show_expanders; - if (enabled) - GTK_TREE_VIEW_SET_FLAG (tree_view, GTK_TREE_VIEW_SHOW_EXPANDERS); - else - GTK_TREE_VIEW_UNSET_FLAG (tree_view, GTK_TREE_VIEW_SHOW_EXPANDERS); + tree_view->priv->show_expanders = enabled == TRUE; if (enabled != was_enabled) gtk_widget_queue_draw (GTK_WIDGET (tree_view)); @@ -15500,7 +15964,7 @@ gtk_tree_view_get_show_expanders (GtkTreeView *tree_view) { g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), FALSE); - return GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_SHOW_EXPANDERS); + return tree_view->priv->show_expanders; } /** diff --git a/gtk/gtktreeviewcolumn.c b/gtk/gtktreeviewcolumn.c index 19d17c8901..905a7bb275 100644 --- a/gtk/gtktreeviewcolumn.c +++ b/gtk/gtktreeviewcolumn.c @@ -32,6 +32,8 @@ #include "gtkhbox.h" #include "gtkmarshalers.h" #include "gtkarrow.h" +#include "gtkcellareacontext.h" +#include "gtkcellareabox.h" #include "gtkprivate.h" #include "gtkintl.h" @@ -54,50 +56,6 @@ */ -enum -{ - PROP_0, - PROP_VISIBLE, - PROP_RESIZABLE, - PROP_WIDTH, - PROP_SPACING, - PROP_SIZING, - PROP_FIXED_WIDTH, - PROP_MIN_WIDTH, - PROP_MAX_WIDTH, - PROP_TITLE, - PROP_EXPAND, - PROP_CLICKABLE, - PROP_WIDGET, - PROP_ALIGNMENT, - PROP_REORDERABLE, - PROP_SORT_INDICATOR, - PROP_SORT_ORDER, - PROP_SORT_COLUMN_ID -}; - -enum -{ - CLICKED, - LAST_SIGNAL -}; - -typedef struct _GtkTreeViewColumnCellInfo GtkTreeViewColumnCellInfo; -struct _GtkTreeViewColumnCellInfo -{ - GtkCellRenderer *cell; - GSList *attributes; - GtkTreeCellDataFunc func; - gpointer func_data; - GDestroyNotify destroy; - gint requested_width; - gint real_width; - guint expand : 1; - guint pack : 1; - guint has_focus : 1; - guint in_editing_mode : 1; -}; - /* Type methods */ static void gtk_tree_view_column_cell_layout_init (GtkCellLayoutIface *iface); @@ -111,30 +69,13 @@ static void gtk_tree_view_column_get_property (GObject GValue *value, GParamSpec *pspec); static void gtk_tree_view_column_finalize (GObject *object); +static void gtk_tree_view_column_dispose (GObject *object); +static GObject *gtk_tree_view_column_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties); /* GtkCellLayout implementation */ -static void gtk_tree_view_column_cell_layout_pack_start (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gboolean expand); -static void gtk_tree_view_column_cell_layout_pack_end (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gboolean expand); -static void gtk_tree_view_column_cell_layout_clear (GtkCellLayout *cell_layout); -static void gtk_tree_view_column_cell_layout_add_attribute (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - const gchar *attribute, - gint column); -static void gtk_tree_view_column_cell_layout_set_cell_data_func (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - GtkCellLayoutDataFunc func, - gpointer func_data, - GDestroyNotify destroy); -static void gtk_tree_view_column_cell_layout_clear_attributes (GtkCellLayout *cell_layout, - GtkCellRenderer *cell); -static void gtk_tree_view_column_cell_layout_reorder (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gint position); -static GList *gtk_tree_view_column_cell_layout_get_cells (GtkCellLayout *cell_layout); +static GtkCellArea *gtk_tree_view_column_cell_layout_get_area (GtkCellLayout *cell_layout); /* Button handling code */ static void gtk_tree_view_column_create_button (GtkTreeViewColumn *tree_column); @@ -154,6 +95,21 @@ static gboolean gtk_tree_view_column_mnemonic_activate (GtkWidget *widge static void gtk_tree_view_model_sort_column_changed (GtkTreeSortable *sortable, GtkTreeViewColumn *tree_column); +/* GtkCellArea/GtkCellAreaContext callbacks */ +static void gtk_tree_view_column_context_changed (GtkCellAreaContext *context, + GParamSpec *pspec, + GtkTreeViewColumn *tree_column); +static void gtk_tree_view_column_add_editable_callback (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *edit_widget, + GdkRectangle *cell_area, + const gchar *path_string, + gpointer user_data); +static void gtk_tree_view_column_remove_editable_callback (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *edit_widget, + gpointer user_data); + /* Internal functions */ static void gtk_tree_view_column_sort (GtkTreeViewColumn *tree_column, gpointer data); @@ -161,21 +117,92 @@ static void gtk_tree_view_column_setup_sort_column_id_callback (GtkTreeViewColum static void gtk_tree_view_column_set_attributesv (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell_renderer, va_list args); -static GtkTreeViewColumnCellInfo *gtk_tree_view_column_get_cell_info (GtkTreeViewColumn *tree_column, - GtkCellRenderer *cell_renderer); - -/* cell list manipulation */ -static GList *gtk_tree_view_column_cell_first (GtkTreeViewColumn *tree_column); -static GList *gtk_tree_view_column_cell_last (GtkTreeViewColumn *tree_column); -static GList *gtk_tree_view_column_cell_next (GtkTreeViewColumn *tree_column, - GList *current); -static GList *gtk_tree_view_column_cell_prev (GtkTreeViewColumn *tree_column, - GList *current); -static void gtk_tree_view_column_clear_attributes_by_info (GtkTreeViewColumn *tree_column, - GtkTreeViewColumnCellInfo *info); + /* GtkBuildable implementation */ static void gtk_tree_view_column_buildable_init (GtkBuildableIface *iface); + +struct _GtkTreeViewColumnPrivate +{ + GtkWidget *tree_view; + GtkWidget *button; + GtkWidget *child; + GtkWidget *arrow; + GtkWidget *alignment; + GdkWindow *window; + gfloat xalign; + gulong property_changed_signal; + + /* Sizing fields */ + /* see gtk+/doc/tree-column-sizing.txt for more information on them */ + GtkTreeViewColumnSizing column_type; + gint padding; + gint resized_width; + gint width; + gint fixed_width; + gint min_width; + gint max_width; + + /* dragging columns */ + gint drag_x; + gint drag_y; + + gchar *title; + + /* Sorting */ + gulong sort_clicked_signal; + gulong sort_column_changed_signal; + gint sort_column_id; + GtkSortType sort_order; + + /* Cell area */ + GtkCellArea *cell_area; + GtkCellAreaContext *cell_area_context; + gulong add_editable_signal; + gulong remove_editable_signal; + gulong context_changed_signal; + + /* Flags */ + guint visible : 1; + guint resizable : 1; + guint clickable : 1; + guint dirty : 1; + guint show_sort_indicator : 1; + guint maybe_reordered : 1; + guint reorderable : 1; + guint use_resized_width : 1; + guint expand : 1; +}; + +enum +{ + PROP_0, + PROP_VISIBLE, + PROP_RESIZABLE, + PROP_WIDTH, + PROP_SPACING, + PROP_SIZING, + PROP_FIXED_WIDTH, + PROP_MIN_WIDTH, + PROP_MAX_WIDTH, + PROP_TITLE, + PROP_EXPAND, + PROP_CLICKABLE, + PROP_WIDGET, + PROP_ALIGNMENT, + PROP_REORDERABLE, + PROP_SORT_INDICATOR, + PROP_SORT_ORDER, + PROP_SORT_COLUMN_ID, + PROP_CELL_AREA +}; + +enum +{ + CLICKED, + LAST_SIGNAL +}; + static guint tree_column_signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE_WITH_CODE (GtkTreeViewColumn, gtk_tree_view_column, G_TYPE_INITIALLY_UNOWNED, @@ -194,7 +221,9 @@ gtk_tree_view_column_class_init (GtkTreeViewColumnClass *class) class->clicked = NULL; + object_class->constructor = gtk_tree_view_column_constructor; object_class->finalize = gtk_tree_view_column_finalize; + object_class->dispose = gtk_tree_view_column_dispose; object_class->set_property = gtk_tree_view_column_set_property; object_class->get_property = gtk_tree_view_column_get_property; @@ -365,6 +394,34 @@ gtk_tree_view_column_class_init (GtkTreeViewColumnClass *class) G_MAXINT, -1, GTK_PARAM_READWRITE)); + + /** + * GtkTreeViewColumn:cell-area: + * + * The #GtkCellArea used to layout cell renderers for this column. + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_CELL_AREA, + g_param_spec_object ("cell-area", + P_("Cell Area"), + P_("The GtkCellArea used to layout cells"), + GTK_TYPE_CELL_AREA, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_type_class_add_private (class, sizeof (GtkTreeViewColumnPrivate)); +} + +static void +gtk_tree_view_column_custom_tag_end (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + gpointer *data) +{ + /* Just ignore the boolean return from here */ + _gtk_cell_layout_buildable_custom_tag_end (buildable, builder, child, tagname, data); } static void @@ -372,79 +429,138 @@ gtk_tree_view_column_buildable_init (GtkBuildableIface *iface) { iface->add_child = _gtk_cell_layout_buildable_add_child; iface->custom_tag_start = _gtk_cell_layout_buildable_custom_tag_start; - iface->custom_tag_end = _gtk_cell_layout_buildable_custom_tag_end; + iface->custom_tag_end = gtk_tree_view_column_custom_tag_end; } static void gtk_tree_view_column_cell_layout_init (GtkCellLayoutIface *iface) { - iface->pack_start = gtk_tree_view_column_cell_layout_pack_start; - iface->pack_end = gtk_tree_view_column_cell_layout_pack_end; - iface->clear = gtk_tree_view_column_cell_layout_clear; - iface->add_attribute = gtk_tree_view_column_cell_layout_add_attribute; - iface->set_cell_data_func = gtk_tree_view_column_cell_layout_set_cell_data_func; - iface->clear_attributes = gtk_tree_view_column_cell_layout_clear_attributes; - iface->reorder = gtk_tree_view_column_cell_layout_reorder; - iface->get_cells = gtk_tree_view_column_cell_layout_get_cells; + iface->get_area = gtk_tree_view_column_cell_layout_get_area; } static void gtk_tree_view_column_init (GtkTreeViewColumn *tree_column) { - tree_column->button = NULL; - tree_column->xalign = 0.0; - tree_column->width = 0; - tree_column->spacing = 0; - tree_column->requested_width = -1; - tree_column->min_width = -1; - tree_column->max_width = -1; - tree_column->resized_width = 0; - tree_column->column_type = GTK_TREE_VIEW_COLUMN_GROW_ONLY; - tree_column->visible = TRUE; - tree_column->resizable = FALSE; - tree_column->expand = FALSE; - tree_column->clickable = FALSE; - tree_column->dirty = TRUE; - tree_column->sort_order = GTK_SORT_ASCENDING; - tree_column->show_sort_indicator = FALSE; - tree_column->property_changed_signal = 0; - tree_column->sort_clicked_signal = 0; - tree_column->sort_column_changed_signal = 0; - tree_column->sort_column_id = -1; - tree_column->reorderable = FALSE; - tree_column->maybe_reordered = FALSE; - tree_column->fixed_width = 1; - tree_column->use_resized_width = FALSE; - tree_column->title = g_strdup (""); + GtkTreeViewColumnPrivate *priv; + + tree_column->priv = G_TYPE_INSTANCE_GET_PRIVATE (tree_column, + GTK_TYPE_TREE_VIEW_COLUMN, + GtkTreeViewColumnPrivate); + priv = tree_column->priv; + + priv->button = NULL; + priv->xalign = 0.0; + priv->width = 0; + priv->padding = -1; + priv->min_width = -1; + priv->max_width = -1; + priv->resized_width = 0; + priv->column_type = GTK_TREE_VIEW_COLUMN_GROW_ONLY; + priv->visible = TRUE; + priv->resizable = FALSE; + priv->expand = FALSE; + priv->clickable = FALSE; + priv->dirty = TRUE; + priv->sort_order = GTK_SORT_ASCENDING; + priv->show_sort_indicator = FALSE; + priv->property_changed_signal = 0; + priv->sort_clicked_signal = 0; + priv->sort_column_changed_signal = 0; + priv->sort_column_id = -1; + priv->reorderable = FALSE; + priv->maybe_reordered = FALSE; + priv->fixed_width = 1; + priv->use_resized_width = FALSE; + priv->title = g_strdup (""); } -static void -gtk_tree_view_column_finalize (GObject *object) +static GObject * +gtk_tree_view_column_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) { - GtkTreeViewColumn *tree_column = (GtkTreeViewColumn *) object; - GList *list; + GtkTreeViewColumn *tree_column; + GtkTreeViewColumnPrivate *priv; + GObject *object; + + object = G_OBJECT_CLASS (gtk_tree_view_column_parent_class)->constructor + (type, n_construct_properties, construct_properties); + + tree_column = (GtkTreeViewColumn *) object; + priv = tree_column->priv; - for (list = tree_column->cell_list; list; list = list->next) + if (!priv->cell_area) { - GtkTreeViewColumnCellInfo *info = (GtkTreeViewColumnCellInfo *) list->data; + priv->cell_area = gtk_cell_area_box_new (); + g_object_ref_sink (priv->cell_area); + } - if (info->destroy) - { - GDestroyNotify d = info->destroy; + gtk_cell_area_set_style_detail (priv->cell_area, "treeview"); - info->destroy = NULL; - d (info->func_data); - } - gtk_tree_view_column_clear_attributes_by_info (tree_column, info); - g_object_unref (info->cell); - g_free (info); + priv->add_editable_signal = + g_signal_connect (priv->cell_area, "add-editable", + G_CALLBACK (gtk_tree_view_column_add_editable_callback), + tree_column); + priv->remove_editable_signal = + g_signal_connect (priv->cell_area, "remove-editable", + G_CALLBACK (gtk_tree_view_column_remove_editable_callback), + tree_column); + + priv->cell_area_context = gtk_cell_area_create_context (priv->cell_area); + + priv->context_changed_signal = + g_signal_connect (priv->cell_area_context, "notify", + G_CALLBACK (gtk_tree_view_column_context_changed), tree_column); + + return object; +} + +static void +gtk_tree_view_column_dispose (GObject *object) +{ + GtkTreeViewColumn *tree_column = (GtkTreeViewColumn *) object; + GtkTreeViewColumnPrivate *priv = tree_column->priv; + + if (priv->cell_area_context) + { + g_signal_handler_disconnect (priv->cell_area_context, + priv->context_changed_signal); + + g_object_unref (priv->cell_area_context); + + priv->cell_area_context = NULL; + priv->context_changed_signal = 0; + } + + if (priv->cell_area) + { + g_signal_handler_disconnect (priv->cell_area, + priv->add_editable_signal); + g_signal_handler_disconnect (priv->cell_area, + priv->remove_editable_signal); + + g_object_unref (priv->cell_area); + priv->cell_area = NULL; + priv->add_editable_signal = 0; + priv->remove_editable_signal = 0; + } + + if (priv->child) + { + g_object_unref (priv->child); + priv->child = NULL; } - g_free (tree_column->title); - g_list_free (tree_column->cell_list); + G_OBJECT_CLASS (gtk_tree_view_column_parent_class)->dispose (object); +} + +static void +gtk_tree_view_column_finalize (GObject *object) +{ + GtkTreeViewColumn *tree_column = (GtkTreeViewColumn *) object; + GtkTreeViewColumnPrivate *priv = tree_column->priv; - if (tree_column->child) - g_object_unref (tree_column->child); + g_free (priv->title); G_OBJECT_CLASS (gtk_tree_view_column_parent_class)->finalize (object); } @@ -456,6 +572,7 @@ gtk_tree_view_column_set_property (GObject *object, GParamSpec *pspec) { GtkTreeViewColumn *tree_column; + GtkCellArea *area; tree_column = GTK_TREE_VIEW_COLUMN (object); @@ -540,6 +657,14 @@ gtk_tree_view_column_set_property (GObject *object, gtk_tree_view_column_set_sort_column_id (tree_column, g_value_get_int (value)); break; + + case PROP_CELL_AREA: + /* Construct-only, can only be assigned once */ + area = g_value_get_object (value); + + if (area) + tree_column->priv->cell_area = g_object_ref_sink (area); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -643,6 +768,10 @@ gtk_tree_view_column_get_property (GObject *object, g_value_set_int (value, gtk_tree_view_column_get_sort_column_id (tree_column)); break; + + case PROP_CELL_AREA: + g_value_set_object (value, tree_column->priv->cell_area); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -652,236 +781,58 @@ gtk_tree_view_column_get_property (GObject *object, /* Implementation of GtkCellLayout interface */ - -static void -gtk_tree_view_column_cell_layout_pack_start (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gboolean expand) -{ - GtkTreeViewColumn *column; - GtkTreeViewColumnCellInfo *cell_info; - - g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (cell_layout)); - column = GTK_TREE_VIEW_COLUMN (cell_layout); - g_return_if_fail (! gtk_tree_view_column_get_cell_info (column, cell)); - - g_object_ref_sink (cell); - - cell_info = g_new0 (GtkTreeViewColumnCellInfo, 1); - cell_info->cell = cell; - cell_info->expand = expand ? TRUE : FALSE; - cell_info->pack = GTK_PACK_START; - cell_info->has_focus = 0; - cell_info->attributes = NULL; - - column->cell_list = g_list_append (column->cell_list, cell_info); -} - -static void -gtk_tree_view_column_cell_layout_pack_end (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gboolean expand) -{ - GtkTreeViewColumn *column; - GtkTreeViewColumnCellInfo *cell_info; - - g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (cell_layout)); - column = GTK_TREE_VIEW_COLUMN (cell_layout); - g_return_if_fail (! gtk_tree_view_column_get_cell_info (column, cell)); - - g_object_ref_sink (cell); - - cell_info = g_new0 (GtkTreeViewColumnCellInfo, 1); - cell_info->cell = cell; - cell_info->expand = expand ? TRUE : FALSE; - cell_info->pack = GTK_PACK_END; - cell_info->has_focus = 0; - cell_info->attributes = NULL; - - column->cell_list = g_list_append (column->cell_list, cell_info); -} - -static void -gtk_tree_view_column_cell_layout_clear (GtkCellLayout *cell_layout) -{ - GtkTreeViewColumn *column; - - g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (cell_layout)); - column = GTK_TREE_VIEW_COLUMN (cell_layout); - - while (column->cell_list) - { - GtkTreeViewColumnCellInfo *info = (GtkTreeViewColumnCellInfo *)column->cell_list->data; - - gtk_tree_view_column_cell_layout_clear_attributes (cell_layout, info->cell); - g_object_unref (info->cell); - g_free (info); - column->cell_list = g_list_delete_link (column->cell_list, - column->cell_list); - } -} - -static void -gtk_tree_view_column_cell_layout_add_attribute (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - const gchar *attribute, - gint column) -{ - GtkTreeViewColumn *tree_column; - GtkTreeViewColumnCellInfo *info; - - g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (cell_layout)); - tree_column = GTK_TREE_VIEW_COLUMN (cell_layout); - - info = gtk_tree_view_column_get_cell_info (tree_column, cell); - g_return_if_fail (info != NULL); - - info->attributes = g_slist_prepend (info->attributes, GINT_TO_POINTER (column)); - info->attributes = g_slist_prepend (info->attributes, g_strdup (attribute)); - - if (tree_column->tree_view) - _gtk_tree_view_column_cell_set_dirty (tree_column, TRUE); -} - -static void -gtk_tree_view_column_cell_layout_set_cell_data_func (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - GtkCellLayoutDataFunc func, - gpointer func_data, - GDestroyNotify destroy) +static GtkCellArea * +gtk_tree_view_column_cell_layout_get_area (GtkCellLayout *cell_layout) { - GtkTreeViewColumn *column; - GtkTreeViewColumnCellInfo *info; + GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN (cell_layout); + GtkTreeViewColumnPrivate *priv = column->priv; - g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (cell_layout)); - column = GTK_TREE_VIEW_COLUMN (cell_layout); - - info = gtk_tree_view_column_get_cell_info (column, cell); - g_return_if_fail (info != NULL); - - if (info->destroy) - { - GDestroyNotify d = info->destroy; - - info->destroy = NULL; - d (info->func_data); - } - - info->func = (GtkTreeCellDataFunc)func; - info->func_data = func_data; - info->destroy = destroy; - - if (column->tree_view) - _gtk_tree_view_column_cell_set_dirty (column, TRUE); + return priv->cell_area; } -static void -gtk_tree_view_column_cell_layout_clear_attributes (GtkCellLayout *cell_layout, - GtkCellRenderer *cell_renderer) -{ - GtkTreeViewColumn *column; - GtkTreeViewColumnCellInfo *info; - - g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (cell_layout)); - column = GTK_TREE_VIEW_COLUMN (cell_layout); - - info = gtk_tree_view_column_get_cell_info (column, cell_renderer); - if (info) - gtk_tree_view_column_clear_attributes_by_info (column, info); -} - -static void -gtk_tree_view_column_cell_layout_reorder (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gint position) -{ - GList *link; - GtkTreeViewColumn *column; - GtkTreeViewColumnCellInfo *info; - - g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (cell_layout)); - column = GTK_TREE_VIEW_COLUMN (cell_layout); - - info = gtk_tree_view_column_get_cell_info (column, cell); - - g_return_if_fail (info != NULL); - g_return_if_fail (position >= 0); - - link = g_list_find (column->cell_list, info); - - g_return_if_fail (link != NULL); - - column->cell_list = g_list_delete_link (column->cell_list, link); - column->cell_list = g_list_insert (column->cell_list, info, position); - - if (column->tree_view) - gtk_widget_queue_draw (column->tree_view); -} - -static void -gtk_tree_view_column_clear_attributes_by_info (GtkTreeViewColumn *tree_column, - GtkTreeViewColumnCellInfo *info) -{ - GSList *list; - - list = info->attributes; - - while (list && list->next) - { - g_free (list->data); - list = list->next->next; - } - g_slist_free (info->attributes); - info->attributes = NULL; - - if (tree_column->tree_view) - _gtk_tree_view_column_cell_set_dirty (tree_column, TRUE); -} - -/* Helper functions - */ - /* Button handling code */ static void gtk_tree_view_column_create_button (GtkTreeViewColumn *tree_column) { + GtkTreeViewColumnPrivate *priv = tree_column->priv; GtkTreeView *tree_view; GtkWidget *child; GtkWidget *hbox; - tree_view = (GtkTreeView *) tree_column->tree_view; + tree_view = (GtkTreeView *) priv->tree_view; g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); - g_return_if_fail (tree_column->button == NULL); + g_return_if_fail (priv->button == NULL); gtk_widget_push_composite_child (); - tree_column->button = gtk_button_new (); - gtk_widget_add_events (tree_column->button, GDK_POINTER_MOTION_MASK); + priv->button = gtk_button_new (); + gtk_widget_add_events (priv->button, GDK_POINTER_MOTION_MASK); gtk_widget_pop_composite_child (); /* make sure we own a reference to it as well. */ - if (tree_view->priv->header_window) - gtk_widget_set_parent_window (tree_column->button, tree_view->priv->header_window); - gtk_widget_set_parent (tree_column->button, GTK_WIDGET (tree_view)); + if (_gtk_tree_view_get_header_window (tree_view)) + gtk_widget_set_parent_window (priv->button, _gtk_tree_view_get_header_window (tree_view)); + + gtk_widget_set_parent (priv->button, GTK_WIDGET (tree_view)); - g_signal_connect (tree_column->button, "event", + g_signal_connect (priv->button, "event", G_CALLBACK (gtk_tree_view_column_button_event), tree_column); - g_signal_connect (tree_column->button, "clicked", + g_signal_connect (priv->button, "clicked", G_CALLBACK (gtk_tree_view_column_button_clicked), tree_column); - tree_column->alignment = gtk_alignment_new (tree_column->xalign, 0.5, 0.0, 0.0); + priv->alignment = gtk_alignment_new (priv->xalign, 0.5, 0.0, 0.0); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); - tree_column->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_IN); + priv->arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_IN); - if (tree_column->child) - child = tree_column->child; + if (priv->child) + child = priv->child; else { - child = gtk_label_new (tree_column->title); + child = gtk_label_new (priv->title); gtk_widget_show (child); } @@ -889,24 +840,25 @@ gtk_tree_view_column_create_button (GtkTreeViewColumn *tree_column) G_CALLBACK (gtk_tree_view_column_mnemonic_activate), tree_column); - if (tree_column->xalign <= 0.5) - gtk_box_pack_end (GTK_BOX (hbox), tree_column->arrow, FALSE, FALSE, 0); + if (priv->xalign <= 0.5) + gtk_box_pack_end (GTK_BOX (hbox), priv->arrow, FALSE, FALSE, 0); else - gtk_box_pack_start (GTK_BOX (hbox), tree_column->arrow, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), priv->arrow, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX (hbox), tree_column->alignment, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (hbox), priv->alignment, TRUE, TRUE, 0); - gtk_container_add (GTK_CONTAINER (tree_column->alignment), child); - gtk_container_add (GTK_CONTAINER (tree_column->button), hbox); + gtk_container_add (GTK_CONTAINER (priv->alignment), child); + gtk_container_add (GTK_CONTAINER (priv->button), hbox); gtk_widget_show (hbox); - gtk_widget_show (tree_column->alignment); + gtk_widget_show (priv->alignment); gtk_tree_view_column_update_button (tree_column); } static void gtk_tree_view_column_update_button (GtkTreeViewColumn *tree_column) { + GtkTreeViewColumnPrivate *priv = tree_column->priv; gint sort_column_id = -1; GtkWidget *hbox; GtkWidget *alignment; @@ -915,38 +867,38 @@ gtk_tree_view_column_update_button (GtkTreeViewColumn *tree_column) GtkArrowType arrow_type = GTK_ARROW_NONE; GtkTreeModel *model; - if (tree_column->tree_view) - model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_column->tree_view)); + if (priv->tree_view) + model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->tree_view)); else model = NULL; /* Create a button if necessary */ - if (tree_column->visible && - tree_column->button == NULL && - tree_column->tree_view && - gtk_widget_get_realized (tree_column->tree_view)) + if (priv->visible && + priv->button == NULL && + priv->tree_view && + gtk_widget_get_realized (priv->tree_view)) gtk_tree_view_column_create_button (tree_column); - if (! tree_column->button) + if (! priv->button) return; - hbox = gtk_bin_get_child (GTK_BIN (tree_column->button)); - alignment = tree_column->alignment; - arrow = tree_column->arrow; + hbox = gtk_bin_get_child (GTK_BIN (priv->button)); + alignment = priv->alignment; + arrow = priv->arrow; current_child = gtk_bin_get_child (GTK_BIN (alignment)); /* Set up the actual button */ - gtk_alignment_set (GTK_ALIGNMENT (alignment), tree_column->xalign, + gtk_alignment_set (GTK_ALIGNMENT (alignment), priv->xalign, 0.5, 0.0, 0.0); - if (tree_column->child) + if (priv->child) { - if (current_child != tree_column->child) + if (current_child != priv->child) { gtk_container_remove (GTK_CONTAINER (alignment), current_child); gtk_container_add (GTK_CONTAINER (alignment), - tree_column->child); + priv->child); } } else @@ -961,9 +913,9 @@ gtk_tree_view_column_update_button (GtkTreeViewColumn *tree_column) g_return_if_fail (GTK_IS_LABEL (current_child)); - if (tree_column->title) + if (priv->title) gtk_label_set_text_with_mnemonic (GTK_LABEL (current_child), - tree_column->title); + priv->title); else gtk_label_set_text_with_mnemonic (GTK_LABEL (current_child), ""); @@ -974,15 +926,15 @@ gtk_tree_view_column_update_button (GtkTreeViewColumn *tree_column) &sort_column_id, NULL); - if (tree_column->show_sort_indicator) + if (priv->show_sort_indicator) { gboolean alternative; - g_object_get (gtk_widget_get_settings (tree_column->tree_view), + g_object_get (gtk_widget_get_settings (priv->tree_view), "gtk-alternative-sort-arrows", &alternative, NULL); - switch (tree_column->sort_order) + switch (priv->sort_order) { case GTK_SORT_ASCENDING: arrow_type = alternative ? GTK_ARROW_UP : GTK_ARROW_DOWN; @@ -1009,7 +961,7 @@ gtk_tree_view_column_update_button (GtkTreeViewColumn *tree_column) g_object_ref (arrow); gtk_container_remove (GTK_CONTAINER (hbox), arrow); - if (tree_column->xalign <= 0.5) + if (priv->xalign <= 0.5) { gtk_box_pack_end (GTK_BOX (hbox), arrow, FALSE, FALSE, 0); } @@ -1021,52 +973,52 @@ gtk_tree_view_column_update_button (GtkTreeViewColumn *tree_column) } g_object_unref (arrow); - if (tree_column->show_sort_indicator - || (GTK_IS_TREE_SORTABLE (model) && tree_column->sort_column_id >= 0)) + if (priv->show_sort_indicator + || (GTK_IS_TREE_SORTABLE (model) && priv->sort_column_id >= 0)) gtk_widget_show (arrow); else gtk_widget_hide (arrow); /* It's always safe to hide the button. It isn't always safe to show it, as * if you show it before it's realized, it'll get the wrong window. */ - if (tree_column->button && - tree_column->tree_view != NULL && - gtk_widget_get_realized (tree_column->tree_view)) + if (priv->button && + priv->tree_view != NULL && + gtk_widget_get_realized (priv->tree_view)) { - if (tree_column->visible) + if (priv->visible) { - gtk_widget_show_now (tree_column->button); - if (tree_column->window) + gtk_widget_show_now (priv->button); + if (priv->window) { - if (tree_column->resizable) + if (priv->resizable) { - gdk_window_show (tree_column->window); - gdk_window_raise (tree_column->window); + gdk_window_show (priv->window); + gdk_window_raise (priv->window); } else { - gdk_window_hide (tree_column->window); + gdk_window_hide (priv->window); } } } else { - gtk_widget_hide (tree_column->button); - if (tree_column->window) - gdk_window_hide (tree_column->window); + gtk_widget_hide (priv->button); + if (priv->window) + gdk_window_hide (priv->window); } } - if (tree_column->reorderable || tree_column->clickable) + if (priv->reorderable || priv->clickable) { - gtk_widget_set_can_focus (tree_column->button, TRUE); + gtk_widget_set_can_focus (priv->button, TRUE); } else { - gtk_widget_set_can_focus (tree_column->button, FALSE); - if (gtk_widget_has_focus (tree_column->button)) + gtk_widget_set_can_focus (priv->button, FALSE); + if (gtk_widget_has_focus (priv->button)) { - GtkWidget *toplevel = gtk_widget_get_toplevel (tree_column->tree_view); + GtkWidget *toplevel = gtk_widget_get_toplevel (priv->tree_view); if (gtk_widget_is_toplevel (toplevel)) { gtk_window_set_focus (GTK_WINDOW (toplevel), NULL); @@ -1076,8 +1028,8 @@ gtk_tree_view_column_update_button (GtkTreeViewColumn *tree_column) /* Queue a resize on the assumption that we always want to catch all changes * and columns don't change all that often. */ - if (gtk_widget_get_realized (tree_column->tree_view)) - gtk_widget_queue_resize (tree_column->tree_view); + if (gtk_widget_get_realized (priv->tree_view)) + gtk_widget_queue_resize (priv->tree_view); } @@ -1089,40 +1041,42 @@ gtk_tree_view_column_button_event (GtkWidget *widget, GdkEvent *event, gpointer data) { - GtkTreeViewColumn *column = (GtkTreeViewColumn *) data; + GtkTreeViewColumn *column = (GtkTreeViewColumn *) data; + GtkTreeViewColumnPrivate *priv = column->priv; g_return_val_if_fail (event != NULL, FALSE); if (event->type == GDK_BUTTON_PRESS && - column->reorderable && + priv->reorderable && ((GdkEventButton *)event)->button == 1) { - column->maybe_reordered = TRUE; + priv->maybe_reordered = TRUE; gdk_window_get_pointer (gtk_button_get_event_window (GTK_BUTTON (widget)), - &column->drag_x, - &column->drag_y, + &priv->drag_x, + &priv->drag_y, NULL); gtk_widget_grab_focus (widget); } if (event->type == GDK_BUTTON_RELEASE || event->type == GDK_LEAVE_NOTIFY) - column->maybe_reordered = FALSE; + priv->maybe_reordered = FALSE; if (event->type == GDK_MOTION_NOTIFY && - column->maybe_reordered && + priv->maybe_reordered && (gtk_drag_check_threshold (widget, - column->drag_x, - column->drag_y, + priv->drag_x, + priv->drag_y, (gint) ((GdkEventMotion *)event)->x, (gint) ((GdkEventMotion *)event)->y))) { - column->maybe_reordered = FALSE; - _gtk_tree_view_column_start_drag (GTK_TREE_VIEW (column->tree_view), column, + priv->maybe_reordered = FALSE; + _gtk_tree_view_column_start_drag (GTK_TREE_VIEW (priv->tree_view), column, event->motion.device); return TRUE; } - if (column->clickable == FALSE) + + if (priv->clickable == FALSE) { switch (event->type) { @@ -1153,17 +1107,19 @@ gtk_tree_view_column_mnemonic_activate (GtkWidget *widget, gboolean group_cycling, gpointer data) { - GtkTreeViewColumn *column = (GtkTreeViewColumn *)data; + GtkTreeViewColumn *column = (GtkTreeViewColumn *)data; + GtkTreeViewColumnPrivate *priv = column->priv; g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (column), FALSE); - GTK_TREE_VIEW (column->tree_view)->priv->focus_column = column; - if (column->clickable) - gtk_button_clicked (GTK_BUTTON (column->button)); - else if (gtk_widget_get_can_focus (column->button)) - gtk_widget_grab_focus (column->button); + _gtk_tree_view_set_focus_column (GTK_TREE_VIEW (priv->tree_view), column); + + if (priv->clickable) + gtk_button_clicked (GTK_BUTTON (priv->button)); + else if (gtk_widget_get_can_focus (priv->button)) + gtk_widget_grab_focus (priv->button); else - gtk_widget_grab_focus (column->tree_view); + gtk_widget_grab_focus (priv->tree_view); return TRUE; } @@ -1172,6 +1128,7 @@ static void gtk_tree_view_model_sort_column_changed (GtkTreeSortable *sortable, GtkTreeViewColumn *column) { + GtkTreeViewColumnPrivate *priv = column->priv; gint sort_column_id; GtkSortType order; @@ -1179,7 +1136,7 @@ gtk_tree_view_model_sort_column_changed (GtkTreeSortable *sortable, &sort_column_id, &order)) { - if (sort_column_id == column->sort_column_id) + if (sort_column_id == priv->sort_column_id) { gtk_tree_view_column_set_sort_indicator (column, TRUE); gtk_tree_view_column_set_sort_order (column, order); @@ -1199,74 +1156,80 @@ static void gtk_tree_view_column_sort (GtkTreeViewColumn *tree_column, gpointer data) { + GtkTreeViewColumnPrivate *priv = tree_column->priv; + GtkTreeModel *model; + GtkTreeSortable *sortable; gint sort_column_id; GtkSortType order; gboolean has_sort_column; gboolean has_default_sort_func; - g_return_if_fail (tree_column->tree_view != NULL); + g_return_if_fail (priv->tree_view != NULL); + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->tree_view)); + sortable = GTK_TREE_SORTABLE (model); has_sort_column = - gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (GTK_TREE_VIEW (tree_column->tree_view)->priv->model), + gtk_tree_sortable_get_sort_column_id (sortable, &sort_column_id, &order); has_default_sort_func = - gtk_tree_sortable_has_default_sort_func (GTK_TREE_SORTABLE (GTK_TREE_VIEW (tree_column->tree_view)->priv->model)); + gtk_tree_sortable_has_default_sort_func (sortable); if (has_sort_column && - sort_column_id == tree_column->sort_column_id) + sort_column_id == priv->sort_column_id) { if (order == GTK_SORT_ASCENDING) - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (GTK_TREE_VIEW (tree_column->tree_view)->priv->model), - tree_column->sort_column_id, + gtk_tree_sortable_set_sort_column_id (sortable, + priv->sort_column_id, GTK_SORT_DESCENDING); else if (order == GTK_SORT_DESCENDING && has_default_sort_func) - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (GTK_TREE_VIEW (tree_column->tree_view)->priv->model), + gtk_tree_sortable_set_sort_column_id (sortable, GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING); else - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (GTK_TREE_VIEW (tree_column->tree_view)->priv->model), - tree_column->sort_column_id, + gtk_tree_sortable_set_sort_column_id (sortable, + priv->sort_column_id, GTK_SORT_ASCENDING); } else { - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (GTK_TREE_VIEW (tree_column->tree_view)->priv->model), - tree_column->sort_column_id, + gtk_tree_sortable_set_sort_column_id (sortable, + priv->sort_column_id, GTK_SORT_ASCENDING); } } - static void gtk_tree_view_column_setup_sort_column_id_callback (GtkTreeViewColumn *tree_column) { + GtkTreeViewColumnPrivate *priv = tree_column->priv; GtkTreeModel *model; - if (tree_column->tree_view == NULL) + if (priv->tree_view == NULL) return; - model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_column->tree_view)); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->tree_view)); if (model == NULL) return; if (GTK_IS_TREE_SORTABLE (model) && - tree_column->sort_column_id != -1) + priv->sort_column_id != -1) { gint real_sort_column_id; GtkSortType real_order; - if (tree_column->sort_column_changed_signal == 0) - tree_column->sort_column_changed_signal = + if (priv->sort_column_changed_signal == 0) + priv->sort_column_changed_signal = g_signal_connect (model, "sort-column-changed", G_CALLBACK (gtk_tree_view_model_sort_column_changed), tree_column); - + if (gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (model), &real_sort_column_id, &real_order) && - (real_sort_column_id == tree_column->sort_column_id)) + (real_sort_column_id == priv->sort_column_id)) { gtk_tree_view_column_set_sort_indicator (tree_column, TRUE); gtk_tree_view_column_set_sort_order (tree_column, real_order); @@ -1278,32 +1241,94 @@ gtk_tree_view_column_setup_sort_column_id_callback (GtkTreeViewColumn *tree_colu } } +static void +gtk_tree_view_column_context_changed (GtkCellAreaContext *context, + GParamSpec *pspec, + GtkTreeViewColumn *tree_column) +{ + /* Here we want the column re-requested if the underlying context was + * actually reset for any reason, this can happen if the underlying + * area/cell configuration changes (i.e. cell packing properties + * or cell spacing and the like) + * + * Note that we block this handler while requesting for sizes + * so there is no need to check for the new context size being -1, + * we also block the handler when explicitly resetting the context + * so as to avoid some infinite stack recursion. + */ + if (!strcmp (pspec->name, "minimum-width") || + !strcmp (pspec->name, "natural-width") || + !strcmp (pspec->name, "minimum-height") || + !strcmp (pspec->name, "natural-height")) + _gtk_tree_view_column_cell_set_dirty (tree_column, TRUE); +} + +static void +gtk_tree_view_column_add_editable_callback (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *edit_widget, + GdkRectangle *cell_area, + const gchar *path_string, + gpointer user_data) +{ + GtkTreeViewColumn *column = user_data; + GtkTreeViewColumnPrivate *priv = column->priv; + GtkTreePath *path; + + if (priv->tree_view) + { + path = gtk_tree_path_new_from_string (path_string); + + _gtk_tree_view_add_editable (GTK_TREE_VIEW (priv->tree_view), + column, + path, + edit_widget, + cell_area); + + gtk_tree_path_free (path); + } +} + +static void +gtk_tree_view_column_remove_editable_callback (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *edit_widget, + gpointer user_data) +{ + GtkTreeViewColumn *column = user_data; + GtkTreeViewColumnPrivate *priv = column->priv; + + if (priv->tree_view) + _gtk_tree_view_remove_editable (GTK_TREE_VIEW (priv->tree_view), + column, + edit_widget); +} /* Exported Private Functions. * These should only be called by gtktreeview.c or gtktreeviewcolumn.c */ - void _gtk_tree_view_column_realize_button (GtkTreeViewColumn *column) { + GtkTreeViewColumnPrivate *priv = column->priv; GtkAllocation allocation; GtkTreeView *tree_view; GdkWindowAttr attr; guint attributes_mask; gboolean rtl; - tree_view = (GtkTreeView *)column->tree_view; - rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL); + tree_view = (GtkTreeView *)priv->tree_view; + rtl = (gtk_widget_get_direction (priv->tree_view) == GTK_TEXT_DIR_RTL); g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); - g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (tree_view))); - g_return_if_fail (tree_view->priv->header_window != NULL); - g_return_if_fail (column->button != NULL); + g_return_if_fail (gtk_widget_get_realized (priv->tree_view)); + g_return_if_fail (priv->button != NULL); - gtk_widget_set_parent_window (column->button, tree_view->priv->header_window); + g_return_if_fail (_gtk_tree_view_get_header_window (tree_view) != NULL); + gtk_widget_set_parent_window (priv->button, _gtk_tree_view_get_header_window (tree_view)); - if (column->visible) - gtk_widget_show (column->button); + if (priv->visible) + gtk_widget_show (priv->button); attr.window_type = GDK_WINDOW_CHILD; attr.wclass = GDK_INPUT_ONLY; @@ -1315,17 +1340,17 @@ _gtk_tree_view_column_realize_button (GtkTreeViewColumn *column) GDK_POINTER_MOTION_HINT_MASK | GDK_KEY_PRESS_MASK); attributes_mask = GDK_WA_CURSOR | GDK_WA_X | GDK_WA_Y; - attr.cursor = gdk_cursor_new_for_display (gdk_window_get_display (tree_view->priv->header_window), - GDK_SB_H_DOUBLE_ARROW); + attr.cursor = gdk_cursor_new_for_display + (gdk_window_get_display (_gtk_tree_view_get_header_window (tree_view)), GDK_SB_H_DOUBLE_ARROW); attr.y = 0; attr.width = TREE_VIEW_DRAG_WIDTH; - attr.height = tree_view->priv->header_height; + attr.height = _gtk_tree_view_get_header_height (tree_view); - gtk_widget_get_allocation (column->button, &allocation); - attr.x = (allocation.x + (rtl ? 0 : allocation.width)) - TREE_VIEW_DRAG_WIDTH / 2; - column->window = gdk_window_new (tree_view->priv->header_window, - &attr, attributes_mask); - gdk_window_set_user_data (column->window, tree_view); + gtk_widget_get_allocation (priv->button, &allocation); + attr.x = (allocation.x + (rtl ? 0 : allocation.width)) - TREE_VIEW_DRAG_WIDTH / 2; + priv->window = gdk_window_new (_gtk_tree_view_get_header_window (tree_view), + &attr, attributes_mask); + gdk_window_set_user_data (priv->window, tree_view); gtk_tree_view_column_update_button (column); @@ -1335,23 +1360,29 @@ _gtk_tree_view_column_realize_button (GtkTreeViewColumn *column) void _gtk_tree_view_column_unrealize_button (GtkTreeViewColumn *column) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (column != NULL); - g_return_if_fail (column->window != NULL); - gdk_window_set_user_data (column->window, NULL); - gdk_window_destroy (column->window); - column->window = NULL; + priv = column->priv; + g_return_if_fail (priv->window != NULL); + + gdk_window_set_user_data (priv->window, NULL); + gdk_window_destroy (priv->window); + priv->window = NULL; } void _gtk_tree_view_column_unset_model (GtkTreeViewColumn *column, GtkTreeModel *old_model) { - if (column->sort_column_changed_signal) + GtkTreeViewColumnPrivate *priv = column->priv; + + if (priv->sort_column_changed_signal) { g_signal_handler_disconnect (old_model, - column->sort_column_changed_signal); - column->sort_column_changed_signal = 0; + priv->sort_column_changed_signal); + priv->sort_column_changed_signal = 0; } gtk_tree_view_column_set_sort_indicator (column, FALSE); } @@ -1360,16 +1391,18 @@ void _gtk_tree_view_column_set_tree_view (GtkTreeViewColumn *column, GtkTreeView *tree_view) { - g_assert (column->tree_view == NULL); + GtkTreeViewColumnPrivate *priv = column->priv; + + g_assert (priv->tree_view == NULL); - column->tree_view = GTK_WIDGET (tree_view); + priv->tree_view = GTK_WIDGET (tree_view); gtk_tree_view_column_create_button (column); - column->property_changed_signal = - g_signal_connect_swapped (tree_view, - "notify::model", - G_CALLBACK (gtk_tree_view_column_setup_sort_column_id_callback), - column); + priv->property_changed_signal = + g_signal_connect_swapped (tree_view, + "notify::model", + G_CALLBACK (gtk_tree_view_column_setup_sort_column_id_callback), + column); gtk_tree_view_column_setup_sort_column_id_callback (column); } @@ -1377,96 +1410,109 @@ _gtk_tree_view_column_set_tree_view (GtkTreeViewColumn *column, void _gtk_tree_view_column_unset_tree_view (GtkTreeViewColumn *column) { - if (column->tree_view && column->button) + GtkTreeViewColumnPrivate *priv = column->priv; + + if (priv->tree_view && priv->button) { - gtk_container_remove (GTK_CONTAINER (column->tree_view), column->button); + gtk_container_remove (GTK_CONTAINER (priv->tree_view), priv->button); } - if (column->property_changed_signal) + if (priv->property_changed_signal) { - g_signal_handler_disconnect (column->tree_view, column->property_changed_signal); - column->property_changed_signal = 0; + g_signal_handler_disconnect (priv->tree_view, priv->property_changed_signal); + priv->property_changed_signal = 0; } - if (column->sort_column_changed_signal) + if (priv->sort_column_changed_signal) { - g_signal_handler_disconnect (gtk_tree_view_get_model (GTK_TREE_VIEW (column->tree_view)), - column->sort_column_changed_signal); - column->sort_column_changed_signal = 0; + g_signal_handler_disconnect (gtk_tree_view_get_model (GTK_TREE_VIEW (priv->tree_view)), + priv->sort_column_changed_signal); + priv->sort_column_changed_signal = 0; } - column->tree_view = NULL; - column->button = NULL; + priv->tree_view = NULL; + priv->button = NULL; } gboolean _gtk_tree_view_column_has_editable_cell (GtkTreeViewColumn *column) { - GtkCellRenderer *cell; - GtkCellRendererMode mode; - GList *list; + GtkTreeViewColumnPrivate *priv = column->priv; + gboolean ret = FALSE; + GList *list, *cells; + + cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->cell_area)); - for (list = column->cell_list; list; list = list->next) + for (list = cells; list; list = list->next) { - cell = ((GtkTreeViewColumnCellInfo *)list->data)->cell; + GtkCellRenderer *cell = list->data; + GtkCellRendererMode mode; + g_object_get (cell, "mode", &mode, NULL); + if (mode == GTK_CELL_RENDERER_MODE_EDITABLE) - return TRUE; + { + ret = TRUE; + break; + } } - return FALSE; + g_list_free (cells); + + return ret; } /* gets cell being edited */ GtkCellRenderer * _gtk_tree_view_column_get_edited_cell (GtkTreeViewColumn *column) { - GList *list; - - for (list = column->cell_list; list; list = list->next) - if (((GtkTreeViewColumnCellInfo *)list->data)->in_editing_mode) - return ((GtkTreeViewColumnCellInfo *)list->data)->cell; - - return NULL; -} - -gint -_gtk_tree_view_column_count_special_cells (GtkTreeViewColumn *column) -{ - gint i = 0; - GList *list; - - for (list = column->cell_list; list; list = list->next) - { - GtkCellRendererMode mode; - GtkTreeViewColumnCellInfo *cellinfo = list->data; - - g_object_get (cellinfo->cell, "mode", &mode, NULL); - if ((mode == GTK_CELL_RENDERER_MODE_EDITABLE || - mode == GTK_CELL_RENDERER_MODE_ACTIVATABLE) && - gtk_cell_renderer_get_visible (cellinfo->cell)) - i++; - } + GtkTreeViewColumnPrivate *priv = column->priv; - return i; + return gtk_cell_area_get_edited_cell (priv->cell_area); } GtkCellRenderer * _gtk_tree_view_column_get_cell_at_pos (GtkTreeViewColumn *column, - gint x) -{ - GList *list; - gint current_x = 0; - - list = gtk_tree_view_column_cell_first (column); - for (; list; list = gtk_tree_view_column_cell_next (column, list)) - { - GtkTreeViewColumnCellInfo *cellinfo = list->data; - if (current_x <= x && x <= current_x + cellinfo->real_width) - return cellinfo->cell; - current_x += cellinfo->real_width; - } + GdkRectangle *cell_area, + GdkRectangle *background_area, + gint x, + gint y) +{ + GtkCellRenderer *match = NULL; + GtkTreeViewColumnPrivate *priv = column->priv; + + /* If (x, y) is outside of the background area, immediately return */ + if (x < background_area->x || + x > background_area->x + background_area->width || + y < background_area->y || + y > background_area->y + background_area->height) + return NULL; + + /* If (x, y) is inside the background area, clamp it to the cell_area + * so that a cell is still returned. The main reason for doing this + * (on the x axis) is for handling clicks in the indentation area + * (either at the left or right depending on RTL setting). Another + * reason is for handling clicks on the area where the focus rectangle + * is drawn (this is outside of cell area), this manifests itself + * mainly when a large setting is used for focus-line-width. + */ + if (x < cell_area->x) + x = cell_area->x; + else if (x > cell_area->x + cell_area->width) + x = cell_area->x + cell_area->width; + + if (y < cell_area->y) + y = cell_area->y; + else if (y > cell_area->y + cell_area->height) + y = cell_area->y + cell_area->height; - return NULL; + match = gtk_cell_area_get_cell_at_position (priv->cell_area, + priv->cell_area_context, + priv->tree_view, + cell_area, + x, y, + NULL); + + return match; } /* Public Functions */ @@ -1490,6 +1536,27 @@ gtk_tree_view_column_new (void) } /** + * gtk_tree_view_column_new_with_area: + * @area: the #GtkCellArea that the newly created column should use to layout cells. + * + * Creates a new #GtkTreeViewColumn using @area to render it's cells. + * + * Return value: A newly created #GtkTreeViewColumn. + * + * Since: 3.0 + */ +GtkTreeViewColumn * +gtk_tree_view_column_new_with_area (GtkCellArea *area) +{ + GtkTreeViewColumn *tree_column; + + tree_column = g_object_new (GTK_TYPE_TREE_VIEW_COLUMN, "cell-area", area, NULL); + + return tree_column; +} + + +/** * gtk_tree_view_column_new_with_attributes: * @title: The title to set the header to. * @cell: The #GtkCellRenderer. @@ -1538,18 +1605,6 @@ gtk_tree_view_column_new_with_attributes (const gchar *title, return retval; } -static GtkTreeViewColumnCellInfo * -gtk_tree_view_column_get_cell_info (GtkTreeViewColumn *tree_column, - GtkCellRenderer *cell_renderer) -{ - GList *list; - for (list = tree_column->cell_list; list; list = list->next) - if (((GtkTreeViewColumnCellInfo *)list->data)->cell == cell_renderer) - return (GtkTreeViewColumnCellInfo *) list->data; - return NULL; -} - - /** * gtk_tree_view_column_pack_start: * @tree_column: A #GtkTreeViewColumn. @@ -1598,24 +1653,6 @@ gtk_tree_view_column_clear (GtkTreeViewColumn *tree_column) gtk_cell_layout_clear (GTK_CELL_LAYOUT (tree_column)); } -static GList * -gtk_tree_view_column_cell_layout_get_cells (GtkCellLayout *layout) -{ - GtkTreeViewColumn *tree_column = GTK_TREE_VIEW_COLUMN (layout); - GList *retval = NULL, *list; - - g_return_val_if_fail (tree_column != NULL, NULL); - - for (list = tree_column->cell_list; list; list = list->next) - { - GtkTreeViewColumnCellInfo *info = (GtkTreeViewColumnCellInfo *)list->data; - - retval = g_list_append (retval, info->cell); - } - - return retval; -} - /** * gtk_tree_view_column_add_attribute: * @tree_column: A #GtkTreeViewColumn. @@ -1645,17 +1682,20 @@ gtk_tree_view_column_set_attributesv (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell_renderer, va_list args) { + GtkTreeViewColumnPrivate *priv = tree_column->priv; gchar *attribute; gint column; attribute = va_arg (args, gchar *); - gtk_tree_view_column_clear_attributes (tree_column, cell_renderer); + gtk_cell_layout_clear_attributes (GTK_CELL_LAYOUT (priv->cell_area), + cell_renderer); while (attribute != NULL) { column = va_arg (args, gint); - gtk_tree_view_column_add_attribute (tree_column, cell_renderer, attribute, column); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->cell_area), + cell_renderer, attribute, column); attribute = va_arg (args, gchar *); } } @@ -1680,7 +1720,6 @@ gtk_tree_view_column_set_attributes (GtkTreeViewColumn *tree_column, g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); g_return_if_fail (GTK_IS_CELL_RENDERER (cell_renderer)); - g_return_if_fail (gtk_tree_view_column_get_cell_info (tree_column, cell_renderer)); va_start (args, cell_renderer); gtk_tree_view_column_set_attributesv (tree_column, cell_renderer, args); @@ -1744,14 +1783,16 @@ void gtk_tree_view_column_set_spacing (GtkTreeViewColumn *tree_column, gint spacing) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); g_return_if_fail (spacing >= 0); - if (tree_column->spacing == spacing) - return; + priv = tree_column->priv; - tree_column->spacing = spacing; - if (tree_column->tree_view) + gtk_cell_area_box_set_spacing (GTK_CELL_AREA_BOX (priv->cell_area), + spacing); + if (priv->tree_view) _gtk_tree_view_column_cell_set_dirty (tree_column, TRUE); } @@ -1766,9 +1807,13 @@ gtk_tree_view_column_set_spacing (GtkTreeViewColumn *tree_column, gint gtk_tree_view_column_get_spacing (GtkTreeViewColumn *tree_column) { + GtkTreeViewColumnPrivate *priv; + g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), 0); - return tree_column->spacing; + priv = tree_column->priv; + + return gtk_cell_area_box_get_spacing (GTK_CELL_AREA_BOX (priv->cell_area)); } /* Options for manipulating the columns */ @@ -1784,16 +1829,19 @@ void gtk_tree_view_column_set_visible (GtkTreeViewColumn *tree_column, gboolean visible) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); + priv = tree_column->priv; visible = !! visible; - if (tree_column->visible == visible) + if (priv->visible == visible) return; - tree_column->visible = visible; + priv->visible = visible; - if (tree_column->visible) + if (priv->visible) _gtk_tree_view_column_cell_set_dirty (tree_column, TRUE); gtk_tree_view_column_update_button (tree_column); @@ -1814,7 +1862,7 @@ gtk_tree_view_column_get_visible (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), FALSE); - return tree_column->visible; + return tree_column->priv->visible; } /** @@ -1831,16 +1879,19 @@ void gtk_tree_view_column_set_resizable (GtkTreeViewColumn *tree_column, gboolean resizable) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); + priv = tree_column->priv; resizable = !! resizable; - if (tree_column->resizable == resizable) + if (priv->resizable == resizable) return; - tree_column->resizable = resizable; + priv->resizable = resizable; - if (resizable && tree_column->column_type == GTK_TREE_VIEW_COLUMN_AUTOSIZE) + if (resizable && priv->column_type == GTK_TREE_VIEW_COLUMN_AUTOSIZE) gtk_tree_view_column_set_sizing (tree_column, GTK_TREE_VIEW_COLUMN_GROW_ONLY); gtk_tree_view_column_update_button (tree_column); @@ -1861,7 +1912,7 @@ gtk_tree_view_column_get_resizable (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), FALSE); - return tree_column->resizable; + return tree_column->priv->resizable; } @@ -1876,25 +1927,19 @@ void gtk_tree_view_column_set_sizing (GtkTreeViewColumn *tree_column, GtkTreeViewColumnSizing type) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); - if (type == tree_column->column_type) + priv = tree_column->priv; + + if (type == priv->column_type) return; if (type == GTK_TREE_VIEW_COLUMN_AUTOSIZE) gtk_tree_view_column_set_resizable (tree_column, FALSE); -#if 0 - /* I was clearly on crack when I wrote this. I'm not sure what's supposed to - * be below so I'll leave it until I figure it out. - */ - if (tree_column->column_type == GTK_TREE_VIEW_COLUMN_AUTOSIZE && - tree_column->requested_width != -1) - { - gtk_tree_view_column_set_sizing (tree_column, tree_column->requested_width); - } -#endif - tree_column->column_type = type; + priv->column_type = type; gtk_tree_view_column_update_button (tree_column); @@ -1914,7 +1959,7 @@ gtk_tree_view_column_get_sizing (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), 0); - return tree_column->column_type; + return tree_column->priv->column_type; } /** @@ -1930,7 +1975,91 @@ gtk_tree_view_column_get_width (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), 0); - return tree_column->width; + return tree_column->priv->width; +} + +gint +_gtk_tree_view_column_request_width (GtkTreeViewColumn *tree_column) +{ + GtkTreeViewColumnPrivate *priv; + gint real_requested_width; + + priv = tree_column->priv; + + if (priv->use_resized_width) + { + real_requested_width = priv->resized_width; + } + else if (priv->column_type == GTK_TREE_VIEW_COLUMN_FIXED) + { + real_requested_width = priv->fixed_width; + } + else if (gtk_tree_view_get_headers_visible (GTK_TREE_VIEW (priv->tree_view))) + { + gint button_request; + gint requested_width; + + gtk_cell_area_context_get_preferred_width (priv->cell_area_context, &requested_width, NULL); + requested_width += priv->padding; + + gtk_widget_get_preferred_width (priv->button, &button_request, NULL); + real_requested_width = MAX (requested_width, button_request); + } + else + { + gint requested_width; + + gtk_cell_area_context_get_preferred_width (priv->cell_area_context, &requested_width, NULL); + requested_width += priv->padding; + + real_requested_width = requested_width; + if (real_requested_width < 0) + real_requested_width = 0; + } + + if (priv->min_width != -1) + real_requested_width = MAX (real_requested_width, priv->min_width); + + if (priv->max_width != -1) + real_requested_width = MIN (real_requested_width, priv->max_width); + + return real_requested_width; +} + +void +_gtk_tree_view_column_allocate (GtkTreeViewColumn *tree_column, + int x_offset, + int width) +{ + GtkTreeViewColumnPrivate *priv; + GtkAllocation allocation; + gboolean rtl; + + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); + + priv = tree_column->priv; + + priv->width = width; + + gtk_cell_area_context_allocate (priv->cell_area_context, priv->width - priv->padding, -1); + + allocation.x = x_offset; + allocation.y = 0; + allocation.width = width; + allocation.height = _gtk_tree_view_get_header_height (GTK_TREE_VIEW (priv->tree_view)); + + gtk_widget_size_allocate (priv->button, &allocation); + + if (priv->window) + { + rtl = (gtk_widget_get_direction (priv->tree_view) == GTK_TEXT_DIR_RTL); + gdk_window_move_resize (priv->window, + allocation.x + (rtl ? 0 : allocation.width) - TREE_VIEW_DRAG_WIDTH/2, + allocation.y, + TREE_VIEW_DRAG_WIDTH, allocation.height); + } + + g_object_notify (G_OBJECT (tree_column), "width"); } /** @@ -1948,17 +2077,21 @@ void gtk_tree_view_column_set_fixed_width (GtkTreeViewColumn *tree_column, gint fixed_width) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); g_return_if_fail (fixed_width > 0); - tree_column->fixed_width = fixed_width; - tree_column->use_resized_width = FALSE; + priv = tree_column->priv; + + priv->fixed_width = fixed_width; + priv->use_resized_width = FALSE; - if (tree_column->tree_view && - gtk_widget_get_realized (tree_column->tree_view) && - tree_column->column_type == GTK_TREE_VIEW_COLUMN_FIXED) + if (priv->tree_view && + gtk_widget_get_realized (priv->tree_view) && + priv->column_type == GTK_TREE_VIEW_COLUMN_FIXED) { - gtk_widget_queue_resize (tree_column->tree_view); + gtk_widget_queue_resize (priv->tree_view); } g_object_notify (G_OBJECT (tree_column), "fixed-width"); @@ -1978,7 +2111,7 @@ gtk_tree_view_column_get_fixed_width (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), 0); - return tree_column->fixed_width; + return tree_column->priv->fixed_width; } /** @@ -1993,32 +2126,36 @@ void gtk_tree_view_column_set_min_width (GtkTreeViewColumn *tree_column, gint min_width) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); g_return_if_fail (min_width >= -1); - if (min_width == tree_column->min_width) + priv = tree_column->priv; + + if (min_width == priv->min_width) return; - if (tree_column->visible && - tree_column->tree_view != NULL && - gtk_widget_get_realized (tree_column->tree_view)) + if (priv->visible && + priv->tree_view != NULL && + gtk_widget_get_realized (priv->tree_view)) { - if (min_width > tree_column->width) - gtk_widget_queue_resize (tree_column->tree_view); + if (min_width > priv->width) + gtk_widget_queue_resize (priv->tree_view); } - tree_column->min_width = min_width; + priv->min_width = min_width; g_object_freeze_notify (G_OBJECT (tree_column)); - if (tree_column->max_width != -1 && tree_column->max_width < min_width) + if (priv->max_width != -1 && priv->max_width < min_width) { - tree_column->max_width = min_width; + priv->max_width = min_width; g_object_notify (G_OBJECT (tree_column), "max-width"); } g_object_notify (G_OBJECT (tree_column), "min-width"); g_object_thaw_notify (G_OBJECT (tree_column)); - if (tree_column->column_type == GTK_TREE_VIEW_COLUMN_AUTOSIZE) - _gtk_tree_view_column_autosize (GTK_TREE_VIEW (tree_column->tree_view), + if (priv->column_type == GTK_TREE_VIEW_COLUMN_AUTOSIZE) + _gtk_tree_view_column_autosize (GTK_TREE_VIEW (priv->tree_view), tree_column); } @@ -2036,7 +2173,7 @@ gtk_tree_view_column_get_min_width (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), -1); - return tree_column->min_width; + return tree_column->priv->min_width; } /** @@ -2053,32 +2190,36 @@ void gtk_tree_view_column_set_max_width (GtkTreeViewColumn *tree_column, gint max_width) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); g_return_if_fail (max_width >= -1); - if (max_width == tree_column->max_width) + priv = tree_column->priv; + + if (max_width == priv->max_width) return; - if (tree_column->visible && - tree_column->tree_view != NULL && - gtk_widget_get_realized (tree_column->tree_view)) + if (priv->visible && + priv->tree_view != NULL && + gtk_widget_get_realized (priv->tree_view)) { - if (max_width != -1 && max_width < tree_column->width) - gtk_widget_queue_resize (tree_column->tree_view); + if (max_width != -1 && max_width < priv->width) + gtk_widget_queue_resize (priv->tree_view); } - tree_column->max_width = max_width; + priv->max_width = max_width; g_object_freeze_notify (G_OBJECT (tree_column)); - if (max_width != -1 && max_width < tree_column->min_width) + if (max_width != -1 && max_width < priv->min_width) { - tree_column->min_width = max_width; + priv->min_width = max_width; g_object_notify (G_OBJECT (tree_column), "min-width"); } g_object_notify (G_OBJECT (tree_column), "max-width"); g_object_thaw_notify (G_OBJECT (tree_column)); - if (tree_column->column_type == GTK_TREE_VIEW_COLUMN_AUTOSIZE) - _gtk_tree_view_column_autosize (GTK_TREE_VIEW (tree_column->tree_view), + if (priv->column_type == GTK_TREE_VIEW_COLUMN_AUTOSIZE) + _gtk_tree_view_column_autosize (GTK_TREE_VIEW (priv->tree_view), tree_column); } @@ -2096,7 +2237,7 @@ gtk_tree_view_column_get_max_width (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), -1); - return tree_column->max_width; + return tree_column->priv->max_width; } /** @@ -2109,12 +2250,16 @@ gtk_tree_view_column_get_max_width (GtkTreeViewColumn *tree_column) void gtk_tree_view_column_clicked (GtkTreeViewColumn *tree_column) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); - if (tree_column->visible && - tree_column->button && - tree_column->clickable) - gtk_button_clicked (GTK_BUTTON (tree_column->button)); + priv = tree_column->priv; + + if (priv->visible && + priv->button && + priv->clickable) + gtk_button_clicked (GTK_BUTTON (priv->button)); } /** @@ -2129,13 +2274,16 @@ void gtk_tree_view_column_set_title (GtkTreeViewColumn *tree_column, const gchar *title) { - gchar *new_title; - + GtkTreeViewColumnPrivate *priv; + gchar *new_title; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); + priv = tree_column->priv; + new_title = g_strdup (title); - g_free (tree_column->title); - tree_column->title = new_title; + g_free (priv->title); + priv->title = new_title; gtk_tree_view_column_update_button (tree_column); g_object_notify (G_OBJECT (tree_column), "title"); @@ -2155,7 +2303,7 @@ gtk_tree_view_column_get_title (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), NULL); - return tree_column->title; + return tree_column->priv->title; } /** @@ -2174,25 +2322,29 @@ void gtk_tree_view_column_set_expand (GtkTreeViewColumn *tree_column, gboolean expand) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); + priv = tree_column->priv; + expand = expand?TRUE:FALSE; - if (tree_column->expand == expand) + if (priv->expand == expand) return; - tree_column->expand = expand; + priv->expand = expand; - if (tree_column->visible && - tree_column->tree_view != NULL && - gtk_widget_get_realized (tree_column->tree_view)) + if (priv->visible && + priv->tree_view != NULL && + gtk_widget_get_realized (priv->tree_view)) { /* We want to continue using the original width of the * column that includes additional space added by the user * resizing the columns and possibly extra (expanded) space, which * are not included in the resized width. */ - tree_column->use_resized_width = FALSE; + priv->use_resized_width = FALSE; - gtk_widget_queue_resize (tree_column->tree_view); + gtk_widget_queue_resize (priv->tree_view); } g_object_notify (G_OBJECT (tree_column), "expand"); @@ -2213,7 +2365,7 @@ gtk_tree_view_column_get_expand (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), FALSE); - return tree_column->expand; + return tree_column->priv->expand; } /** @@ -2228,13 +2380,17 @@ void gtk_tree_view_column_set_clickable (GtkTreeViewColumn *tree_column, gboolean clickable) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); + priv = tree_column->priv; + clickable = !! clickable; - if (tree_column->clickable == clickable) + if (priv->clickable == clickable) return; - tree_column->clickable = clickable; + priv->clickable = clickable; gtk_tree_view_column_update_button (tree_column); g_object_notify (G_OBJECT (tree_column), "clickable"); } @@ -2252,7 +2408,7 @@ gtk_tree_view_column_get_clickable (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), FALSE); - return tree_column->clickable; + return tree_column->priv->clickable; } /** @@ -2267,16 +2423,20 @@ void gtk_tree_view_column_set_widget (GtkTreeViewColumn *tree_column, GtkWidget *widget) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); g_return_if_fail (widget == NULL || GTK_IS_WIDGET (widget)); + priv = tree_column->priv; + if (widget) g_object_ref_sink (widget); - if (tree_column->child) - g_object_unref (tree_column->child); + if (priv->child) + g_object_unref (priv->child); - tree_column->child = widget; + priv->child = widget; gtk_tree_view_column_update_button (tree_column); g_object_notify (G_OBJECT (tree_column), "widget"); } @@ -2296,7 +2456,7 @@ gtk_tree_view_column_get_widget (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), NULL); - return tree_column->child; + return tree_column->priv->child; } /** @@ -2312,14 +2472,18 @@ void gtk_tree_view_column_set_alignment (GtkTreeViewColumn *tree_column, gfloat xalign) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); + priv = tree_column->priv; + xalign = CLAMP (xalign, 0.0, 1.0); - if (tree_column->xalign == xalign) + if (priv->xalign == xalign) return; - tree_column->xalign = xalign; + priv->xalign = xalign; gtk_tree_view_column_update_button (tree_column); g_object_notify (G_OBJECT (tree_column), "alignment"); } @@ -2338,7 +2502,7 @@ gtk_tree_view_column_get_alignment (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), 0.5); - return tree_column->xalign; + return tree_column->priv->xalign; } /** @@ -2353,15 +2517,19 @@ void gtk_tree_view_column_set_reorderable (GtkTreeViewColumn *tree_column, gboolean reorderable) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); + priv = tree_column->priv; + /* if (reorderable) gtk_tree_view_column_set_clickable (tree_column, TRUE);*/ - if (tree_column->reorderable == (reorderable?TRUE:FALSE)) + if (priv->reorderable == (reorderable?TRUE:FALSE)) return; - tree_column->reorderable = (reorderable?TRUE:FALSE); + priv->reorderable = (reorderable?TRUE:FALSE); gtk_tree_view_column_update_button (tree_column); g_object_notify (G_OBJECT (tree_column), "reorderable"); } @@ -2379,7 +2547,7 @@ gtk_tree_view_column_get_reorderable (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), FALSE); - return tree_column->reorderable; + return tree_column->priv->reorderable; } @@ -2395,29 +2563,33 @@ void gtk_tree_view_column_set_sort_column_id (GtkTreeViewColumn *tree_column, gint sort_column_id) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); g_return_if_fail (sort_column_id >= -1); - if (tree_column->sort_column_id == sort_column_id) + priv = tree_column->priv; + + if (priv->sort_column_id == sort_column_id) return; - tree_column->sort_column_id = sort_column_id; + priv->sort_column_id = sort_column_id; /* Handle unsetting the id */ if (sort_column_id == -1) { - GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_column->tree_view)); + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->tree_view)); - if (tree_column->sort_clicked_signal) + if (priv->sort_clicked_signal) { - g_signal_handler_disconnect (tree_column, tree_column->sort_clicked_signal); - tree_column->sort_clicked_signal = 0; + g_signal_handler_disconnect (tree_column, priv->sort_clicked_signal); + priv->sort_clicked_signal = 0; } - if (tree_column->sort_column_changed_signal) + if (priv->sort_column_changed_signal) { - g_signal_handler_disconnect (model, tree_column->sort_column_changed_signal); - tree_column->sort_column_changed_signal = 0; + g_signal_handler_disconnect (model, priv->sort_column_changed_signal); + priv->sort_column_changed_signal = 0; } gtk_tree_view_column_set_sort_order (tree_column, GTK_SORT_ASCENDING); @@ -2429,11 +2601,11 @@ gtk_tree_view_column_set_sort_column_id (GtkTreeViewColumn *tree_column, gtk_tree_view_column_set_clickable (tree_column, TRUE); - if (! tree_column->sort_clicked_signal) - tree_column->sort_clicked_signal = g_signal_connect (tree_column, - "clicked", - G_CALLBACK (gtk_tree_view_column_sort), - NULL); + if (! priv->sort_clicked_signal) + priv->sort_clicked_signal = g_signal_connect (tree_column, + "clicked", + G_CALLBACK (gtk_tree_view_column_sort), + NULL); gtk_tree_view_column_setup_sort_column_id_callback (tree_column); g_object_notify (G_OBJECT (tree_column), "sort-column-id"); @@ -2455,7 +2627,7 @@ gtk_tree_view_column_get_sort_column_id (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), 0); - return tree_column->sort_column_id; + return tree_column->priv->sort_column_id; } /** @@ -2477,10 +2649,10 @@ gtk_tree_view_column_set_sort_indicator (GtkTreeViewColumn *tree_column, setting = setting != FALSE; - if (setting == tree_column->show_sort_indicator) + if (setting == tree_column->priv->show_sort_indicator) return; - tree_column->show_sort_indicator = setting; + tree_column->priv->show_sort_indicator = setting; gtk_tree_view_column_update_button (tree_column); g_object_notify (G_OBJECT (tree_column), "sort-indicator"); } @@ -2498,7 +2670,7 @@ gtk_tree_view_column_get_sort_indicator (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), FALSE); - return tree_column->show_sort_indicator; + return tree_column->priv->show_sort_indicator; } /** @@ -2524,10 +2696,10 @@ gtk_tree_view_column_set_sort_order (GtkTreeViewColumn *tree_column, { g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); - if (order == tree_column->sort_order) + if (order == tree_column->priv->sort_order) return; - tree_column->sort_order = order; + tree_column->priv->sort_order = order; gtk_tree_view_column_update_button (tree_column); g_object_notify (G_OBJECT (tree_column), "sort-order"); } @@ -2545,7 +2717,7 @@ gtk_tree_view_column_get_sort_order (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), 0); - return tree_column->sort_order; + return tree_column->priv->sort_order; } /** @@ -2568,48 +2740,13 @@ gtk_tree_view_column_cell_set_cell_data (GtkTreeViewColumn *tree_column, gboolean is_expander, gboolean is_expanded) { - GSList *list; - GValue value = { 0, }; - GList *cell_list; - gboolean cell_is_expander, cell_is_expanded; - g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); if (tree_model == NULL) return; - for (cell_list = tree_column->cell_list; cell_list; cell_list = cell_list->next) - { - GtkTreeViewColumnCellInfo *info = (GtkTreeViewColumnCellInfo *) cell_list->data; - GObject *cell = (GObject *) info->cell; - - list = info->attributes; - - g_object_freeze_notify (cell); - - g_object_get (cell, "is-expander", &cell_is_expander, NULL); - if (cell_is_expander != is_expander) - g_object_set (cell, "is-expander", is_expander, NULL); - - g_object_get (cell, "is-expanded", &cell_is_expanded, NULL); - if (cell_is_expanded != is_expanded) - g_object_set (cell, "is-expanded", is_expanded, NULL); - - while (list && list->next) - { - gtk_tree_model_get_value (tree_model, iter, - GPOINTER_TO_INT (list->next->data), - &value); - g_object_set_property (cell, (gchar *) list->data, &value); - g_value_unset (&value); - list = list->next->next; - } - - if (info->func) - (* info->func) (tree_column, info->cell, tree_model, iter, info->func_data); - g_object_thaw_notify (G_OBJECT (info->cell)); - } - + gtk_cell_area_apply_attributes (tree_column->priv->cell_area, tree_model, iter, + is_expander, is_expanded); } /** @@ -2632,528 +2769,39 @@ gtk_tree_view_column_cell_get_size (GtkTreeViewColumn *tree_column, gint *width, gint *height) { - GtkRequisition min_size; - GList *list; - gboolean first_cell = TRUE; - gint focus_line_width; + GtkTreeViewColumnPrivate *priv; + gint min_width = 0, min_height = 0; g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); - if (height) - * height = 0; - if (width) - * width = 0; + priv = tree_column->priv; - gtk_widget_style_get (tree_column->tree_view, "focus-line-width", &focus_line_width, NULL); - - for (list = tree_column->cell_list; list; list = list->next) - { - GtkTreeViewColumnCellInfo *info = (GtkTreeViewColumnCellInfo *) list->data; - gboolean visible; - g_object_get (info->cell, "visible", &visible, NULL); - - if (visible == FALSE) - continue; - - if (first_cell == FALSE && width) - *width += tree_column->spacing; - - gtk_cell_renderer_get_preferred_size (info->cell, - GTK_WIDGET (tree_column->tree_view), - &min_size, NULL); - - if (height) - * height = MAX (*height, min_size.height + focus_line_width * 2); - info->requested_width = MAX (info->requested_width, min_size.width + focus_line_width * 2); - if (width) - * width += info->requested_width; - first_cell = FALSE; - } -} + g_signal_handler_block (priv->cell_area_context, + priv->context_changed_signal); -/* rendering, event handling and rendering focus are somewhat complicated, and - * quite a bit of code. Rather than duplicate them, we put them together to - * keep the code in one place. - * - * To better understand what's going on, check out - * docs/tree-column-sizing.png - */ -enum { - CELL_ACTION_RENDER, - CELL_ACTION_FOCUS, - CELL_ACTION_EVENT -}; + gtk_cell_area_get_preferred_width (priv->cell_area, + priv->cell_area_context, + priv->tree_view, + NULL, NULL); -static gboolean -gtk_tree_view_column_cell_process_action (GtkTreeViewColumn *tree_column, - cairo_t *cr, - const GdkRectangle *background_area, - const GdkRectangle *cell_area, - guint flags, - gint action, - GdkRectangle *focus_rectangle, /* FOCUS */ - GtkCellEditable **editable_widget, /* EVENT */ - GdkEvent *event, /* EVENT */ - gchar *path_string) /* EVENT */ -{ - GList *list; - GdkRectangle real_cell_area; - GdkRectangle real_background_area; - gint depth = 0; - gint expand_cell_count = 0; - gint full_requested_width = 0; - gint extra_space; - gint min_x, min_y, max_x, max_y; - gint focus_line_width; - gint special_cells; - gint horizontal_separator; - gboolean cursor_row = FALSE; - gboolean first_cell = TRUE; - gboolean rtl; - /* If we have rtl text, we need to transform our areas */ - GdkRectangle rtl_cell_area; - GdkRectangle rtl_background_area; - - min_x = G_MAXINT; - min_y = G_MAXINT; - max_x = 0; - max_y = 0; - - rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_column->tree_view)) == GTK_TEXT_DIR_RTL); - special_cells = _gtk_tree_view_column_count_special_cells (tree_column); - - if (special_cells > 1 && action == CELL_ACTION_FOCUS) - { - GtkTreeViewColumnCellInfo *info = NULL; - gboolean found_has_focus = FALSE; - - /* one should have focus */ - for (list = tree_column->cell_list; list; list = list->next) - { - info = list->data; - if (info && info->has_focus) - { - found_has_focus = TRUE; - break; - } - } - - if (!found_has_focus) - { - /* give the first one focus */ - info = gtk_tree_view_column_cell_first (tree_column)->data; - info->has_focus = TRUE; - } - } - - cursor_row = flags & GTK_CELL_RENDERER_FOCUSED; - - gtk_widget_style_get (GTK_WIDGET (tree_column->tree_view), - "focus-line-width", &focus_line_width, - "horizontal-separator", &horizontal_separator, - NULL); - - real_cell_area = *cell_area; - real_background_area = *background_area; - - - real_cell_area.x += focus_line_width; - real_cell_area.y += focus_line_width; - real_cell_area.height -= 2 * focus_line_width; - - if (rtl) - depth = real_background_area.width - real_cell_area.width; - else - depth = real_cell_area.x - real_background_area.x; - - /* Find out how much extra space we have to allocate */ - for (list = tree_column->cell_list; list; list = list->next) - { - GtkTreeViewColumnCellInfo *info = (GtkTreeViewColumnCellInfo *)list->data; - - if (!gtk_cell_renderer_get_visible (info->cell)) - continue; - - if (info->expand == TRUE) - expand_cell_count ++; - full_requested_width += info->requested_width; - - if (!first_cell) - full_requested_width += tree_column->spacing; - - first_cell = FALSE; - } - - extra_space = cell_area->width - full_requested_width; - if (extra_space < 0) - extra_space = 0; - else if (extra_space > 0 && expand_cell_count > 0) - extra_space /= expand_cell_count; - - /* iterate list for GTK_PACK_START cells */ - for (list = tree_column->cell_list; list; list = list->next) - { - GtkTreeViewColumnCellInfo *info = (GtkTreeViewColumnCellInfo *) list->data; - - if (info->pack == GTK_PACK_END) - continue; - - if (!gtk_cell_renderer_get_visible (info->cell)) - continue; - - if ((info->has_focus || special_cells == 1) && cursor_row) - flags |= GTK_CELL_RENDERER_FOCUSED; - else - flags &= ~GTK_CELL_RENDERER_FOCUSED; - - info->real_width = info->requested_width + (info->expand?extra_space:0); - - /* We constrain ourselves to only the width available */ - if (real_cell_area.x - focus_line_width + info->real_width > cell_area->x + cell_area->width) - { - info->real_width = cell_area->x + cell_area->width - real_cell_area.x; - } - - if (real_cell_area.x > cell_area->x + cell_area->width) - break; - - real_cell_area.width = info->real_width; - real_cell_area.width -= 2 * focus_line_width; - - if (list->next) - { - real_background_area.width = info->real_width + depth; - } - else - { - /* fill the rest of background for the last cell */ - real_background_area.width = background_area->x + background_area->width - real_background_area.x; - } - - rtl_cell_area = real_cell_area; - rtl_background_area = real_background_area; - - if (rtl) - { - rtl_cell_area.x = cell_area->x + cell_area->width - (real_cell_area.x - cell_area->x) - real_cell_area.width; - rtl_background_area.x = background_area->x + background_area->width - (real_background_area.x - background_area->x) - real_background_area.width; - } - - /* RENDER */ - if (action == CELL_ACTION_RENDER) - { - gtk_cell_renderer_render (info->cell, - cr, - tree_column->tree_view, - &rtl_background_area, - &rtl_cell_area, - flags); - } - /* FOCUS */ - else if (action == CELL_ACTION_FOCUS) - { - gint x_offset, y_offset; - GtkRequisition min_size; - - gtk_cell_renderer_get_preferred_size (info->cell, - tree_column->tree_view, - &min_size, NULL); - - _gtk_cell_renderer_calc_offset (info->cell, &rtl_cell_area, - gtk_widget_get_direction (tree_column->tree_view), - min_size.width, min_size.height, - &x_offset, &y_offset); - - if (special_cells > 1) - { - if (info->has_focus) - { - min_x = rtl_cell_area.x + x_offset; - max_x = min_x + min_size.width; - min_y = rtl_cell_area.y + y_offset; - max_y = min_y + min_size.height; - } - } - else - { - if (min_x > (rtl_cell_area.x + x_offset)) - min_x = rtl_cell_area.x + x_offset; - if (max_x < rtl_cell_area.x + x_offset + min_size.width) - max_x = rtl_cell_area.x + x_offset + min_size.width; - if (min_y > (rtl_cell_area.y + y_offset)) - min_y = rtl_cell_area.y + y_offset; - if (max_y < rtl_cell_area.y + y_offset + min_size.height) - max_y = rtl_cell_area.y + y_offset + min_size.height; - } - } - /* EVENT */ - else if (action == CELL_ACTION_EVENT) - { - gboolean try_event = FALSE; - - if (event) - { - if (special_cells == 1) - { - /* only 1 activatable cell -> whole column can activate */ - if (cell_area->x <= ((GdkEventButton *)event)->x && - cell_area->x + cell_area->width > ((GdkEventButton *)event)->x) - try_event = TRUE; - } - else if (rtl_cell_area.x <= ((GdkEventButton *)event)->x && - rtl_cell_area.x + rtl_cell_area.width > ((GdkEventButton *)event)->x) - /* only activate cell if the user clicked on an individual - * cell - */ - try_event = TRUE; - } - else if (special_cells > 1 && info->has_focus) - try_event = TRUE; - else if (special_cells == 1) - try_event = TRUE; - - if (try_event) - { - gboolean visible, mode; - - g_object_get (info->cell, - "visible", &visible, - "mode", &mode, - NULL); - if (visible && mode == GTK_CELL_RENDERER_MODE_ACTIVATABLE) - { - if (gtk_cell_renderer_activate (info->cell, - event, - tree_column->tree_view, - path_string, - &rtl_background_area, - &rtl_cell_area, - flags)) - { - flags &= ~GTK_CELL_RENDERER_FOCUSED; - return TRUE; - } - } - else if (visible && mode == GTK_CELL_RENDERER_MODE_EDITABLE) - { - *editable_widget = - gtk_cell_renderer_start_editing (info->cell, - event, - tree_column->tree_view, - path_string, - &rtl_background_area, - &rtl_cell_area, - flags); - - if (*editable_widget != NULL) - { - g_return_val_if_fail (GTK_IS_CELL_EDITABLE (*editable_widget), FALSE); - info->in_editing_mode = TRUE; - gtk_tree_view_column_focus_cell (tree_column, info->cell); - - flags &= ~GTK_CELL_RENDERER_FOCUSED; - - return TRUE; - } - } - } - } - - flags &= ~GTK_CELL_RENDERER_FOCUSED; - - real_cell_area.x += (real_cell_area.width + 2 * focus_line_width + tree_column->spacing); - real_background_area.x += real_background_area.width + tree_column->spacing; - - /* Only needed for first cell */ - depth = 0; - } - - /* iterate list for PACK_END cells */ - for (list = g_list_last (tree_column->cell_list); list; list = list->prev) - { - GtkTreeViewColumnCellInfo *info = (GtkTreeViewColumnCellInfo *) list->data; - - if (info->pack == GTK_PACK_START) - continue; - - if (!gtk_cell_renderer_get_visible(info->cell)) - continue; - - if ((info->has_focus || special_cells == 1) && cursor_row) - flags |= GTK_CELL_RENDERER_FOCUSED; - else - flags &= ~GTK_CELL_RENDERER_FOCUSED; - - info->real_width = info->requested_width + (info->expand?extra_space:0); - - /* We constrain ourselves to only the width available */ - if (real_cell_area.x - focus_line_width + info->real_width > cell_area->x + cell_area->width) - { - info->real_width = cell_area->x + cell_area->width - real_cell_area.x; - } - - if (real_cell_area.x > cell_area->x + cell_area->width) - break; - - real_cell_area.width = info->real_width; - real_cell_area.width -= 2 * focus_line_width; - real_background_area.width = info->real_width + depth; + gtk_cell_area_context_get_preferred_width (priv->cell_area_context, &min_width, NULL); - rtl_cell_area = real_cell_area; - rtl_background_area = real_background_area; - if (rtl) - { - rtl_cell_area.x = cell_area->x + cell_area->width - (real_cell_area.x - cell_area->x) - real_cell_area.width; - rtl_background_area.x = background_area->x + background_area->width - (real_background_area.x - background_area->x) - real_background_area.width; - } - - /* RENDER */ - if (action == CELL_ACTION_RENDER) - { - gtk_cell_renderer_render (info->cell, - cr, - tree_column->tree_view, - &rtl_background_area, - &rtl_cell_area, - flags); - } - /* FOCUS */ - else if (action == CELL_ACTION_FOCUS) - { - gint x_offset, y_offset; - GtkRequisition min_size; - - gtk_cell_renderer_get_preferred_size (info->cell, - tree_column->tree_view, - &min_size, NULL); - - _gtk_cell_renderer_calc_offset (info->cell, &rtl_cell_area, - gtk_widget_get_direction (tree_column->tree_view), - min_size.width, min_size.height, - &x_offset, &y_offset); - - if (special_cells > 1) - { - if (info->has_focus) - { - min_x = rtl_cell_area.x + x_offset; - max_x = min_x + min_size.width; - min_y = rtl_cell_area.y + y_offset; - max_y = min_y + min_size.height; - } - } - else - { - if (min_x > (rtl_cell_area.x + x_offset)) - min_x = rtl_cell_area.x + x_offset; - if (max_x < rtl_cell_area.x + x_offset + min_size.width) - max_x = rtl_cell_area.x + x_offset + min_size.width; - if (min_y > (rtl_cell_area.y + y_offset)) - min_y = rtl_cell_area.y + y_offset; - if (max_y < rtl_cell_area.y + y_offset + min_size.height) - max_y = rtl_cell_area.y + y_offset + min_size.height; - } - } - /* EVENT */ - else if (action == CELL_ACTION_EVENT) - { - gboolean try_event = FALSE; + gtk_cell_area_get_preferred_height_for_width (priv->cell_area, + priv->cell_area_context, + priv->tree_view, + min_width, + &min_height, + NULL); - if (event) - { - if (special_cells == 1) - { - /* only 1 activatable cell -> whole column can activate */ - if (cell_area->x <= ((GdkEventButton *)event)->x && - cell_area->x + cell_area->width > ((GdkEventButton *)event)->x) - try_event = TRUE; - } - else if (rtl_cell_area.x <= ((GdkEventButton *)event)->x && - rtl_cell_area.x + rtl_cell_area.width > ((GdkEventButton *)event)->x) - /* only activate cell if the user clicked on an individual - * cell - */ - try_event = TRUE; - } - else if (special_cells > 1 && info->has_focus) - try_event = TRUE; - else if (special_cells == 1) - try_event = TRUE; + g_signal_handler_unblock (priv->cell_area_context, + priv->context_changed_signal); - if (try_event) - { - gboolean visible, mode; - - g_object_get (info->cell, - "visible", &visible, - "mode", &mode, - NULL); - if (visible && mode == GTK_CELL_RENDERER_MODE_ACTIVATABLE) - { - if (gtk_cell_renderer_activate (info->cell, - event, - tree_column->tree_view, - path_string, - &rtl_background_area, - &rtl_cell_area, - flags)) - { - flags &= ~GTK_CELL_RENDERER_FOCUSED; - return TRUE; - } - } - else if (visible && mode == GTK_CELL_RENDERER_MODE_EDITABLE) - { - *editable_widget = - gtk_cell_renderer_start_editing (info->cell, - event, - tree_column->tree_view, - path_string, - &rtl_background_area, - &rtl_cell_area, - flags); - - if (*editable_widget != NULL) - { - g_return_val_if_fail (GTK_IS_CELL_EDITABLE (*editable_widget), FALSE); - info->in_editing_mode = TRUE; - gtk_tree_view_column_focus_cell (tree_column, info->cell); - - flags &= ~GTK_CELL_RENDERER_FOCUSED; - return TRUE; - } - } - } - } - flags &= ~GTK_CELL_RENDERER_FOCUSED; - - real_cell_area.x += (real_cell_area.width + 2 * focus_line_width + tree_column->spacing); - real_background_area.x += (real_background_area.width + tree_column->spacing); - - /* Only needed for first cell */ - depth = 0; - } - - /* fill focus_rectangle when required */ - if (action == CELL_ACTION_FOCUS) - { - if (min_x >= max_x || min_y >= max_y) - { - *focus_rectangle = *cell_area; - /* don't change the focus_rectangle, just draw it nicely inside - * the cell area */ - } - else - { - focus_rectangle->x = min_x - focus_line_width; - focus_rectangle->y = min_y - focus_line_width; - focus_rectangle->width = (max_x - min_x) + 2 * focus_line_width; - focus_rectangle->height = (max_y - min_y) + 2 * focus_line_width; - } - } + if (height) + * height = min_height; + if (width) + * width = min_width; - return FALSE; } /** @@ -3172,371 +2820,46 @@ _gtk_tree_view_column_cell_render (GtkTreeViewColumn *tree_column, cairo_t *cr, const GdkRectangle *background_area, const GdkRectangle *cell_area, - guint flags) + guint flags, + gboolean draw_focus) { + GtkTreeViewColumnPrivate *priv; + g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); g_return_if_fail (cr != NULL); g_return_if_fail (background_area != NULL); g_return_if_fail (cell_area != NULL); + priv = tree_column->priv; + cairo_save (cr); - gtk_tree_view_column_cell_process_action (tree_column, - cr, - background_area, - cell_area, - flags, - CELL_ACTION_RENDER, - NULL, NULL, NULL, NULL); + gtk_cell_area_render (priv->cell_area, priv->cell_area_context, + priv->tree_view, cr, + background_area, cell_area, flags, + draw_focus); cairo_restore (cr); } gboolean _gtk_tree_view_column_cell_event (GtkTreeViewColumn *tree_column, - GtkCellEditable **editable_widget, GdkEvent *event, - gchar *path_string, - const GdkRectangle *background_area, const GdkRectangle *cell_area, guint flags) { - g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), FALSE); - - return gtk_tree_view_column_cell_process_action (tree_column, - NULL, - background_area, - cell_area, - flags, - CELL_ACTION_EVENT, - NULL, - editable_widget, - event, - path_string); -} - -void -_gtk_tree_view_column_get_focus_area (GtkTreeViewColumn *tree_column, - const GdkRectangle *background_area, - const GdkRectangle *cell_area, - GdkRectangle *focus_area) -{ - gtk_tree_view_column_cell_process_action (tree_column, - NULL, - background_area, - cell_area, - 0, - CELL_ACTION_FOCUS, - focus_area, - NULL, NULL, NULL); -} - - -/* cell list manipulation */ -static GList * -gtk_tree_view_column_cell_first (GtkTreeViewColumn *tree_column) -{ - GList *list = tree_column->cell_list; - - /* first GTK_PACK_START cell we find */ - for ( ; list; list = list->next) - { - GtkTreeViewColumnCellInfo *info = list->data; - if (info->pack == GTK_PACK_START) - return list; - } - - /* hmm, else the *last* GTK_PACK_END cell */ - list = g_list_last (tree_column->cell_list); - - for ( ; list; list = list->prev) - { - GtkTreeViewColumnCellInfo *info = list->data; - if (info->pack == GTK_PACK_END) - return list; - } - - return NULL; -} - -static GList * -gtk_tree_view_column_cell_last (GtkTreeViewColumn *tree_column) -{ - GList *list = tree_column->cell_list; - - /* *first* GTK_PACK_END cell we find */ - for ( ; list ; list = list->next) - { - GtkTreeViewColumnCellInfo *info = list->data; - if (info->pack == GTK_PACK_END) - return list; - } - - /* hmm, else the last GTK_PACK_START cell */ - list = g_list_last (tree_column->cell_list); + GtkTreeViewColumnPrivate *priv; - for ( ; list; list = list->prev) - { - GtkTreeViewColumnCellInfo *info = list->data; - if (info->pack == GTK_PACK_START) - return list; - } - - return NULL; -} - -static GList * -gtk_tree_view_column_cell_next (GtkTreeViewColumn *tree_column, - GList *current) -{ - GList *list; - GtkTreeViewColumnCellInfo *info = current->data; - - if (info->pack == GTK_PACK_START) - { - for (list = current->next; list; list = list->next) - { - GtkTreeViewColumnCellInfo *inf = list->data; - if (inf->pack == GTK_PACK_START) - return list; - } - - /* out of GTK_PACK_START cells, get *last* GTK_PACK_END one */ - list = g_list_last (tree_column->cell_list); - for (; list; list = list->prev) - { - GtkTreeViewColumnCellInfo *inf = list->data; - if (inf->pack == GTK_PACK_END) - return list; - } - } - - for (list = current->prev; list; list = list->prev) - { - GtkTreeViewColumnCellInfo *inf = list->data; - if (inf->pack == GTK_PACK_END) - return list; - } - - return NULL; -} - -static GList * -gtk_tree_view_column_cell_prev (GtkTreeViewColumn *tree_column, - GList *current) -{ - GList *list; - GtkTreeViewColumnCellInfo *info = current->data; - - if (info->pack == GTK_PACK_END) - { - for (list = current->next; list; list = list->next) - { - GtkTreeViewColumnCellInfo *inf = list->data; - if (inf->pack == GTK_PACK_END) - return list; - } - - /* out of GTK_PACK_END, get last GTK_PACK_START one */ - list = g_list_last (tree_column->cell_list); - for ( ; list; list = list->prev) - { - GtkTreeViewColumnCellInfo *inf = list->data; - if (inf->pack == GTK_PACK_START) - return list; - } - } - - for (list = current->prev; list; list = list->prev) - { - GtkTreeViewColumnCellInfo *inf = list->data; - if (inf->pack == GTK_PACK_START) - return list; - } - - return NULL; -} - -gboolean -_gtk_tree_view_column_cell_focus (GtkTreeViewColumn *tree_column, - gint direction, - gboolean left, - gboolean right) -{ - gint count; - gboolean rtl; - - count = _gtk_tree_view_column_count_special_cells (tree_column); - rtl = gtk_widget_get_direction (GTK_WIDGET (tree_column->tree_view)) == GTK_TEXT_DIR_RTL; - - /* if we are the current focus column and have multiple editable cells, - * try to select the next one, else move the focus to the next column - */ - if (GTK_TREE_VIEW (tree_column->tree_view)->priv->focus_column == tree_column) - { - if (count > 1) - { - GList *next, *prev; - GList *list = tree_column->cell_list; - GtkTreeViewColumnCellInfo *info = NULL; - - /* find current focussed cell */ - for ( ; list; list = list->next) - { - info = list->data; - if (info->has_focus) - break; - } - - /* not a focussed cell in the focus column? */ - if (!list || !info || !info->has_focus) - return FALSE; - - if (rtl) - { - prev = gtk_tree_view_column_cell_next (tree_column, list); - next = gtk_tree_view_column_cell_prev (tree_column, list); - } - else - { - next = gtk_tree_view_column_cell_next (tree_column, list); - prev = gtk_tree_view_column_cell_prev (tree_column, list); - } - - info->has_focus = FALSE; - if (direction > 0 && next) - { - info = next->data; - info->has_focus = TRUE; - return TRUE; - } - else if (direction > 0 && !next && !right) - { - /* keep focus on last cell */ - if (rtl) - info = gtk_tree_view_column_cell_first (tree_column)->data; - else - info = gtk_tree_view_column_cell_last (tree_column)->data; - - info->has_focus = TRUE; - return TRUE; - } - else if (direction < 0 && prev) - { - info = prev->data; - info->has_focus = TRUE; - return TRUE; - } - else if (direction < 0 && !prev && !left) - { - /* keep focus on first cell */ - if (rtl) - info = gtk_tree_view_column_cell_last (tree_column)->data; - else - info = gtk_tree_view_column_cell_first (tree_column)->data; - - info->has_focus = TRUE; - return TRUE; - } - } - return FALSE; - } - - /* we get focus, if we have multiple editable cells, give the correct one - * focus - */ - if (count > 1) - { - GList *list = tree_column->cell_list; - - /* clear focus first */ - for ( ; list ; list = list->next) - { - GtkTreeViewColumnCellInfo *info = list->data; - if (info->has_focus) - info->has_focus = FALSE; - } - - list = NULL; - if (rtl) - { - if (direction > 0) - list = gtk_tree_view_column_cell_last (tree_column); - else if (direction < 0) - list = gtk_tree_view_column_cell_first (tree_column); - } - else - { - if (direction > 0) - list = gtk_tree_view_column_cell_first (tree_column); - else if (direction < 0) - list = gtk_tree_view_column_cell_last (tree_column); - } - - if (list) - ((GtkTreeViewColumnCellInfo *) list->data)->has_focus = TRUE; - } - - return TRUE; -} + g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), FALSE); -void -_gtk_tree_view_column_cell_draw_focus (GtkTreeViewColumn *tree_column, - cairo_t *cr, - const GdkRectangle *background_area, - const GdkRectangle *cell_area, - guint flags) -{ - gint focus_line_width; - GtkStateType cell_state; - - g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); - g_return_if_fail (cr != NULL); + priv = tree_column->priv; - gtk_widget_style_get (GTK_WIDGET (tree_column->tree_view), - "focus-line-width", &focus_line_width, NULL); - if (tree_column->editable_widget) - { - /* This function is only called on the editable row when editing. - */ -#if 0 - gtk_paint_focus (tree_column->tree_view->style, - window, - gtk_widget_get_state (tree_column->tree_view), - NULL, - tree_column->tree_view, - "treeview", - cell_area->x - focus_line_width, - cell_area->y - focus_line_width, - cell_area->width + 2 * focus_line_width, - cell_area->height + 2 * focus_line_width); -#endif - } - else - { - GdkRectangle focus_rectangle; - gtk_tree_view_column_cell_process_action (tree_column, - cr, - background_area, - cell_area, - flags, - CELL_ACTION_FOCUS, - &focus_rectangle, - NULL, NULL, NULL); - - cell_state = flags & GTK_CELL_RENDERER_SELECTED ? GTK_STATE_SELECTED : - (flags & GTK_CELL_RENDERER_PRELIT ? GTK_STATE_PRELIGHT : - (flags & GTK_CELL_RENDERER_INSENSITIVE ? GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL)); - gtk_paint_focus (gtk_widget_get_style (tree_column->tree_view), - cr, - cell_state, - tree_column->tree_view, - "treeview", - focus_rectangle.x, - focus_rectangle.y, - focus_rectangle.width, - focus_rectangle.height); - } + return gtk_cell_area_event (priv->cell_area, + priv->cell_area_context, + priv->tree_view, + event, + cell_area, + flags); } /** @@ -3553,17 +2876,25 @@ gboolean gtk_tree_view_column_cell_is_visible (GtkTreeViewColumn *tree_column) { GList *list; + GList *cells; + GtkTreeViewColumnPrivate *priv; g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), FALSE); - for (list = tree_column->cell_list; list; list = list->next) - { - GtkTreeViewColumnCellInfo *info = (GtkTreeViewColumnCellInfo *) list->data; + priv = tree_column->priv; - if (gtk_cell_renderer_get_visible (info->cell)) - return TRUE; + cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->cell_area)); + for (list = cells; list; list = list->next) + { + if (gtk_cell_renderer_get_visible (list->data)) + { + g_list_free (cells); + return TRUE; + } } + g_list_free (cells); + return FALSE; } @@ -3581,140 +2912,50 @@ void gtk_tree_view_column_focus_cell (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell) { - GList *list; - gboolean found_cell = FALSE; - g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); - if (_gtk_tree_view_column_count_special_cells (tree_column) < 2) - return; - - for (list = tree_column->cell_list; list; list = list->next) - { - GtkTreeViewColumnCellInfo *info = list->data; - - if (info->cell == cell) - { - info->has_focus = TRUE; - found_cell = TRUE; - break; - } - } - - if (found_cell) - { - for (list = tree_column->cell_list; list; list = list->next) - { - GtkTreeViewColumnCellInfo *info = list->data; - - if (info->cell != cell) - info->has_focus = FALSE; - } - - /* FIXME: redraw? */ - } + gtk_cell_area_set_focus_cell (tree_column->priv->cell_area, cell); } void _gtk_tree_view_column_cell_set_dirty (GtkTreeViewColumn *tree_column, gboolean install_handler) { - GList *list; + GtkTreeViewColumnPrivate *priv = tree_column->priv; - for (list = tree_column->cell_list; list; list = list->next) - { - GtkTreeViewColumnCellInfo *info = (GtkTreeViewColumnCellInfo *) list->data; + priv->dirty = TRUE; + priv->padding = 0; + priv->width = 0; - info->requested_width = 0; - } - tree_column->dirty = TRUE; - tree_column->requested_width = -1; - tree_column->width = 0; + /* Issue a manual reset on the context to have all + * sizes re-requested for the context. + */ + g_signal_handler_block (priv->cell_area_context, + priv->context_changed_signal); + gtk_cell_area_context_reset (priv->cell_area_context); + g_signal_handler_unblock (priv->cell_area_context, + priv->context_changed_signal); - if (tree_column->tree_view && - gtk_widget_get_realized (tree_column->tree_view)) + if (priv->tree_view && + gtk_widget_get_realized (priv->tree_view)) { - if (install_handler) - _gtk_tree_view_install_mark_rows_col_dirty (GTK_TREE_VIEW (tree_column->tree_view)); - else - GTK_TREE_VIEW (tree_column->tree_view)->priv->mark_rows_col_dirty = TRUE; - gtk_widget_queue_resize (tree_column->tree_view); + _gtk_tree_view_install_mark_rows_col_dirty (GTK_TREE_VIEW (priv->tree_view), install_handler); + gtk_widget_queue_resize (priv->tree_view); } } -void -_gtk_tree_view_column_start_editing (GtkTreeViewColumn *tree_column, - GtkCellEditable *cell_editable) -{ - g_return_if_fail (tree_column->editable_widget == NULL); - - tree_column->editable_widget = cell_editable; -} - -void -_gtk_tree_view_column_stop_editing (GtkTreeViewColumn *tree_column) +gboolean +_gtk_tree_view_column_cell_get_dirty (GtkTreeViewColumn *tree_column) { - GList *list; - - g_return_if_fail (tree_column->editable_widget != NULL); - - tree_column->editable_widget = NULL; - for (list = tree_column->cell_list; list; list = list->next) - ((GtkTreeViewColumnCellInfo *)list->data)->in_editing_mode = FALSE; -} - -void -_gtk_tree_view_column_get_neighbor_sizes (GtkTreeViewColumn *column, - GtkCellRenderer *cell, - gint *left, - gint *right) -{ - GList *list; - GtkTreeViewColumnCellInfo *info; - gint l, r; - gboolean rtl; - - l = r = 0; - - list = gtk_tree_view_column_cell_first (column); - - while (list) - { - info = (GtkTreeViewColumnCellInfo *)list->data; - - list = gtk_tree_view_column_cell_next (column, list); - - if (info->cell == cell) - break; - - if (gtk_cell_renderer_get_visible (info->cell)) - l += info->real_width + column->spacing; - } - - while (list) - { - info = (GtkTreeViewColumnCellInfo *)list->data; - - list = gtk_tree_view_column_cell_next (column, list); - - if (gtk_cell_renderer_get_visible (info->cell)) - r += info->real_width + column->spacing; - } - - rtl = (gtk_widget_get_direction (GTK_WIDGET (column->tree_view)) == GTK_TEXT_DIR_RTL); - if (left) - *left = rtl ? r : l; - - if (right) - *right = rtl ? l : r; + return tree_column->priv->dirty; } /** * gtk_tree_view_column_cell_get_position: * @tree_column: a #GtkTreeViewColumn * @cell_renderer: a #GtkCellRenderer - * @start_pos: return location for the horizontal position of @cell within + * @x_offset: return location for the horizontal position of @cell within * @tree_column, may be %NULL * @width: return location for the width of @cell, may be %NULL * @@ -3727,37 +2968,32 @@ _gtk_tree_view_column_get_neighbor_sizes (GtkTreeViewColumn *column, gboolean gtk_tree_view_column_cell_get_position (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell_renderer, - gint *start_pos, + gint *x_offset, gint *width) { - GList *list; - gint current_x = 0; - gboolean found_cell = FALSE; - GtkTreeViewColumnCellInfo *cellinfo = NULL; + GtkTreeViewColumnPrivate *priv; + GdkRectangle zero_cell_area = { 0, }; + GdkRectangle allocation; - list = gtk_tree_view_column_cell_first (tree_column); - for (; list; list = gtk_tree_view_column_cell_next (tree_column, list)) - { - cellinfo = list->data; - if (cellinfo->cell == cell_renderer) - { - found_cell = TRUE; - break; - } + g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), FALSE); + g_return_val_if_fail (GTK_IS_CELL_RENDERER (cell_renderer), FALSE); - if (gtk_cell_renderer_get_visible (cellinfo->cell)) - current_x += cellinfo->real_width; - } + priv = tree_column->priv; - if (found_cell) - { - if (start_pos) - *start_pos = current_x; - if (width) - *width = cellinfo->real_width; - } + /* FIXME: Could use a boolean return value for invalid cells */ + gtk_cell_area_get_cell_allocation (priv->cell_area, + priv->cell_area_context, + priv->tree_view, + cell_renderer, + &zero_cell_area, + &allocation); + + if (x_offset) + *x_offset = allocation.x; + if (width) + *width = allocation.width; - return found_cell; + return TRUE; } /** @@ -3774,7 +3010,7 @@ gtk_tree_view_column_queue_resize (GtkTreeViewColumn *tree_column) { g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column)); - if (tree_column->tree_view) + if (tree_column->priv->tree_view) _gtk_tree_view_column_cell_set_dirty (tree_column, TRUE); } @@ -3796,5 +3032,85 @@ gtk_tree_view_column_get_tree_view (GtkTreeViewColumn *tree_column) { g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), NULL); - return tree_column->tree_view; + return tree_column->priv->tree_view; +} + +/** + * gtk_tree_view_column_get_button: + * @tree_column: A #GtkTreeViewColumn + * + * Returns the button used in the treeview column header + * + * Return value: (transfer none): The button for the column header. + * + * Since: 3.0 + */ +GtkWidget * +gtk_tree_view_column_get_button (GtkTreeViewColumn *tree_column) +{ + g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN (tree_column), NULL); + + return tree_column->priv->button; +} + +GdkWindow * +_gtk_tree_view_column_get_window (GtkTreeViewColumn *column) +{ + return column->priv->window; +} + +void +_gtk_tree_view_column_push_padding (GtkTreeViewColumn *column, + gint padding) +{ + column->priv->padding = MAX (column->priv->padding, padding); +} + +gint +_gtk_tree_view_column_get_requested_width (GtkTreeViewColumn *column) +{ + gint requested_width; + + gtk_cell_area_context_get_preferred_width (column->priv->cell_area_context, &requested_width, NULL); + + return requested_width + column->priv->padding; +} + + +void +_gtk_tree_view_column_set_resized_width (GtkTreeViewColumn *column, + gint width) +{ + column->priv->resized_width = width; +} + +gint +_gtk_tree_view_column_get_resized_width (GtkTreeViewColumn *column) +{ + return column->priv->resized_width; +} + +void +_gtk_tree_view_column_set_use_resized_width (GtkTreeViewColumn *column, + gboolean use_resized_width) +{ + column->priv->use_resized_width = use_resized_width; +} + +gboolean +_gtk_tree_view_column_get_use_resized_width (GtkTreeViewColumn *column) +{ + return column->priv->use_resized_width; +} + +gint +_gtk_tree_view_column_get_drag_x (GtkTreeViewColumn *column) +{ + return column->priv->drag_x; +} + +GtkCellAreaContext * +_gtk_tree_view_column_get_context (GtkTreeViewColumn *column) +{ + return column->priv->cell_area_context; } diff --git a/gtk/gtktreeviewcolumn.h b/gtk/gtktreeviewcolumn.h index 6c0b3a1e88..f964177283 100644 --- a/gtk/gtktreeviewcolumn.h +++ b/gtk/gtktreeviewcolumn.h @@ -27,6 +27,7 @@ #include <gtk/gtkcellrenderer.h> #include <gtk/gtktreemodel.h> #include <gtk/gtktreesortable.h> +#include <gtk/gtkcellarea.h> G_BEGIN_DECLS @@ -39,8 +40,9 @@ G_BEGIN_DECLS #define GTK_IS_TREE_VIEW_COLUMN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TREE_VIEW_COLUMN)) #define GTK_TREE_VIEW_COLUMN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TREE_VIEW_COLUMN, GtkTreeViewColumnClass)) -typedef struct _GtkTreeViewColumn GtkTreeViewColumn; -typedef struct _GtkTreeViewColumnClass GtkTreeViewColumnClass; +typedef struct _GtkTreeViewColumn GtkTreeViewColumn; +typedef struct _GtkTreeViewColumnClass GtkTreeViewColumnClass; +typedef struct _GtkTreeViewColumnPrivate GtkTreeViewColumnPrivate; /** * GtkTreeViewColumnSizing: @@ -85,51 +87,7 @@ struct _GtkTreeViewColumn { GInitiallyUnowned parent_instance; - GtkWidget *GSEAL (tree_view); - GtkWidget *GSEAL (button); - GtkWidget *GSEAL (child); - GtkWidget *GSEAL (arrow); - GtkWidget *GSEAL (alignment); - GdkWindow *GSEAL (window); - GtkCellEditable *GSEAL (editable_widget); - gfloat GSEAL (xalign); - gulong GSEAL (property_changed_signal); - gint GSEAL (spacing); - - /* Sizing fields */ - /* see gtk+/doc/tree-column-sizing.txt for more information on them */ - GtkTreeViewColumnSizing GSEAL (column_type); - gint GSEAL (requested_width); - gint GSEAL (button_request); - gint GSEAL (resized_width); - gint GSEAL (width); - gint GSEAL (fixed_width); - gint GSEAL (min_width); - gint GSEAL (max_width); - - /* dragging columns */ - gint GSEAL (drag_x); - gint GSEAL (drag_y); - - gchar *GSEAL (title); - GList *GSEAL (cell_list); - - /* Sorting */ - gulong GSEAL (sort_clicked_signal); - gulong GSEAL (sort_column_changed_signal); - gint GSEAL (sort_column_id); - GtkSortType GSEAL (sort_order); - - /* Flags */ - guint GSEAL (visible) : 1; - guint GSEAL (resizable) : 1; - guint GSEAL (clickable) : 1; - guint GSEAL (dirty) : 1; - guint GSEAL (show_sort_indicator) : 1; - guint GSEAL (maybe_reordered) : 1; - guint GSEAL (reorderable) : 1; - guint GSEAL (use_resized_width) : 1; - guint GSEAL (expand) : 1; + GtkTreeViewColumnPrivate *priv; }; struct _GtkTreeViewColumnClass @@ -147,6 +105,7 @@ struct _GtkTreeViewColumnClass GType gtk_tree_view_column_get_type (void) G_GNUC_CONST; GtkTreeViewColumn *gtk_tree_view_column_new (void); +GtkTreeViewColumn *gtk_tree_view_column_new_with_area (GtkCellArea *area); GtkTreeViewColumn *gtk_tree_view_column_new_with_attributes (const gchar *title, GtkCellRenderer *cell, ...) G_GNUC_NULL_TERMINATED; @@ -252,10 +211,11 @@ void gtk_tree_view_column_focus_cell (GtkTreeViewCol GtkCellRenderer *cell); gboolean gtk_tree_view_column_cell_get_position (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell_renderer, - gint *start_pos, + gint *x_offset, gint *width); void gtk_tree_view_column_queue_resize (GtkTreeViewColumn *tree_column); GtkWidget *gtk_tree_view_column_get_tree_view (GtkTreeViewColumn *tree_column); +GtkWidget *gtk_tree_view_column_get_button (GtkTreeViewColumn *tree_column); G_END_DECLS diff --git a/gtk/tests/treeview.c b/gtk/tests/treeview.c index 1b3ce9952c..fe42095530 100644 --- a/gtk/tests/treeview.c +++ b/gtk/tests/treeview.c @@ -150,6 +150,84 @@ test_select_collapsed_row (void) gtk_tree_path_free (path); } +static gboolean +test_row_separator_height_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gboolean ret = FALSE; + GtkTreePath *path; + + path = gtk_tree_model_get_path (model, iter); + if (gtk_tree_path_get_indices (path)[0] == 2) + ret = TRUE; + gtk_tree_path_free (path); + + return ret; +} + +static void +test_row_separator_height (void) +{ + int focus_pad, separator_height, height; + gboolean wide_separators; + GtkTreeIter iter; + GtkTreePath *path; + GtkListStore *store; + GtkWidget *window; + GtkWidget *tree_view; + GdkRectangle rect, cell_rect; + + store = gtk_list_store_new (1, G_TYPE_STRING); + gtk_list_store_insert_with_values (store, &iter, 0, 0, "Row content", -1); + gtk_list_store_insert_with_values (store, &iter, 1, 0, "Row content", -1); + gtk_list_store_insert_with_values (store, &iter, 2, 0, "Row content", -1); + gtk_list_store_insert_with_values (store, &iter, 3, 0, "Row content", -1); + gtk_list_store_insert_with_values (store, &iter, 4, 0, "Row content", -1); + + window = gtk_offscreen_window_new (); + + tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store)); + gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (tree_view), + test_row_separator_height_func, + NULL, + NULL); + + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree_view), + 0, + "Test", + gtk_cell_renderer_text_new (), + "text", 0, + NULL); + + gtk_container_add (GTK_CONTAINER (window), tree_view); + gtk_widget_show_all (window); + + + path = gtk_tree_path_new_from_indices (2, -1); + gtk_tree_view_get_background_area (GTK_TREE_VIEW (tree_view), + path, NULL, &rect); + gtk_tree_view_get_cell_area (GTK_TREE_VIEW (tree_view), + path, NULL, &cell_rect); + gtk_tree_path_free (path); + + gtk_widget_style_get (tree_view, + "focus-padding", &focus_pad, + "wide-separators", &wide_separators, + "separator-height", &separator_height, + NULL); + + if (wide_separators) + height = separator_height + 2 * focus_pad; + else + height = 2 + 2 * focus_pad; + + g_assert_cmpint (rect.height, ==, height); + g_assert_cmpint (cell_rect.height, ==, height); + + gtk_widget_destroy (tree_view); +} + int main (int argc, char **argv) @@ -160,6 +238,8 @@ main (int argc, g_test_add_func ("/TreeView/cursor/bug-539377", test_bug_539377); g_test_add_func ("/TreeView/cursor/select-collapsed_row", test_select_collapsed_row); + g_test_add_func ("/TreeView/sizing/row-separator-height", + test_row_separator_height); return g_test_run (); } diff --git a/modules/other/gail/gailtreeview.c b/modules/other/gail/gailtreeview.c index 60de100e3a..f1b506357f 100644 --- a/modules/other/gail/gailtreeview.c +++ b/modules/other/gail/gailtreeview.c @@ -4651,7 +4651,7 @@ get_header_from_column (GtkTreeViewColumn *tv_col) /* If the user has not set a header object, grab the column */ /* header object defined by the GtkTreeView */ - header_widget = tv_col->button; + header_widget = gtk_tree_view_column_get_button (tv_col); if (header_widget) { diff --git a/tests/Makefile.am b/tests/Makefile.am index 50e3a3167f..681e9b3fda 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -87,6 +87,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \ testtreecolumns \ testtreecolumnsizing \ testtreesort \ + testverticalcells \ treestoretest \ testxinerama \ testwindows \ @@ -98,6 +99,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \ testexpander \ testvolumebutton \ testscrolledwindow \ + testcellarea \ testswitch \ styleexamples @@ -178,6 +180,7 @@ testtreeflow_DEPENDENCIES = $(DEPS) testtreecolumns_DEPENDENCIES = $(DEPS) testtreecolumnsizing_DEPENDENCIES = $(DEPS) testtreesort_DEPENDENCIES = $(DEPS) +testverticalcells_DEPENDENCIES = $(DEPS) treestoretest_DEPENDENCIES = $(TEST_DEPS) testxinerama_DEPENDENCIES = $(TEST_DEPS) testmerge_DEPENDENCIES = $(TEST_DEPS) @@ -186,6 +189,7 @@ testgrouping_DEPENDENCIES = $(TEST_DEPS) testtooltips_DEPENDENCIES = $(TEST_DEPS) testvolumebutton_DEPENDENCIES = $(TEST_DEPS) testscrolledwindow_DEPENDENCIES = $(TEST_DEPS) +testcellarea_DEPENDENCIES = $(TEST_DEPS) testwindows_DEPENDENCIES = $(TEST_DEPS) testexpand_DEPENDENCIES = $(TEST_DEPS) testexpander_DEPENDENCIES = $(TEST_DEPS) @@ -255,6 +259,7 @@ testtreeflow_LDADD = $(LDADDS) testtreecolumns_LDADD = $(LDADDS) testtreecolumnsizing_LDADD = $(LDADDS) testtreesort_LDADD = $(LDADDS) +testverticalcells_LDADD = $(LDADDS) treestoretest_LDADD = $(LDADDS) testxinerama_LDADD = $(LDADDS) testmerge_LDADD = $(LDADDS) @@ -263,6 +268,7 @@ testgrouping_LDADD = $(LDADDS) testtooltips_LDADD = $(LDADDS) testvolumebutton_LDADD = $(LDADDS) testscrolledwindow_LDADD = $(LDADDS) +testcellarea_LDADD = $(LDADDS) testwindows_LDADD = $(LDADDS) testexpand_LDADD = $(LDADDS) testexpander_LDADD = $(LDADDS) @@ -373,6 +379,11 @@ testvolumebutton_SOURCES = \ testscrolledwindow_SOURCES = \ testscrolledwindow.c +testcellarea_SOURCES = \ + testcellarea.c \ + cellareascaffold.c \ + cellareascaffold.h + testoffscreen_SOURCES = \ gtkoffscreenbox.c \ gtkoffscreenbox.h \ diff --git a/tests/cellareascaffold.c b/tests/cellareascaffold.c new file mode 100644 index 0000000000..c623b20acd --- /dev/null +++ b/tests/cellareascaffold.c @@ -0,0 +1,1140 @@ +/* cellareascaffold.c + * + * Copyright (C) 2010 Openismus GmbH + * + * Authors: + * Tristan Van Berkom <tristanvb@openismus.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <string.h> +#include "cellareascaffold.h" + +/* GObjectClass */ +static void cell_area_scaffold_finalize (GObject *object); +static void cell_area_scaffold_dispose (GObject *object); + +/* GtkWidgetClass */ +static void cell_area_scaffold_realize (GtkWidget *widget); +static void cell_area_scaffold_unrealize (GtkWidget *widget); +static gboolean cell_area_scaffold_draw (GtkWidget *widget, + cairo_t *cr); +static void cell_area_scaffold_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void cell_area_scaffold_get_preferred_width (GtkWidget *widget, + gint *minimum_size, + gint *natural_size); +static void cell_area_scaffold_get_preferred_height_for_width (GtkWidget *widget, + gint for_size, + gint *minimum_size, + gint *natural_size); +static void cell_area_scaffold_get_preferred_height (GtkWidget *widget, + gint *minimum_size, + gint *natural_size); +static void cell_area_scaffold_get_preferred_width_for_height (GtkWidget *widget, + 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); + +/* GtkContainerClass */ +static void cell_area_scaffold_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data); +static void cell_area_scaffold_remove (GtkContainer *container, + GtkWidget *child); +static void cell_area_scaffold_put_edit_widget (CellAreaScaffold *scaffold, + GtkWidget *edit_widget, + gint x, + gint y, + gint width, + gint height); + +/* CellAreaScaffoldClass */ +static void cell_area_scaffold_activate (CellAreaScaffold *scaffold); + +/* CellArea/GtkTreeModel callbacks */ +static void size_changed_cb (GtkCellAreaContext *context, + GParamSpec *pspec, + CellAreaScaffold *scaffold); +static void focus_changed_cb (GtkCellArea *area, + GtkCellRenderer *renderer, + const gchar *path, + CellAreaScaffold *scaffold); +static void add_editable_cb (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *edit_widget, + GdkRectangle *cell_area, + const gchar *path, + CellAreaScaffold *scaffold); +static void remove_editable_cb (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *edit_widget, + CellAreaScaffold *scaffold); +static void row_changed_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + CellAreaScaffold *scaffold); +static void row_inserted_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + CellAreaScaffold *scaffold); +static void row_deleted_cb (GtkTreeModel *model, + GtkTreePath *path, + CellAreaScaffold *scaffold); +static void rows_reordered_cb (GtkTreeModel *model, + GtkTreePath *parent, + GtkTreeIter *iter, + gint *new_order, + CellAreaScaffold *scaffold); + +typedef struct { + gint size; /* The height of rows in the scaffold's */ +} RowData; + +struct _CellAreaScaffoldPrivate { + + /* Window for catching events and dispatching them to the cell area */ + GdkWindow *event_window; + + /* The model we're showing data for */ + GtkTreeModel *model; + gulong row_changed_id; + gulong row_inserted_id; + gulong row_deleted_id; + gulong rows_reordered_id; + + /* The area rendering the data and a global context */ + GtkCellArea *area; + GtkCellAreaContext *context; + + /* Cache some info about rows (hieghts etc) */ + GArray *row_data; + + /* Focus handling */ + gint focus_row; + gulong focus_changed_id; + + /* Check when the underlying area changes the size and + * we need to queue a redraw */ + gulong size_changed_id; + + /* Currently edited widget */ + GtkWidget *edit_widget; + GdkRectangle edit_rect; + gulong add_editable_id; + gulong remove_editable_id; + + + gint row_spacing; + gint indent; +}; + +enum { + ACTIVATE, + N_SIGNALS +}; + +static guint scaffold_signals[N_SIGNALS] = { 0 }; + +#define DIRECTION_STR(dir) \ + ((dir) == GTK_DIR_TAB_FORWARD ? "tab forward" : \ + (dir) == GTK_DIR_TAB_BACKWARD ? "tab backward" : \ + (dir) == GTK_DIR_UP ? "up" : \ + (dir) == GTK_DIR_DOWN ? "down" : \ + (dir) == GTK_DIR_LEFT ? "left" : \ + (dir) == GTK_DIR_RIGHT ? "right" : "invalid") + +G_DEFINE_TYPE_WITH_CODE (CellAreaScaffold, cell_area_scaffold, GTK_TYPE_CONTAINER, + G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)); + + +static void +cell_area_scaffold_init (CellAreaScaffold *scaffold) +{ + CellAreaScaffoldPrivate *priv; + + scaffold->priv = G_TYPE_INSTANCE_GET_PRIVATE (scaffold, + TYPE_CELL_AREA_SCAFFOLD, + CellAreaScaffoldPrivate); + priv = scaffold->priv; + + priv->area = gtk_cell_area_box_new (); + priv->context = gtk_cell_area_create_context (priv->area); + + priv->row_data = g_array_new (FALSE, FALSE, sizeof (RowData)); + + gtk_widget_set_has_window (GTK_WIDGET (scaffold), FALSE); + gtk_widget_set_can_focus (GTK_WIDGET (scaffold), TRUE); + + priv->size_changed_id = + g_signal_connect (priv->context, "notify", + G_CALLBACK (size_changed_cb), scaffold); + + priv->focus_changed_id = + g_signal_connect (priv->area, "focus-changed", + G_CALLBACK (focus_changed_cb), scaffold); + + priv->add_editable_id = + g_signal_connect (priv->area, "add-editable", + G_CALLBACK (add_editable_cb), scaffold); + + priv->remove_editable_id = + g_signal_connect (priv->area, "remove-editable", + G_CALLBACK (remove_editable_cb), scaffold); +} + +static void +cell_area_scaffold_class_init (CellAreaScaffoldClass *class) +{ + GObjectClass *gobject_class; + GtkWidgetClass *widget_class; + GtkContainerClass *container_class; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->dispose = cell_area_scaffold_dispose; + gobject_class->finalize = cell_area_scaffold_finalize; + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->realize = cell_area_scaffold_realize; + widget_class->unrealize = cell_area_scaffold_unrealize; + widget_class->draw = cell_area_scaffold_draw; + widget_class->size_allocate = cell_area_scaffold_size_allocate; + widget_class->get_preferred_width = cell_area_scaffold_get_preferred_width; + 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; + + container_class = GTK_CONTAINER_CLASS (class); + container_class->forall = cell_area_scaffold_forall; + container_class->remove = cell_area_scaffold_remove; + + class->activate = cell_area_scaffold_activate; + + scaffold_signals[ACTIVATE] = + g_signal_new ("activate", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (CellAreaScaffoldClass, activate), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + widget_class->activate_signal = scaffold_signals[ACTIVATE]; + + + g_type_class_add_private (gobject_class, sizeof (CellAreaScaffoldPrivate)); +} + +/********************************************************* + * GObjectClass * + *********************************************************/ +static void +cell_area_scaffold_finalize (GObject *object) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (object); + CellAreaScaffoldPrivate *priv; + + priv = scaffold->priv; + + g_array_free (priv->row_data, TRUE); + + G_OBJECT_CLASS (cell_area_scaffold_parent_class)->finalize (object); +} + +static void +cell_area_scaffold_dispose (GObject *object) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (object); + CellAreaScaffoldPrivate *priv; + + priv = scaffold->priv; + + cell_area_scaffold_set_model (scaffold, NULL); + + if (priv->context) + { + /* Disconnect signals */ + g_signal_handler_disconnect (priv->context, priv->size_changed_id); + + g_object_unref (priv->context); + priv->context = NULL; + priv->size_changed_id = 0; + } + + if (priv->area) + { + /* Disconnect signals */ + g_signal_handler_disconnect (priv->area, priv->focus_changed_id); + g_signal_handler_disconnect (priv->area, priv->add_editable_id); + g_signal_handler_disconnect (priv->area, priv->remove_editable_id); + + g_object_unref (priv->area); + priv->area = NULL; + priv->focus_changed_id = 0; + } + + G_OBJECT_CLASS (cell_area_scaffold_parent_class)->dispose (object); +} + +/********************************************************* + * GtkWidgetClass * + *********************************************************/ +static void +cell_area_scaffold_realize (GtkWidget *widget) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget); + CellAreaScaffoldPrivate *priv = scaffold->priv; + GtkAllocation allocation; + GdkWindow *window; + GdkWindowAttr attributes; + gint attributes_mask; + + gtk_widget_get_allocation (widget, &allocation); + + gtk_widget_set_realized (widget, TRUE); + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + attributes.wclass = GDK_INPUT_ONLY; + attributes.event_mask = gtk_widget_get_events (widget); + attributes.event_mask |= (GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK); + + attributes_mask = GDK_WA_X | GDK_WA_Y; + + window = gtk_widget_get_parent_window (widget); + gtk_widget_set_window (widget, window); + g_object_ref (window); + + priv->event_window = gdk_window_new (window, &attributes, attributes_mask); + gdk_window_set_user_data (priv->event_window, widget); + + gtk_widget_style_attach (widget); +} + +static void +cell_area_scaffold_unrealize (GtkWidget *widget) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget); + CellAreaScaffoldPrivate *priv = scaffold->priv; + + if (priv->event_window) + { + gdk_window_set_user_data (priv->event_window, NULL); + gdk_window_destroy (priv->event_window); + priv->event_window = NULL; + } + + GTK_WIDGET_CLASS (cell_area_scaffold_parent_class)->unrealize (widget); +} + +static gboolean +cell_area_scaffold_draw (GtkWidget *widget, + cairo_t *cr) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget); + CellAreaScaffoldPrivate *priv = scaffold->priv; + GtkTreeIter iter; + gboolean valid; + GdkRectangle background_area; + GdkRectangle render_area; + GtkAllocation allocation; + gint i = 0; + gboolean have_focus; + GtkCellRendererState flags; + + if (!priv->model) + return FALSE; + + have_focus = gtk_widget_has_focus (widget); + + gtk_widget_get_allocation (widget, &allocation); + + render_area.x = 0; + render_area.y = 0; + render_area.width = allocation.width; + render_area.height = allocation.height; + + background_area = render_area; + + render_area.x = priv->indent; + render_area.width -= priv->indent; + + valid = gtk_tree_model_get_iter_first (priv->model, &iter); + while (valid) + { + RowData *data = &g_array_index (priv->row_data, RowData, i); + + if (have_focus && i == priv->focus_row) + flags = GTK_CELL_RENDERER_FOCUSED; + else + flags = 0; + + render_area.height = data->size; + + background_area.height = render_area.height; + background_area.y = render_area.y; + + if (i == 0) + { + background_area.height += priv->row_spacing / 2; + background_area.height += priv->row_spacing % 2; + } + else if (i == priv->row_data->len - 1) + { + background_area.y -= priv->row_spacing / 2; + background_area.height += priv->row_spacing / 2; + } + else + { + background_area.y -= priv->row_spacing / 2; + background_area.height += priv->row_spacing; + } + + gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE); + gtk_cell_area_render (priv->area, priv->context, widget, cr, + &background_area, &render_area, flags, + (have_focus && i == priv->focus_row)); + + render_area.y += data->size; + render_area.y += priv->row_spacing; + + i++; + valid = gtk_tree_model_iter_next (priv->model, &iter); + } + + /* Draw the edit widget after drawing everything else */ + GTK_WIDGET_CLASS (cell_area_scaffold_parent_class)->draw (widget, cr); + + return FALSE; +} + +static void +request_all_base (CellAreaScaffold *scaffold) +{ + CellAreaScaffoldPrivate *priv = scaffold->priv; + GtkWidget *widget = GTK_WIDGET (scaffold); + GtkTreeIter iter; + gboolean valid; + + if (!priv->model) + return; + + g_signal_handler_block (priv->context, priv->size_changed_id); + + valid = gtk_tree_model_get_iter_first (priv->model, &iter); + while (valid) + { + gint min, nat; + + gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE); + gtk_cell_area_get_preferred_width (priv->area, priv->context, widget, &min, &nat); + + valid = gtk_tree_model_iter_next (priv->model, &iter); + } + + g_signal_handler_unblock (priv->context, priv->size_changed_id); +} + +static void +get_row_sizes (CellAreaScaffold *scaffold, + GArray *array, + gint for_size) +{ + CellAreaScaffoldPrivate *priv = scaffold->priv; + GtkWidget *widget = GTK_WIDGET (scaffold); + GtkTreeIter iter; + gboolean valid; + gint i = 0; + + if (!priv->model) + return; + + valid = gtk_tree_model_get_iter_first (priv->model, &iter); + while (valid) + { + RowData *data = &g_array_index (array, RowData, i); + + gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE); + gtk_cell_area_get_preferred_height_for_width (priv->area, priv->context, widget, + for_size, &data->size, NULL); + + i++; + valid = gtk_tree_model_iter_next (priv->model, &iter); + } +} + +static void +cell_area_scaffold_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget); + CellAreaScaffoldPrivate *priv = scaffold->priv; + + gtk_widget_set_allocation (widget, allocation); + + if (gtk_widget_get_realized (widget)) + gdk_window_move_resize (priv->event_window, + allocation->x, + allocation->y, + allocation->width, + allocation->height); + + /* Allocate the child GtkCellEditable widget if one is currently editing a row */ + if (priv->edit_widget) + gtk_widget_size_allocate (priv->edit_widget, &priv->edit_rect); + + if (!priv->model) + return; + + /* Cache the per-row sizes and allocate the context */ + gtk_cell_area_context_allocate (priv->context, allocation->width - priv->indent, -1); + get_row_sizes (scaffold, priv->row_data, allocation->width - priv->indent); +} + + +static void +cell_area_scaffold_get_preferred_width (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget); + CellAreaScaffoldPrivate *priv = scaffold->priv; + + if (!priv->model) + return; + + request_all_base (scaffold); + gtk_cell_area_context_get_preferred_width (priv->context, minimum_size, natural_size); + + *minimum_size += priv->indent; + *natural_size += priv->indent; +} + +static void +cell_area_scaffold_get_preferred_height_for_width (GtkWidget *widget, + gint for_size, + gint *minimum_size, + gint *natural_size) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget); + CellAreaScaffoldPrivate *priv = scaffold->priv; + GArray *request_array; + gint n_rows, i, full_size = 0; + + if (!priv->model) + return; + + n_rows = gtk_tree_model_iter_n_children (priv->model, NULL); + + /* Get an array for the contextual request */ + request_array = g_array_new (FALSE, FALSE, sizeof (RowData)); + g_array_set_size (request_array, n_rows); + memset (request_array->data, 0x0, n_rows * sizeof (RowData)); + + /* Gather each contextual size into the request array */ + get_row_sizes (scaffold, request_array, for_size - priv->indent); + + /* Sum up the size and add some row spacing */ + for (i = 0; i < n_rows; i++) + { + RowData *data = &g_array_index (request_array, RowData, i); + + full_size += data->size; + } + + full_size += MAX (0, n_rows -1) * priv->row_spacing; + + g_array_free (request_array, TRUE); + + *minimum_size = full_size; + *natural_size = full_size; +} + +static void +cell_area_scaffold_get_preferred_height (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget); + CellAreaScaffoldPrivate *priv = scaffold->priv; + gint min_size, nat_size; + + if (!priv->model) + return; + + GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_size, &nat_size); + GTK_WIDGET_GET_CLASS (widget)->get_preferred_height_for_width (widget, min_size, + minimum_size, natural_size); +} + +static void +cell_area_scaffold_get_preferred_width_for_height (GtkWidget *widget, + gint for_size, + gint *minimum_size, + gint *natural_size) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget); + CellAreaScaffoldPrivate *priv = scaffold->priv; + + if (!priv->model) + return; + + GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, minimum_size, natural_size); +} + +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) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget); + CellAreaScaffoldPrivate *priv = scaffold->priv; + GtkTreeIter iter; + gboolean valid; + gint focus_row; + gboolean changed = FALSE; + + /* Grab focus on ourself if we dont already have focus */ + if (!gtk_widget_has_focus (widget)) + gtk_widget_grab_focus (widget); + + /* Move focus from cell to cell and row to row */ + 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) + { + gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE); + + /* If focus stays in the area we dont need to do any more */ + if (gtk_cell_area_focus (priv->area, direction)) + { + priv->focus_row = focus_row; + + /* XXX A smarter implementation would only invalidate the rectangles where + * focus was removed from and new focus was placed */ + gtk_widget_queue_draw (widget); + changed = TRUE; + break; + } + else + { + if (direction == GTK_DIR_RIGHT || + direction == GTK_DIR_LEFT) + break; + else if (direction == GTK_DIR_UP || + direction == GTK_DIR_TAB_BACKWARD) + { + if (focus_row == 0) + break; + else + { + /* XXX A real implementation should check if the + * previous row can focus with it's attributes setup */ + focus_row--; + valid = gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, focus_row); + } + } + else /* direction == GTK_DIR_DOWN || GTK_DIR_TAB_FORWARD */ + { + if (focus_row == priv->row_data->len - 1) + break; + else + { + /* XXX A real implementation should check if the + * previous row can focus with it's attributes setup */ + focus_row++; + valid = gtk_tree_model_iter_next (priv->model, &iter); + } + } + } + } + + 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 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; + gint i = 0; + GdkRectangle event_area; + GtkAllocation allocation; + gboolean handled = FALSE; + + gtk_widget_get_allocation (widget, &allocation); + + event_area.x = 0; + event_area.y = 0; + event_area.width = allocation.width; + event_area.height = allocation.height; + + event_area.x = priv->indent; + event_area.width -= priv->indent; + + valid = gtk_tree_model_get_iter_first (priv->model, &iter); + while (valid) + { + RowData *data = &g_array_index (priv->row_data, RowData, i); + + event_area.height = data->size; + + if (event->y >= event_area.y && + event->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->context, GTK_WIDGET (scaffold), + (GdkEvent *)event, &event_area, 0); + break; + } + + event_area.y += data->size; + event_area.y += priv->row_spacing; + + i++; + valid = gtk_tree_model_iter_next (priv->model, &iter); + } + + return handled; +} + + +/********************************************************* + * GtkContainerClass * + *********************************************************/ +static void +cell_area_scaffold_put_edit_widget (CellAreaScaffold *scaffold, + GtkWidget *edit_widget, + gint x, + gint y, + gint width, + gint height) +{ + CellAreaScaffoldPrivate *priv = scaffold->priv; + + priv->edit_rect.x = x; + priv->edit_rect.y = y; + priv->edit_rect.width = width; + priv->edit_rect.height = height; + priv->edit_widget = edit_widget; + + gtk_widget_set_parent (edit_widget, GTK_WIDGET (scaffold)); +} + +static void +cell_area_scaffold_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (container); + CellAreaScaffoldPrivate *priv = scaffold->priv; + + if (priv->edit_widget) + (* callback) (priv->edit_widget, callback_data); +} + +static void +cell_area_scaffold_remove (GtkContainer *container, + GtkWidget *child) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (container); + CellAreaScaffoldPrivate *priv = scaffold->priv; + + g_return_if_fail (child == priv->edit_widget); + + gtk_widget_unparent (priv->edit_widget); + priv->edit_widget = NULL; +} + +/********************************************************* + * CellAreaScaffoldClass * + *********************************************************/ +static void +cell_area_scaffold_activate (CellAreaScaffold *scaffold) +{ + CellAreaScaffoldPrivate *priv = scaffold->priv; + GtkWidget *widget = GTK_WIDGET (scaffold); + GtkAllocation allocation; + GdkRectangle cell_area; + GtkTreeIter iter; + gboolean valid; + gint i = 0; + + gtk_widget_get_allocation (widget, &allocation); + + cell_area.x = 0; + cell_area.y = 0; + cell_area.width = allocation.width; + cell_area.height = allocation.height; + + cell_area.x = priv->indent; + cell_area.width -= priv->indent; + + valid = gtk_tree_model_get_iter_first (priv->model, &iter); + while (valid) + { + RowData *data = &g_array_index (priv->row_data, RowData, i); + + if (i == priv->focus_row) + { + cell_area.height = data->size; + + gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE); + gtk_cell_area_activate (priv->area, priv->context, widget, &cell_area, + GTK_CELL_RENDERER_FOCUSED, FALSE); + + break; + } + + cell_area.y += data->size + priv->row_spacing; + + i++; + valid = gtk_tree_model_iter_next (priv->model, &iter); + } +} + +/********************************************************* + * CellArea/GtkTreeModel callbacks * + *********************************************************/ +static void +size_changed_cb (GtkCellAreaContext *context, + GParamSpec *pspec, + CellAreaScaffold *scaffold) +{ + if (!strcmp (pspec->name, "minimum-width") || + !strcmp (pspec->name, "natural-width") || + !strcmp (pspec->name, "minimum-height") || + !strcmp (pspec->name, "natural-height")) + 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; + 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); + + /* Make sure we have focus now */ + if (!gtk_widget_has_focus (widget)) + gtk_widget_grab_focus (widget); + + gtk_widget_queue_draw (widget); +} + +static void +add_editable_cb (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *edit_widget, + GdkRectangle *cell_area, + const gchar *path, + CellAreaScaffold *scaffold) +{ + GtkAllocation allocation; + + gtk_widget_get_allocation (GTK_WIDGET (scaffold), &allocation); + + cell_area_scaffold_put_edit_widget (scaffold, GTK_WIDGET (edit_widget), + allocation.x + cell_area->x, + allocation.y + cell_area->y, + cell_area->width, cell_area->height); +} + +static void +remove_editable_cb (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *edit_widget, + CellAreaScaffold *scaffold) +{ + gtk_container_remove (GTK_CONTAINER (scaffold), GTK_WIDGET (edit_widget)); + + gtk_widget_grab_focus (GTK_WIDGET (scaffold)); +} + +static void +rebuild_and_reset_internals (CellAreaScaffold *scaffold) +{ + CellAreaScaffoldPrivate *priv = scaffold->priv; + gint n_rows; + + if (priv->model) + { + n_rows = gtk_tree_model_iter_n_children (priv->model, NULL); + + /* Clear/reset the array */ + g_array_set_size (priv->row_data, n_rows); + memset (priv->row_data->data, 0x0, n_rows * sizeof (RowData)); + } + else + g_array_set_size (priv->row_data, 0); + + /* Data changed, lets reset the context and consequently queue resize and + * start everything over again (note this is definitly far from optimized) */ + gtk_cell_area_context_reset (priv->context); +} + +static void +row_changed_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + CellAreaScaffold *scaffold) +{ + rebuild_and_reset_internals (scaffold); +} + +static void +row_inserted_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + CellAreaScaffold *scaffold) +{ + rebuild_and_reset_internals (scaffold); +} + +static void +row_deleted_cb (GtkTreeModel *model, + GtkTreePath *path, + CellAreaScaffold *scaffold) +{ + rebuild_and_reset_internals (scaffold); +} + +static void +rows_reordered_cb (GtkTreeModel *model, + GtkTreePath *parent, + GtkTreeIter *iter, + gint *new_order, + CellAreaScaffold *scaffold) +{ + rebuild_and_reset_internals (scaffold); +} + +/********************************************************* + * API * + *********************************************************/ +GtkWidget * +cell_area_scaffold_new (void) +{ + return (GtkWidget *)g_object_new (TYPE_CELL_AREA_SCAFFOLD, NULL); +} + +GtkCellArea * +cell_area_scaffold_get_area (CellAreaScaffold *scaffold) +{ + CellAreaScaffoldPrivate *priv; + + g_return_val_if_fail (IS_CELL_AREA_SCAFFOLD (scaffold), NULL); + + priv = scaffold->priv; + + return priv->area; +} + +void +cell_area_scaffold_set_model (CellAreaScaffold *scaffold, + GtkTreeModel *model) +{ + CellAreaScaffoldPrivate *priv; + + g_return_if_fail (IS_CELL_AREA_SCAFFOLD (scaffold)); + + priv = scaffold->priv; + + if (priv->model != model) + { + if (priv->model) + { + g_signal_handler_disconnect (priv->model, priv->row_changed_id); + g_signal_handler_disconnect (priv->model, priv->row_inserted_id); + g_signal_handler_disconnect (priv->model, priv->row_deleted_id); + g_signal_handler_disconnect (priv->model, priv->rows_reordered_id); + + g_object_unref (priv->model); + } + + priv->model = model; + + if (priv->model) + { + g_object_ref (priv->model); + + priv->row_changed_id = + g_signal_connect (priv->model, "row-changed", + G_CALLBACK (row_changed_cb), scaffold); + + priv->row_inserted_id = + g_signal_connect (priv->model, "row-inserted", + G_CALLBACK (row_inserted_cb), scaffold); + + priv->row_deleted_id = + g_signal_connect (priv->model, "row-deleted", + G_CALLBACK (row_deleted_cb), scaffold); + + priv->rows_reordered_id = + g_signal_connect (priv->model, "rows-reordered", + G_CALLBACK (rows_reordered_cb), scaffold); + } + + rebuild_and_reset_internals (scaffold); + } +} + +GtkTreeModel * +cell_area_scaffold_get_model (CellAreaScaffold *scaffold) +{ + CellAreaScaffoldPrivate *priv; + + g_return_val_if_fail (IS_CELL_AREA_SCAFFOLD (scaffold), NULL); + + priv = scaffold->priv; + + return priv->model; +} + + +void +cell_area_scaffold_set_row_spacing (CellAreaScaffold *scaffold, + gint spacing) +{ + CellAreaScaffoldPrivate *priv; + + g_return_if_fail (IS_CELL_AREA_SCAFFOLD (scaffold)); + + priv = scaffold->priv; + + if (priv->row_spacing != spacing) + { + priv->row_spacing = spacing; + gtk_widget_queue_resize (GTK_WIDGET (scaffold)); + } +} + +gint +cell_area_scaffold_get_row_spacing (CellAreaScaffold *scaffold) +{ + CellAreaScaffoldPrivate *priv; + + g_return_val_if_fail (IS_CELL_AREA_SCAFFOLD (scaffold), 0); + + priv = scaffold->priv; + + return priv->row_spacing; +} + +void +cell_area_scaffold_set_indentation (CellAreaScaffold *scaffold, + gint indent) +{ + CellAreaScaffoldPrivate *priv; + + g_return_if_fail (IS_CELL_AREA_SCAFFOLD (scaffold)); + + priv = scaffold->priv; + + if (priv->indent != indent) + { + priv->indent = indent; + gtk_widget_queue_resize (GTK_WIDGET (scaffold)); + } +} + +gint +cell_area_scaffold_get_indentation (CellAreaScaffold *scaffold) +{ + CellAreaScaffoldPrivate *priv; + + g_return_val_if_fail (IS_CELL_AREA_SCAFFOLD (scaffold), 0); + + priv = scaffold->priv; + + return priv->indent; +} + + diff --git a/tests/cellareascaffold.h b/tests/cellareascaffold.h new file mode 100644 index 0000000000..cdc7b254c9 --- /dev/null +++ b/tests/cellareascaffold.h @@ -0,0 +1,79 @@ +/* cellareascaffold.h + * + * Copyright (C) 2010 Openismus GmbH + * + * Authors: + * Tristan Van Berkom <tristanvb@openismus.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __CELL_AREA_SCAFFOLD_H__ +#define __CELL_AREA_SCAFFOLD_H__ + +#include <gtk/gtk.h> + + +G_BEGIN_DECLS + +#define TYPE_CELL_AREA_SCAFFOLD (cell_area_scaffold_get_type ()) +#define CELL_AREA_SCAFFOLD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_CELL_AREA_SCAFFOLD, CellAreaScaffold)) +#define CELL_AREA_SCAFFOLD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_CELL_AREA_SCAFFOLD, CellAreaScaffoldClass)) +#define IS_CELL_AREA_SCAFFOLD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_CELL_AREA_SCAFFOLD)) +#define IS_CELL_AREA_SCAFFOLD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_CELL_AREA_SCAFFOLD)) +#define CELL_AREA_SCAFFOLD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_CELL_AREA_SCAFFOLD, CellAreaScaffoldClass)) + + +typedef struct _CellAreaScaffold CellAreaScaffold; +typedef struct _CellAreaScaffoldClass CellAreaScaffoldClass; +typedef struct _CellAreaScaffoldPrivate CellAreaScaffoldPrivate; + +struct _CellAreaScaffold +{ + GtkContainer widget; + + CellAreaScaffoldPrivate *priv; +}; + +struct _CellAreaScaffoldClass +{ + GtkContainerClass parent_class; + + void (* activate) (CellAreaScaffold *scaffold); +}; + + +GType cell_area_scaffold_get_type (void) G_GNUC_CONST; +GtkWidget *cell_area_scaffold_new (void); + +GtkCellArea *cell_area_scaffold_get_area (CellAreaScaffold *scaffold); +void cell_area_scaffold_set_model (CellAreaScaffold *scaffold, + GtkTreeModel *model); +GtkTreeModel *cell_area_scaffold_get_model (CellAreaScaffold *scaffold); + +void cell_area_scaffold_set_row_spacing (CellAreaScaffold *scaffold, + gint spacing); +gint cell_area_scaffold_get_row_spacing (CellAreaScaffold *scaffold); + +void cell_area_scaffold_set_indentation (CellAreaScaffold *scaffold, + gint indent); +gint cell_area_scaffold_get_indentation (CellAreaScaffold *scaffold); + + + +G_END_DECLS + +#endif /* __CELL_AREA_SCAFFOLD_H__ */ diff --git a/tests/testcellarea.c b/tests/testcellarea.c new file mode 100644 index 0000000000..ec9398d6f2 --- /dev/null +++ b/tests/testcellarea.c @@ -0,0 +1,627 @@ +#include <gtk/gtk.h> +#include "cellareascaffold.h" + +/******************************************************* + * Simple Test * + *******************************************************/ +enum { + SIMPLE_COLUMN_NAME, + SIMPLE_COLUMN_ICON, + SIMPLE_COLUMN_DESCRIPTION, + N_SIMPLE_COLUMNS +}; + +static GtkCellRenderer *cell_1 = NULL, *cell_2 = NULL, *cell_3 = NULL; + +static GtkTreeModel * +simple_list_model (void) +{ + GtkTreeIter iter; + GtkListStore *store = + gtk_list_store_new (N_SIMPLE_COLUMNS, + G_TYPE_STRING, /* name text */ + G_TYPE_STRING, /* icon name */ + G_TYPE_STRING); /* description text */ + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + SIMPLE_COLUMN_NAME, "Alice in wonderland", + SIMPLE_COLUMN_ICON, "gtk-execute", + SIMPLE_COLUMN_DESCRIPTION, + "Twas brillig, and the slithy toves " + "did gyre and gimble in the wabe; " + "all mimsy were the borogoves, " + "and the mome raths outgrabe", + -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + SIMPLE_COLUMN_NAME, "Marry Poppins", + SIMPLE_COLUMN_ICON, "gtk-yes", + SIMPLE_COLUMN_DESCRIPTION, "Supercalifragilisticexpialidocious", + -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + SIMPLE_COLUMN_NAME, "George Bush", + SIMPLE_COLUMN_ICON, "gtk-dialog-warning", + SIMPLE_COLUMN_DESCRIPTION, "It's a very good question, very direct, " + "and I'm not going to answer it", + -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + SIMPLE_COLUMN_NAME, "Whinnie the pooh", + SIMPLE_COLUMN_ICON, "gtk-stop", + SIMPLE_COLUMN_DESCRIPTION, "The most wonderful thing about tiggers, " + "is tiggers are wonderful things", + -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + SIMPLE_COLUMN_NAME, "Aleister Crowley", + SIMPLE_COLUMN_ICON, "gtk-about", + SIMPLE_COLUMN_DESCRIPTION, + "Thou shalt do what thou wilt shall be the whole of the law", + -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + SIMPLE_COLUMN_NAME, "Mark Twain", + SIMPLE_COLUMN_ICON, "gtk-quit", + SIMPLE_COLUMN_DESCRIPTION, + "Giving up smoking is the easiest thing in the world. " + "I know because I've done it thousands of times.", + -1); + + + return (GtkTreeModel *)store; +} + +static GtkWidget * +simple_scaffold (void) +{ + GtkTreeModel *model; + GtkWidget *scaffold; + GtkCellArea *area; + GtkCellRenderer *renderer; + + scaffold = cell_area_scaffold_new (); + gtk_widget_show (scaffold); + + model = simple_list_model (); + + cell_area_scaffold_set_model (CELL_AREA_SCAFFOLD (scaffold), model); + + area = cell_area_scaffold_get_area (CELL_AREA_SCAFFOLD (scaffold)); + + cell_1 = renderer = gtk_cell_renderer_text_new (); + gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), renderer, FALSE, FALSE); + gtk_cell_area_attribute_connect (area, renderer, "text", SIMPLE_COLUMN_NAME); + + cell_2 = renderer = gtk_cell_renderer_pixbuf_new (); + g_object_set (G_OBJECT (renderer), "xalign", 0.0F, NULL); + gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), renderer, TRUE, FALSE); + gtk_cell_area_attribute_connect (area, renderer, "stock-id", SIMPLE_COLUMN_ICON); + + cell_3 = renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), + "wrap-mode", PANGO_WRAP_WORD, + "wrap-width", 215, + NULL); + gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), renderer, FALSE, TRUE); + gtk_cell_area_attribute_connect (area, renderer, "text", SIMPLE_COLUMN_DESCRIPTION); + + return scaffold; +} + +static void +orientation_changed (GtkComboBox *combo, + CellAreaScaffold *scaffold) +{ + GtkCellArea *area = cell_area_scaffold_get_area (scaffold); + GtkOrientation orientation = gtk_combo_box_get_active (combo); + + gtk_orientable_set_orientation (GTK_ORIENTABLE (area), orientation); +} + +static void +align_cell_2_toggled (GtkToggleButton *toggle, + CellAreaScaffold *scaffold) +{ + GtkCellArea *area = cell_area_scaffold_get_area (scaffold); + gboolean align = gtk_toggle_button_get_active (toggle); + + gtk_cell_area_cell_set (area, cell_2, "align", align, NULL); +} + +static void +align_cell_3_toggled (GtkToggleButton *toggle, + CellAreaScaffold *scaffold) +{ + GtkCellArea *area = cell_area_scaffold_get_area (scaffold); + gboolean align = gtk_toggle_button_get_active (toggle); + + gtk_cell_area_cell_set (area, cell_3, "align", align, NULL); +} + +static void +expand_cell_1_toggled (GtkToggleButton *toggle, + CellAreaScaffold *scaffold) +{ + GtkCellArea *area = cell_area_scaffold_get_area (scaffold); + gboolean expand = gtk_toggle_button_get_active (toggle); + + gtk_cell_area_cell_set (area, cell_1, "expand", expand, NULL); +} + +static void +expand_cell_2_toggled (GtkToggleButton *toggle, + CellAreaScaffold *scaffold) +{ + GtkCellArea *area = cell_area_scaffold_get_area (scaffold); + gboolean expand = gtk_toggle_button_get_active (toggle); + + gtk_cell_area_cell_set (area, cell_2, "expand", expand, NULL); +} + +static void +expand_cell_3_toggled (GtkToggleButton *toggle, + CellAreaScaffold *scaffold) +{ + GtkCellArea *area = cell_area_scaffold_get_area (scaffold); + gboolean expand = gtk_toggle_button_get_active (toggle); + + gtk_cell_area_cell_set (area, cell_3, "expand", expand, NULL); +} + +static void +simple_cell_area (void) +{ + GtkWidget *window, *widget; + GtkWidget *scaffold, *frame, *vbox, *hbox; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title (GTK_WINDOW (window), "CellArea expand and alignments"); + + scaffold = simple_scaffold (); + + hbox = gtk_hbox_new (FALSE, 4); + frame = gtk_frame_new (NULL); + gtk_widget_show (hbox); + gtk_widget_show (frame); + + gtk_widget_set_valign (frame, GTK_ALIGN_CENTER); + gtk_widget_set_halign (frame, GTK_ALIGN_FILL); + + gtk_container_add (GTK_CONTAINER (frame), scaffold); + + gtk_box_pack_end (GTK_BOX (hbox), frame, TRUE, TRUE, 0); + + /* Now add some controls */ + vbox = gtk_vbox_new (FALSE, 4); + gtk_widget_show (vbox); + gtk_box_pack_end (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Horizontal"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Vertical"); + gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "changed", + G_CALLBACK (orientation_changed), scaffold); + + widget = gtk_check_button_new_with_label ("Align 2nd Cell"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "toggled", + G_CALLBACK (align_cell_2_toggled), scaffold); + + widget = gtk_check_button_new_with_label ("Align 3rd Cell"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "toggled", + G_CALLBACK (align_cell_3_toggled), scaffold); + + + widget = gtk_check_button_new_with_label ("Expand 1st Cell"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "toggled", + G_CALLBACK (expand_cell_1_toggled), scaffold); + + widget = gtk_check_button_new_with_label ("Expand 2nd Cell"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "toggled", + G_CALLBACK (expand_cell_2_toggled), scaffold); + + widget = gtk_check_button_new_with_label ("Expand 3rd Cell"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "toggled", + G_CALLBACK (expand_cell_3_toggled), scaffold); + + gtk_container_add (GTK_CONTAINER (window), hbox); + + gtk_widget_show (window); +} + +/******************************************************* + * Focus Test * + *******************************************************/ +static GtkCellRenderer *focus_renderer, *sibling_renderer; + +enum { + FOCUS_COLUMN_NAME, + FOCUS_COLUMN_CHECK, + FOCUS_COLUMN_STATIC_TEXT, + N_FOCUS_COLUMNS +}; + +static GtkTreeModel * +focus_list_model (void) +{ + GtkTreeIter iter; + GtkListStore *store = + gtk_list_store_new (N_FOCUS_COLUMNS, + G_TYPE_STRING, /* name text */ + G_TYPE_BOOLEAN, /* check */ + G_TYPE_STRING); /* static text */ + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + FOCUS_COLUMN_NAME, "Enter a string", + FOCUS_COLUMN_CHECK, TRUE, + FOCUS_COLUMN_STATIC_TEXT, "Does it fly ?", + -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + FOCUS_COLUMN_NAME, "Enter a string", + FOCUS_COLUMN_CHECK, FALSE, + FOCUS_COLUMN_STATIC_TEXT, "Would you put it in a toaster ?", + -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + FOCUS_COLUMN_NAME, "Type something", + FOCUS_COLUMN_CHECK, FALSE, + FOCUS_COLUMN_STATIC_TEXT, "Does it feed on cute kittens ?", + -1); + + return (GtkTreeModel *)store; +} + +static void +cell_toggled (GtkCellRendererToggle *cell_renderer, + const gchar *path, + CellAreaScaffold *scaffold) +{ + GtkTreeModel *model = cell_area_scaffold_get_model (scaffold); + GtkTreeIter iter; + gboolean active; + + g_print ("Cell toggled !\n"); + + if (!gtk_tree_model_get_iter_from_string (model, &iter, path)) + return; + + gtk_tree_model_get (model, &iter, FOCUS_COLUMN_CHECK, &active, -1); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, FOCUS_COLUMN_CHECK, !active, -1); +} + +static void +cell_edited (GtkCellRendererToggle *cell_renderer, + const gchar *path, + const gchar *new_text, + CellAreaScaffold *scaffold) +{ + GtkTreeModel *model = cell_area_scaffold_get_model (scaffold); + GtkTreeIter iter; + + g_print ("Cell edited with new text '%s' !\n", new_text); + + if (!gtk_tree_model_get_iter_from_string (model, &iter, path)) + return; + + gtk_list_store_set (GTK_LIST_STORE (model), &iter, FOCUS_COLUMN_NAME, new_text, -1); +} + +static GtkWidget * +focus_scaffold (gboolean color_bg, GtkCellRenderer **focus, GtkCellRenderer **sibling) +{ + GtkTreeModel *model; + GtkWidget *scaffold; + GtkCellArea *area; + GtkCellRenderer *renderer, *toggle; + + scaffold = cell_area_scaffold_new (); + gtk_widget_show (scaffold); + + model = focus_list_model (); + + cell_area_scaffold_set_model (CELL_AREA_SCAFFOLD (scaffold), model); + + area = cell_area_scaffold_get_area (CELL_AREA_SCAFFOLD (scaffold)); + + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL); + gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), renderer, TRUE, FALSE); + gtk_cell_area_attribute_connect (area, renderer, "text", FOCUS_COLUMN_NAME); + + if (color_bg) + g_object_set (G_OBJECT (renderer), "cell-background", "red", NULL); + + g_signal_connect (G_OBJECT (renderer), "edited", + G_CALLBACK (cell_edited), scaffold); + + toggle = renderer = gtk_cell_renderer_toggle_new (); + g_object_set (G_OBJECT (renderer), "xalign", 0.0F, NULL); + gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), renderer, FALSE, TRUE); + gtk_cell_area_attribute_connect (area, renderer, "active", FOCUS_COLUMN_CHECK); + + if (color_bg) + g_object_set (G_OBJECT (renderer), "cell-background", "green", NULL); + + if (focus) + *focus = renderer; + + g_signal_connect (G_OBJECT (renderer), "toggled", + G_CALLBACK (cell_toggled), scaffold); + + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), + "wrap-mode", PANGO_WRAP_WORD, + "wrap-width", 150, + NULL); + + if (color_bg) + g_object_set (G_OBJECT (renderer), "cell-background", "blue", NULL); + + if (sibling) + *sibling = renderer; + + gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), renderer, FALSE, TRUE); + gtk_cell_area_attribute_connect (area, renderer, "text", FOCUS_COLUMN_STATIC_TEXT); + + gtk_cell_area_add_focus_sibling (area, toggle, renderer); + + return scaffold; +} + +static void +focus_sibling_toggled (GtkToggleButton *toggle, + CellAreaScaffold *scaffold) +{ + GtkCellArea *area = cell_area_scaffold_get_area (scaffold); + gboolean active = gtk_toggle_button_get_active (toggle); + + if (active) + gtk_cell_area_add_focus_sibling (area, focus_renderer, sibling_renderer); + else + gtk_cell_area_remove_focus_sibling (area, focus_renderer, sibling_renderer); + + gtk_widget_queue_draw (GTK_WIDGET (scaffold)); +} + + +static void +focus_cell_area (void) +{ + GtkWidget *window, *widget; + GtkWidget *scaffold, *frame, *vbox, *hbox; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + hbox = gtk_hbox_new (FALSE, 4); + gtk_widget_show (hbox); + + gtk_window_set_title (GTK_WINDOW (window), "Focus and editable cells"); + + scaffold = focus_scaffold (FALSE, &focus_renderer, &sibling_renderer); + + frame = gtk_frame_new (NULL); + gtk_widget_show (frame); + + gtk_widget_set_valign (frame, GTK_ALIGN_CENTER); + gtk_widget_set_halign (frame, GTK_ALIGN_FILL); + + gtk_container_add (GTK_CONTAINER (frame), scaffold); + + gtk_box_pack_end (GTK_BOX (hbox), frame, TRUE, TRUE, 0); + + /* Now add some controls */ + vbox = gtk_vbox_new (FALSE, 4); + gtk_widget_show (vbox); + gtk_box_pack_end (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Horizontal"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Vertical"); + gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "changed", + G_CALLBACK (orientation_changed), scaffold); + + widget = gtk_check_button_new_with_label ("Focus Sibling"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "toggled", + G_CALLBACK (focus_sibling_toggled), scaffold); + + gtk_container_add (GTK_CONTAINER (window), hbox); + + gtk_widget_show (window); +} + + + +/******************************************************* + * Background Area * + *******************************************************/ +static void +cell_spacing_changed (GtkSpinButton *spin_button, + CellAreaScaffold *scaffold) +{ + GtkCellArea *area = cell_area_scaffold_get_area (scaffold); + gint value; + + value = (gint)gtk_spin_button_get_value (spin_button); + + gtk_cell_area_box_set_spacing (GTK_CELL_AREA_BOX (area), value); +} + +static void +row_spacing_changed (GtkSpinButton *spin_button, + CellAreaScaffold *scaffold) +{ + gint value; + + value = (gint)gtk_spin_button_get_value (spin_button); + + cell_area_scaffold_set_row_spacing (scaffold, value); +} + +static void +indentation_changed (GtkSpinButton *spin_button, + CellAreaScaffold *scaffold) +{ + gint value; + + value = (gint)gtk_spin_button_get_value (spin_button); + + cell_area_scaffold_set_indentation (scaffold, value); +} + +static void +background_area (void) +{ + GtkWidget *window, *widget, *label, *main_vbox; + GtkWidget *scaffold, *frame, *vbox, *hbox; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + hbox = gtk_hbox_new (FALSE, 4); + main_vbox = gtk_vbox_new (FALSE, 4); + gtk_widget_show (hbox); + gtk_widget_show (main_vbox); + gtk_container_add (GTK_CONTAINER (window), main_vbox); + + gtk_window_set_title (GTK_WINDOW (window), "Background Area"); + + label = gtk_label_new ("In this example, row spacing gets devided into the background area, " + "column spacing is added between each background area, indentation is " + "prepended space distributed to the background area."); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_label_set_width_chars (GTK_LABEL (label), 40); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (main_vbox), label, FALSE, FALSE, 0); + + scaffold = focus_scaffold (TRUE, NULL, NULL); + + frame = gtk_frame_new (NULL); + gtk_widget_show (frame); + + gtk_widget_set_valign (frame, GTK_ALIGN_CENTER); + gtk_widget_set_halign (frame, GTK_ALIGN_FILL); + + gtk_container_add (GTK_CONTAINER (frame), scaffold); + + gtk_box_pack_end (GTK_BOX (hbox), frame, TRUE, TRUE, 0); + + /* Now add some controls */ + vbox = gtk_vbox_new (FALSE, 4); + gtk_widget_show (vbox); + gtk_box_pack_end (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0); + + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Horizontal"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Vertical"); + gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "changed", + G_CALLBACK (orientation_changed), scaffold); + + widget = gtk_spin_button_new_with_range (0, 10, 1); + label = gtk_label_new ("Cell spacing"); + hbox = gtk_hbox_new (FALSE, 4); + gtk_widget_show (hbox); + gtk_widget_show (label); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "value-changed", + G_CALLBACK (cell_spacing_changed), scaffold); + + + widget = gtk_spin_button_new_with_range (0, 10, 1); + label = gtk_label_new ("Row spacing"); + hbox = gtk_hbox_new (FALSE, 4); + gtk_widget_show (hbox); + gtk_widget_show (label); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "value-changed", + G_CALLBACK (row_spacing_changed), scaffold); + + widget = gtk_spin_button_new_with_range (0, 30, 1); + label = gtk_label_new ("Intentation"); + hbox = gtk_hbox_new (FALSE, 4); + gtk_widget_show (hbox); + gtk_widget_show (label); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + g_signal_connect (G_OBJECT (widget), "value-changed", + G_CALLBACK (indentation_changed), scaffold); + + gtk_widget_show (window); +} + + + + + + +int +main (int argc, char *argv[]) +{ + gtk_init (NULL, NULL); + + if (g_getenv ("RTL")) + gtk_widget_set_default_direction (GTK_TEXT_DIR_RTL); + + simple_cell_area (); + focus_cell_area (); + background_area (); + + gtk_main (); + + return 0; +} diff --git a/tests/testtooltips.c b/tests/testtooltips.c index 54c2e05b88..483b8fd2a3 100644 --- a/tests/testtooltips.c +++ b/tests/testtooltips.c @@ -359,7 +359,7 @@ main (int argc, char *argv[]) /* Set a tooltip on the column */ column = gtk_tree_view_get_column (GTK_TREE_VIEW (tree_view), 0); gtk_tree_view_column_set_clickable (column, TRUE); - g_object_set (column->button, "tooltip-text", "Header", NULL); + g_object_set (gtk_tree_view_column_get_button (column), "tooltip-text", "Header", NULL); gtk_box_pack_start (GTK_BOX (box), tree_view, FALSE, FALSE, 2); diff --git a/tests/testtreeedit.c b/tests/testtreeedit.c index 4375691a08..01e1f2bbe7 100644 --- a/tests/testtreeedit.c +++ b/tests/testtreeedit.c @@ -31,6 +31,7 @@ enum { STRING_COLUMN, IS_EDITABLE_COLUMN, PIXBUF_COLUMN, + LAST_PIXBUF_COLUMN, PROGRESS_COLUMN, NUM_COLUMNS }; @@ -51,17 +52,19 @@ create_model (void) GtkTreeStore *model; GtkTreeIter iter; gint i; - GdkPixbuf *foo; + GdkPixbuf *foo, *bar; GtkWidget *blah; blah = gtk_window_new (GTK_WINDOW_TOPLEVEL); foo = gtk_widget_render_icon_pixbuf (blah, GTK_STOCK_NEW, GTK_ICON_SIZE_MENU); + bar = gtk_widget_render_icon_pixbuf (blah, GTK_STOCK_DELETE, GTK_ICON_SIZE_MENU); gtk_widget_destroy (blah); model = gtk_tree_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, + GDK_TYPE_PIXBUF, G_TYPE_INT); for (i = 0; model_strings[i].string != NULL; i++) @@ -72,6 +75,7 @@ create_model (void) STRING_COLUMN, model_strings[i].string, IS_EDITABLE_COLUMN, model_strings[i].is_editable, PIXBUF_COLUMN, foo, + LAST_PIXBUF_COLUMN, bar, PROGRESS_COLUMN, model_strings[i].progress, -1); } @@ -128,15 +132,65 @@ button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer callback_ return FALSE; } +typedef struct { + GtkCellArea *area; + GtkCellRenderer *renderer; +} CallbackData; + +static void +align_cell_toggled (GtkToggleButton *toggle, + CallbackData *data) +{ + gboolean active = gtk_toggle_button_get_active (toggle); + + gtk_cell_area_cell_set (data->area, data->renderer, "align", active, NULL); +} + +static void +expand_cell_toggled (GtkToggleButton *toggle, + CallbackData *data) +{ + gboolean active = gtk_toggle_button_get_active (toggle); + + gtk_cell_area_cell_set (data->area, data->renderer, "expand", active, NULL); +} + +static void +create_control (GtkWidget *box, gint number, gboolean align, CallbackData *data) +{ + GtkWidget *checkbutton; + gchar *name; + + if (align) + name = g_strdup_printf ("Align Cell #%d", number); + else + name = g_strdup_printf ("Expand Cell #%d", number); + + checkbutton = gtk_check_button_new_with_label (name); + gtk_widget_show (checkbutton); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton), align); + gtk_box_pack_start (GTK_BOX (box), checkbutton, FALSE, FALSE, 0); + + if (align) + g_signal_connect (G_OBJECT (checkbutton), "toggled", + G_CALLBACK (align_cell_toggled), data); + else + g_signal_connect (G_OBJECT (checkbutton), "toggled", + G_CALLBACK (expand_cell_toggled), data); +} + gint main (gint argc, gchar **argv) { GtkWidget *window; GtkWidget *scrolled_window; GtkWidget *tree_view; + GtkWidget *vbox, *hbox, *cntl_vbox; GtkTreeModel *tree_model; GtkCellRenderer *renderer; GtkTreeViewColumn *column; + GtkCellArea *area; + CallbackData callback[4]; gtk_init (&argc, &argv); @@ -147,10 +201,15 @@ main (gint argc, gchar **argv) gtk_window_set_title (GTK_WINDOW (window), "GtkTreeView editing sample"); g_signal_connect (window, "destroy", gtk_main_quit, NULL); + vbox = gtk_vbox_new (FALSE, 6); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (window), vbox); + scrolled_window = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_ETCHED_IN); - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_container_add (GTK_CONTAINER (window), scrolled_window); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 0); tree_model = create_model (); tree_view = gtk_tree_view_new_with_model (tree_model); @@ -160,33 +219,47 @@ main (gint argc, gchar **argv) column = gtk_tree_view_column_new (); gtk_tree_view_column_set_title (column, "String"); + area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (column)); renderer = gtk_cell_renderer_pixbuf_new (); - gtk_tree_view_column_pack_start (column, renderer, TRUE); + gtk_tree_view_column_pack_start (column, renderer, FALSE); gtk_tree_view_column_set_attributes (column, renderer, "pixbuf", PIXBUF_COLUMN, NULL); + callback[0].area = area; + callback[0].renderer = renderer; renderer = gtk_cell_renderer_text_new (); - gtk_tree_view_column_pack_start (column, renderer, TRUE); + gtk_tree_view_column_pack_start (column, renderer, FALSE); gtk_tree_view_column_set_attributes (column, renderer, "text", STRING_COLUMN, "editable", IS_EDITABLE_COLUMN, NULL); + callback[1].area = area; + callback[1].renderer = renderer; g_signal_connect (renderer, "edited", G_CALLBACK (edited), tree_model); + renderer = gtk_cell_renderer_text_new (); - gtk_tree_view_column_pack_start (column, renderer, TRUE); + gtk_tree_view_column_pack_start (column, renderer, FALSE); gtk_tree_view_column_set_attributes (column, renderer, "text", STRING_COLUMN, "editable", IS_EDITABLE_COLUMN, NULL); + callback[2].area = area; + callback[2].renderer = renderer; g_signal_connect (renderer, "edited", G_CALLBACK (edited), tree_model); renderer = gtk_cell_renderer_pixbuf_new (); - gtk_tree_view_column_pack_start (column, renderer, TRUE); + g_object_set (renderer, + "xalign", 0.0, + NULL); + gtk_tree_view_column_pack_start (column, renderer, FALSE); gtk_tree_view_column_set_attributes (column, renderer, - "pixbuf", PIXBUF_COLUMN, NULL); + "pixbuf", LAST_PIXBUF_COLUMN, NULL); + callback[3].area = area; + callback[3].renderer = renderer; + gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column); renderer = gtk_cell_renderer_toggle_new (); @@ -212,7 +285,31 @@ main (gint argc, gchar **argv) gtk_container_add (GTK_CONTAINER (scrolled_window), tree_view); gtk_window_set_default_size (GTK_WINDOW (window), - 800, 175); + 800, 250); + + hbox = gtk_hbox_new (FALSE, 6); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + /* Alignment controls */ + cntl_vbox = gtk_vbox_new (FALSE, 2); + gtk_widget_show (cntl_vbox); + gtk_box_pack_start (GTK_BOX (hbox), cntl_vbox, FALSE, FALSE, 0); + + create_control (cntl_vbox, 1, TRUE, &callback[0]); + create_control (cntl_vbox, 2, TRUE, &callback[1]); + create_control (cntl_vbox, 3, TRUE, &callback[2]); + create_control (cntl_vbox, 4, TRUE, &callback[3]); + + /* Expand controls */ + cntl_vbox = gtk_vbox_new (FALSE, 2); + gtk_widget_show (cntl_vbox); + gtk_box_pack_start (GTK_BOX (hbox), cntl_vbox, FALSE, FALSE, 0); + + create_control (cntl_vbox, 1, FALSE, &callback[0]); + create_control (cntl_vbox, 2, FALSE, &callback[1]); + create_control (cntl_vbox, 3, FALSE, &callback[2]); + create_control (cntl_vbox, 4, FALSE, &callback[3]); gtk_widget_show_all (window); gtk_main (); diff --git a/tests/testtreeview.c b/tests/testtreeview.c index d28cb3520a..80efc90fb8 100644 --- a/tests/testtreeview.c +++ b/tests/testtreeview.c @@ -351,7 +351,7 @@ set_columns_type (GtkTreeView *tree_view, ColumnsType type) gtk_tree_view_set_rules_hint (tree_view, TRUE); rend = gtk_cell_renderer_text_new (); - + col = gtk_tree_view_column_new_with_attributes ("Column 1", rend, "text", 1, @@ -709,6 +709,9 @@ main (int argc, gtk_init (&argc, &argv); + if (g_getenv ("RTL")) + gtk_widget_set_default_direction (GTK_TEXT_DIR_RTL); + our_pixbuf = gdk_pixbuf_new_from_xpm_data ((const char **) book_closed_xpm); #if 0 diff --git a/tests/testverticalcells.c b/tests/testverticalcells.c new file mode 100644 index 0000000000..e1a522d8d8 --- /dev/null +++ b/tests/testverticalcells.c @@ -0,0 +1,378 @@ +/* testverticalcells.c + * + * Copyright (C) 2010 Openismus GmbH + * + * Authors: + * Tristan Van Berkom <tristanvb@openismus.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include <gtk/gtk.h> + +typedef struct _TreeEntry TreeEntry; + +struct _TreeEntry { + const gchar *icon; + const gchar *info; + const gchar *description; + const gchar *fine_print; + const gchar *fine_print_color; + gint progress; + TreeEntry *entries; +}; + +enum { + ICON_COLUMN, + INFO_COLUMN, + DESCRIPTION_COLUMN, + FINE_PRINT_COLUMN, + FINE_PRINT_COLOR_COLUMN, + PROGRESS_COLUMN, + NUM_COLUMNS +}; + + +static TreeEntry info_entries[] = + { + { + "gtk-execute", + "Will you\n" + "run this ?", + "Currently executing that thing... you might want to abort", + "and every day he went fishing for pigs in the sky", + "green", + 83, + NULL + }, + { + "gtk-dialog-authentication", + "This is the\n" + "realest of the real", + "We are about to authenticate the actual realness, this could take some time", + "one day he caught a giant ogre who barked and barked and barked", + "purple", + 4, + NULL + }, + { 0, }, + }; + +static TreeEntry directory_entries[] = + { + { + "gtk-edit", + "We can edit\n" + "things in here", + "Time to edit your directory, almost finished now", + "she thought the best remedy for daydreams was going to be sleep", + "dark sea green", + 99, + NULL + }, + { + "gtk-file", + "You have a\n" + "file here", + "Who would of thought there would be a file in the directory ?", + "after taking loads of sleeping pills he could still hear the pigs barking", + "green yellow", + 33, + NULL + }, + { + "gtk-dialog-question", + "Any questions ?", + "This file would like to ask you a question", + "so he decided that the fine print underneath the progress bar probably made no sense at all", + "lavender", + 73, + NULL + }, + { 0, }, + }; + +static TreeEntry other_entries[] = + { + { + "gtk-zoom-fit", + "Thats the\n" + "perfect fit", + "Now fitting foo into bar using frobnicator", + "using his nifty wide angle lense, he was able to catch a 'dark salmon', it was no flying pig " + "however it was definitely a keeper", + "dark salmon", + 59, + NULL + }, + { + "gtk-underline", + "Under the\n" + "line", + "Now underlining that this demo would look alot niftier with some real content", + "it was indeed strange to catch a red salmon while fishing for pigs in the deep sky blue.", + "deep sky blue", + 99, + NULL + }, + }; + +static TreeEntry add_entries[] = + { + { + "gtk-about", + "its about\n" + "to start", + "This is what it's all about", + "so he went ahead and added the 'gtk-about' icon to his story, thinking 'mint cream' would be the " + "right color for something like that", + "dark violet", + 1, + NULL + }, + { + "gtk-zoom-in", + "Watch it\n" + "Zoom !", + "Now zooming into something", + "while fishing for pigs in the sky, maybe he would have caught something faster if only he had zoomed in", + "orchid", + 6, + NULL + }, + { + "gtk-zoom-out", + "Zoom Zoom\n" + "Zoom !", + "Now zooming out of something else", + "the daydream had a much prettier picture over all once he had zoomed out for the wide angle, " + "jill agreed", + "dark magenta", + 46, + other_entries + }, + { 0, }, + }; + + +static TreeEntry main_entries[] = + { + { + "gtk-info", + "This is all\n" + "the info", + "We are currently informing you", + "once upon a time in a land far far away there was a guy named buba", + "red", + 64, + info_entries + }, + { + "gtk-dialog-warning", + "This is a\n" + "warning", + "We would like to warn you that your laptop might explode after we're done", + "so he decided that he must be stark raving mad", + "orange", + 43, + NULL + }, + { + "gtk-dialog-error", + "An error will\n" + "occur", + "Once we're done here, dont worry... an error will surely occur.", + "and went to a see a yellow physiotherapist who's name was jill", + "yellow", + 98, + NULL + }, + { + "gtk-directory", + "The directory", + "Currently scanning your directories.", + "jill didnt know what to make of the barking pigs either so she fed him sleeping pills", + "brown", + 20, + directory_entries + }, + { + "gtk-delete", + "Now deleting\n" + "the whole thing", + "Time to delete the sucker", + "and he decided to just delete the whole conversation since it didnt make sense to him", + "dark orange", + 26, + NULL + }, + { + "gtk-add", + "Anything\n" + "to add ?", + "Now adding stuff... please be patient", + "but on second thought, maybe he had something to add so that things could make a little less sense.", + "maroon", + 72, + add_entries + }, + { + "gtk-redo", + "Time to\n" + "do it again", + "For the hell of it, lets add the content to the treeview over and over again !", + "buba likes telling his story, so maybe he's going to tell it 20 times more.", + "deep pink", + 100, + NULL + }, + { 0, }, + }; + + +static void +populate_model (GtkTreeStore *model, + GtkTreeIter *parent, + TreeEntry *entries) +{ + GtkTreeIter iter; + gint i; + + for (i = 0; entries[i].info != NULL; i++) + { + gtk_tree_store_append (model, &iter, parent); + gtk_tree_store_set (model, &iter, + ICON_COLUMN, entries[i].icon, + INFO_COLUMN, entries[i].info, + DESCRIPTION_COLUMN, entries[i].description, + FINE_PRINT_COLUMN, entries[i].fine_print, + FINE_PRINT_COLOR_COLUMN, entries[i].fine_print_color, + PROGRESS_COLUMN, entries[i].progress, + -1); + + if (entries[i].entries) + populate_model (model, &iter, entries[i].entries); + } +} + +static GtkTreeModel * +create_model (void) +{ + GtkTreeStore *model; + gint i; + + model = gtk_tree_store_new (NUM_COLUMNS, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_INT); + + for (i = 0; i < 20; i++) + populate_model (model, NULL, main_entries); + + return GTK_TREE_MODEL (model); +} + +gint +main (gint argc, gchar **argv) +{ + GtkWidget *window; + GtkWidget *scrolled_window; + GtkWidget *tree_view; + GtkTreeModel *tree_model; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkCellArea *area; + + gtk_init (&argc, &argv); + + if (g_getenv ("RTL")) + gtk_widget_set_default_direction (GTK_TEXT_DIR_RTL); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Vertical cells in GtkTreeViewColumn example"); + g_signal_connect (window, "destroy", gtk_main_quit, NULL); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_ETCHED_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_container_add (GTK_CONTAINER (window), scrolled_window); + + tree_model = create_model (); + tree_view = gtk_tree_view_new_with_model (tree_model); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree_view), FALSE); + + /* First column */ + column = gtk_tree_view_column_new (); + + renderer = gtk_cell_renderer_pixbuf_new (); + g_object_set (renderer, "stock-size", GTK_ICON_SIZE_DIALOG, NULL); + gtk_tree_view_column_pack_start (column, renderer, TRUE); + gtk_tree_view_column_set_attributes (column, renderer, + "stock-id", ICON_COLUMN, NULL); + + renderer = gtk_cell_renderer_text_new (); + g_object_set (renderer, "scale", 1.2F, "weight", PANGO_WEIGHT_BOLD, NULL); + gtk_tree_view_column_pack_start (column, renderer, TRUE); + gtk_tree_view_column_set_attributes (column, renderer, + "text", INFO_COLUMN, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column); + + /* Second (vertical) column */ + column = gtk_tree_view_column_new (); + area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (column)); + gtk_orientable_set_orientation (GTK_ORIENTABLE (area), GTK_ORIENTATION_VERTICAL); + + renderer = gtk_cell_renderer_text_new (); + g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, "editable", TRUE, NULL); + gtk_tree_view_column_pack_start (column, renderer, TRUE); + gtk_tree_view_column_set_attributes (column, renderer, + "text", DESCRIPTION_COLUMN, + NULL); + + renderer = gtk_cell_renderer_progress_new (); + gtk_tree_view_column_pack_start (column, renderer, TRUE); + gtk_tree_view_column_set_attributes (column, renderer, + "value", PROGRESS_COLUMN, + NULL); + + renderer = gtk_cell_renderer_text_new (); + g_object_set (renderer, "scale", 0.6F, "ellipsize", PANGO_ELLIPSIZE_END, NULL); + gtk_tree_view_column_pack_start (column, renderer, TRUE); + gtk_tree_view_column_set_attributes (column, renderer, + "text", FINE_PRINT_COLUMN, + "foreground", FINE_PRINT_COLOR_COLUMN, + NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column); + + gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view)); + + gtk_container_add (GTK_CONTAINER (scrolled_window), tree_view); + + gtk_window_set_default_size (GTK_WINDOW (window), + 800, 400); + + gtk_widget_show_all (window); + gtk_main (); + + return 0; +} |