LCOV - code coverage report
Current view: top level - malcontent-control - cc-bar-chart.c (source / functions) Coverage Total Hit
Test: 2 coverage DB files Lines: 0.0 % 683 0
Test Date: 2025-10-05 20:47:42 Functions: 0.0 % 43 0
Branches: 0.0 % 405 0

             Branch data     Line data    Source code
       1                 :             : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
       2                 :             : /*
       3                 :             :  * Copyright 2024 GNOME Foundation, Inc.
       4                 :             :  *
       5                 :             :  * This library is free software; you can redistribute it and/or
       6                 :             :  * modify it under the terms of the GNU Lesser General Public
       7                 :             :  * License as published by the Free Software Foundation; either
       8                 :             :  * version 2 of the License, or (at your option) any later version.
       9                 :             :  *
      10                 :             :  * This library is distributed in the hope that it will be useful,
      11                 :             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      12                 :             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13                 :             :  * Lesser General Public License for more details.
      14                 :             :  *
      15                 :             :  * You should have received a copy of the GNU Lesser General
      16                 :             :  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
      17                 :             :  *
      18                 :             :  * Authors:
      19                 :             :  *  - Philip Withnall <pwithnall@gnome.org>
      20                 :             :  *
      21                 :             :  * SPDX-License-Identifier: GPL-3.0-or-later
      22                 :             :  */
      23                 :             : 
      24                 :             : #include <adwaita.h>
      25                 :             : #include <glib.h>
      26                 :             : #include <glib-object.h>
      27                 :             : #include <gsk/gsk.h>
      28                 :             : #include <gtk/gtk.h>
      29                 :             : #include <math.h>
      30                 :             : 
      31                 :             : #include "cc-bar-chart.h"
      32                 :             : #include "cc-bar-chart-bar.h"
      33                 :             : #include "cc-bar-chart-group.h"
      34                 :             : 
      35                 :             : /**
      36                 :             :  * CcBarChart:
      37                 :             :  *
      38                 :             :  * #CcBarChart is a widget for displaying a
      39                 :             :  * [bar chart](https://en.wikipedia.org/wiki/Bar_chart).
      40                 :             :  *
      41                 :             :  * It currently supports vertical bar charts, with a horizontal discrete axis
      42                 :             :  * and a vertical continuous axis. It supports single (non-grouped) bars, and a
      43                 :             :  * single colour scheme. It does not support negative data values.
      44                 :             :  * These limitations may change in future.
      45                 :             :  *
      46                 :             :  * The labels on the discrete axis can be set using
      47                 :             :  * cc_bar_chart_set_discrete_axis_labels(). You must localise these before
      48                 :             :  * setting them. The number of discrete axis labels must match the number of
      49                 :             :  * data elements set using cc_bar_chart_set_data().
      50                 :             :  *
      51                 :             :  * The widget’s appearance is undefined until data is provided to it via
      52                 :             :  * cc_bar_chart_set_data(). If you need to present a placeholder if no data is
      53                 :             :  * available, it’s recommended to place the #CcBarChart in a #GtkStack with the
      54                 :             :  * placeholder widgets in another page of the stack. Placeholders could be an
      55                 :             :  * image, text or spinner.
      56                 :             :  *
      57                 :             :  * The labels on the continuous axis are constructed dynamically to match the
      58                 :             :  * grid lines. Provide a grid line generator callback using
      59                 :             :  * cc_bar_chart_set_continuous_axis_grid_line_callback(), and provide a
      60                 :             :  * labelling callback using cc_bar_chart_set_continuous_axis_label_callback().
      61                 :             :  *
      62                 :             :  * An overlay line may be rendered to indicate a target or average. Set its
      63                 :             :  * value using cc_bar_chart_set_overlay_line_value();
      64                 :             :  *
      65                 :             :  * Bars in the chart may be selected, and the currently selected bar is
      66                 :             :  * available as #CcBarChart:selected-index. Bars may also be activated,
      67                 :             :  * resulting in #CcBarChart:bar-activated being emitted. By default, activating
      68                 :             :  * a bar will also focus and select it.
      69                 :             :  *
      70                 :             :  * # Shortcuts and Gestures
      71                 :             :  *
      72                 :             :  * The following signals have default keybindings:
      73                 :             :  *
      74                 :             :  * - #CcBarChart::move-cursor
      75                 :             :  *
      76                 :             :  * # CSS nodes
      77                 :             :  *
      78                 :             :  * |[<!-- language="plain" -->
      79                 :             :  * bar-chart
      80                 :             :  * ├── label.discrete-axis-label
      81                 :             :  * ├── label.continuous-axis-label
      82                 :             :  * ╰── bar-group[:hover][:selected]
      83                 :             :  *     ╰── bar[:hover][:selected]
      84                 :             :  * ]|
      85                 :             :  *
      86                 :             :  * #CcBarChart uses a single CSS node named `bar-chart`. Each bar group is a
      87                 :             :  * sub-node named `bar-group`, with `bar` sub-nodes beneath it. Bars and groups
      88                 :             :  * may have `:hover` or `:selected` pseudo-selectors to indicate whether they
      89                 :             :  * are selected or being hovered over with the mouse. Axis labels are `label`
      90                 :             :  * sub-nodes, with either a `.discrete-axis-label` or `.continuous-axis-label`
      91                 :             :  * class.
      92                 :             :  *
      93                 :             :  * # Accessibility
      94                 :             :  *
      95                 :             :  * #CcBarChart uses the %GTK_ACCESSIBLE_ROLE_LIST role, with its groups using
      96                 :             :  * the %GTK_ACCESSIBLE_ROLE_GROUP role and bars using the
      97                 :             :  * %GTK_ACCESSIBLE_ROLE_LIST_ITEM role.
      98                 :             :  *
      99                 :             :  * This allows access technologies to interpret the chart as if it were a table
     100                 :             :  * of data. You should, however, specify a description for the chart as a whole,
     101                 :             :  * which also includes the semantics and value of
     102                 :             :  * #CcBarChart:overlay-line-value (if set).
     103                 :             :  *
     104                 :             :  * For example:
     105                 :             :  * |[
     106                 :             :  *   gtk_accessible_update_property (GTK_ACCESSIBLE (bar_chart),
     107                 :             :  *                                   GTK_ACCESSIBLE_PROPERTY_DESCRIPTION,
     108                 :             :  *                                   _("Bar chart of screen time usage over "
     109                 :             :  *                                     "the week starting 2nd February 2024. A "
     110                 :             :  *                                     "line is overlayed at the 10 hour mark "
     111                 :             :  *                                     "to indicate the configured screen time "
     112                 :             :  *                                     "limit."),
     113                 :             :  *                                   -1);
     114                 :             :  * ]|
     115                 :             :  *
     116                 :             :  * # Text direction
     117                 :             :  *
     118                 :             :  * If the widget text direction is changed (see gtk_widget_set_direction()),
     119                 :             :  * the discrete axis will be reversed and the continuous axis will be drawn on
     120                 :             :  * the opposite side of the graph from normal.
     121                 :             :  *
     122                 :             :  * For discrete axes which represent the passage of time, this is correct. For
     123                 :             :  * discrete axes which represent unordered categories, reversing the axis is
     124                 :             :  * not necessary and can be disabled by explicitly setting the text direction
     125                 :             :  * of the widget using gtk_widget_set_direction().
     126                 :             :  *
     127                 :             :  * See the [Material design guidelines](https://m2.material.io/design/usability/bidirectionality.html#mirroring-elements)
     128                 :             :  * or the [GNOME HIG](https://gitlab.gnome.org/Teams/Websites/developer.gnome.org-hig/-/issues/61)
     129                 :             :  * for guidance about when charts should be mirrored.
     130                 :             :  */
     131                 :             : struct _CcBarChart {
     132                 :             :   GtkWidget parent_instance;
     133                 :             : 
     134                 :             :   /* Configured state: */
     135                 :             :   double *data;  /* (nullable) (owned) */
     136                 :             :   size_t n_data;
     137                 :             : 
     138                 :             :   char **discrete_axis_labels;  /* (nullable) (array zero-terminated=1) */
     139                 :             :   size_t n_discrete_axis_labels;  /* cached result of g_strv_length(discrete_axis_labels) */
     140                 :             : 
     141                 :             :   CcBarChartLabelCallback continuous_axis_label_callback;
     142                 :             :   void *continuous_axis_label_user_data;
     143                 :             :   GDestroyNotify continuous_axis_label_destroy_notify;
     144                 :             : 
     145                 :             :   CcBarChartGridLineCallback continuous_axis_grid_line_callback;
     146                 :             :   void *continuous_axis_grid_line_user_data;
     147                 :             :   GDestroyNotify continuous_axis_grid_line_destroy_notify;
     148                 :             : 
     149                 :             :   gboolean selected_index_set;
     150                 :             :   unsigned int selected_index;  /* undefined if !selected_index_set */
     151                 :             : 
     152                 :             :   double overlay_line_value;
     153                 :             : 
     154                 :             :   /* Layout and rendering cache. See cc-bar-chart-diagram.svg for a rough sketch
     155                 :             :    * of how these child widgets and lengths fit into the overall widget. */
     156                 :             :   GPtrArray *cached_discrete_axis_labels;  /* (owned) (nullable) (element-type GtkLabel), always indexed the same as data */
     157                 :             :   GPtrArray *cached_continuous_axis_labels;  /* (owned) (nullable) (element-type GtkLabel), always indexed the same as cached_continuous_axis_grid_line_values */
     158                 :             :   GArray *cached_continuous_axis_grid_line_values;  /* (owned) (nullable) (element-type double), always indexed the same as cached_continuous_axis_labels */
     159                 :             :   GPtrArray *cached_groups;  /* (owned) (nullable) (element-type CcBarChartGroup), always indexed the same as data */
     160                 :             :   int cached_continuous_axis_area_width;
     161                 :             :   int cached_continuous_axis_label_height;
     162                 :             :   int cached_discrete_axis_area_height;
     163                 :             :   int cached_discrete_axis_baseline;  /* may be -1 if baseline is undefined */
     164                 :             :   double cached_pixels_per_data;  /* > 0.0 */
     165                 :             :   int cached_minimum_group_width;
     166                 :             :   unsigned int cached_continuous_axis_label_collision_modulus;
     167                 :             : };
     168                 :             : 
     169   [ #  #  #  #  :           0 : G_DEFINE_TYPE (CcBarChart, cc_bar_chart, GTK_TYPE_WIDGET)
                   #  # ]
     170                 :             : 
     171                 :             : typedef enum {
     172                 :             :   PROP_SELECTED_INDEX = 1,
     173                 :             :   PROP_SELECTED_INDEX_SET,
     174                 :             :   PROP_OVERLAY_LINE_VALUE,
     175                 :             :   PROP_DISCRETE_AXIS_LABELS,
     176                 :             : } CcBarChartProperty;
     177                 :             : 
     178                 :             : static GParamSpec *props[PROP_DISCRETE_AXIS_LABELS + 1];
     179                 :             : 
     180                 :             : typedef enum {
     181                 :             :   SIGNAL_DATA_CHANGED,
     182                 :             :   SIGNAL_BAR_ACTIVATED,
     183                 :             :   SIGNAL_ACTIVATE_CURSOR_BAR,
     184                 :             :   SIGNAL_MOVE_CURSOR,
     185                 :             : } CcBarChartSignal;
     186                 :             : 
     187                 :             : static guint signals[SIGNAL_MOVE_CURSOR + 1];
     188                 :             : 
     189                 :             : static void cc_bar_chart_get_property (GObject    *object,
     190                 :             :                                        guint       property_id,
     191                 :             :                                        GValue     *value,
     192                 :             :                                        GParamSpec *pspec);
     193                 :             : static void cc_bar_chart_set_property (GObject      *object,
     194                 :             :                                        guint         property_id,
     195                 :             :                                        const GValue *value,
     196                 :             :                                        GParamSpec   *pspec);
     197                 :             : static void cc_bar_chart_dispose (GObject *object);
     198                 :             : static void cc_bar_chart_finalize (GObject *object);
     199                 :             : static void cc_bar_chart_size_allocate (GtkWidget *widget,
     200                 :             :                                         int        width,
     201                 :             :                                         int        height,
     202                 :             :                                         int        baseline);
     203                 :             : static void cc_bar_chart_measure (GtkWidget      *widget,
     204                 :             :                                   GtkOrientation  orientation,
     205                 :             :                                   int             for_size,
     206                 :             :                                   int            *minimum,
     207                 :             :                                   int            *natural,
     208                 :             :                                   int            *minimum_baseline,
     209                 :             :                                   int            *natural_baseline);
     210                 :             : static void cc_bar_chart_snapshot (GtkWidget   *widget,
     211                 :             :                                    GtkSnapshot *snapshot);
     212                 :             : static gboolean cc_bar_chart_focus (GtkWidget        *widget,
     213                 :             :                                     GtkDirectionType  direction);
     214                 :             : 
     215                 :             : static void activate_cursor_bar_cb (CcBarChart *self,
     216                 :             :                                     gpointer    user_data);
     217                 :             : static void move_cursor_cb (CcBarChart      *self,
     218                 :             :                             GtkMovementStep  step,
     219                 :             :                             int              count);
     220                 :             : 
     221                 :             : static gboolean find_index_for_group (CcBarChart      *self,
     222                 :             :                                       CcBarChartGroup *group,
     223                 :             :                                       unsigned int    *out_idx);
     224                 :             : static gboolean find_index_for_bar (CcBarChart    *self,
     225                 :             :                                     CcBarChartBar *bar,
     226                 :             :                                     unsigned int  *out_idx);
     227                 :             : static CcBarChartGroup *get_adjacent_focusable_group (CcBarChart      *self,
     228                 :             :                                                       CcBarChartGroup *group,
     229                 :             :                                                       int              direction);
     230                 :             : static CcBarChartGroup *get_first_focusable_group (CcBarChart *self);
     231                 :             : static CcBarChartGroup *get_last_focusable_group (CcBarChart *self);
     232                 :             : static void ensure_cached_grid_lines_and_labels (CcBarChart *self);
     233                 :             : static inline void calculate_axis_area_widths (CcBarChart *self,
     234                 :             :                                                int        *out_left_axis_area_width,
     235                 :             :                                                int        *out_right_axis_area_width);
     236                 :             : static inline void calculate_group_x_bounds (CcBarChart   *self,
     237                 :             :                                              unsigned int  idx,
     238                 :             :                                              int          *out_spacing_start_x,
     239                 :             :                                              int          *out_bar_start_x,
     240                 :             :                                              int          *out_bar_finish_x,
     241                 :             :                                              int          *out_spacing_finish_x);
     242                 :             : static int value_to_widget_y (CcBarChart *self,
     243                 :             :                               double      value);
     244                 :             : static GtkLabel *create_discrete_axis_label (const char *text);
     245                 :             : static GtkLabel *create_continuous_axis_label (const char *text);
     246                 :             : static char *format_continuous_axis_label (CcBarChart *self,
     247                 :             :                                            double      value);
     248                 :             : static double get_maximum_data_value (CcBarChart *self,
     249                 :             :                                       gboolean    include_overlay_line);
     250                 :             : 
     251                 :             : static void
     252                 :           0 : cc_bar_chart_class_init (CcBarChartClass *klass)
     253                 :             : {
     254                 :           0 :   GObjectClass *object_class = G_OBJECT_CLASS (klass);
     255                 :           0 :   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
     256                 :             : 
     257                 :           0 :   object_class->get_property = cc_bar_chart_get_property;
     258                 :           0 :   object_class->set_property = cc_bar_chart_set_property;
     259                 :           0 :   object_class->dispose = cc_bar_chart_dispose;
     260                 :           0 :   object_class->finalize = cc_bar_chart_finalize;
     261                 :             : 
     262                 :           0 :   widget_class->size_allocate = cc_bar_chart_size_allocate;
     263                 :           0 :   widget_class->measure = cc_bar_chart_measure;
     264                 :           0 :   widget_class->snapshot = cc_bar_chart_snapshot;
     265                 :           0 :   widget_class->focus = cc_bar_chart_focus;
     266                 :             : 
     267                 :             :   /**
     268                 :             :    * CcBarChart:selected-index:
     269                 :             :    *
     270                 :             :    * Index of the currently selected data.
     271                 :             :    *
     272                 :             :    * If nothing is currently selected, the value of this property is undefined.
     273                 :             :    * See #CcBarChart:selected-index-set to check this.
     274                 :             :    */
     275                 :           0 :   props[PROP_SELECTED_INDEX] =
     276                 :           0 :     g_param_spec_uint ("selected-index",
     277                 :             :                        NULL, NULL,
     278                 :             :                        0, G_MAXUINT, 0,
     279                 :             :                        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
     280                 :             : 
     281                 :             :   /**
     282                 :             :    * CcBarChart:selected-index-set:
     283                 :             :    *
     284                 :             :    * Whether a data item is currently selected.
     285                 :             :    *
     286                 :             :    * If this property is `TRUE`, the value of #CcBarChart:selected-index is
     287                 :             :    * defined.
     288                 :             :    */
     289                 :           0 :   props[PROP_SELECTED_INDEX_SET] =
     290                 :           0 :     g_param_spec_boolean ("selected-index-set",
     291                 :             :                           NULL, NULL,
     292                 :             :                           FALSE,
     293                 :             :                           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
     294                 :             : 
     295                 :             :   /**
     296                 :             :    * CcBarChart:overlay-line-value:
     297                 :             :    *
     298                 :             :    * Value (in the same domain as the chart data) to render an overlay line at,
     299                 :             :    * or `NAN` to not render one.
     300                 :             :    *
     301                 :             :    * An overlay line could represent an average value or target value for the
     302                 :             :    * bars, for example.
     303                 :             :    */
     304                 :           0 :   props[PROP_OVERLAY_LINE_VALUE] =
     305                 :           0 :     g_param_spec_double ("overlay-line-value",
     306                 :             :                          NULL, NULL,
     307                 :             :                          -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
     308                 :             :                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
     309                 :             : 
     310                 :             :   /**
     311                 :             :    * CcBarChart:discrete-axis-labels: (nullable)
     312                 :             :    *
     313                 :             :    * Labels for the discrete axis of the chart.
     314                 :             :    *
     315                 :             :    * The number of labels must match the number of bars set in the chart data,
     316                 :             :    * one label per bar.
     317                 :             :    *
     318                 :             :    * This will be `NULL` if no labels have been set yet.
     319                 :             :    */
     320                 :           0 :   props[PROP_DISCRETE_AXIS_LABELS] =
     321                 :           0 :     g_param_spec_boxed ("discrete-axis-labels",
     322                 :             :                         NULL, NULL,
     323                 :             :                         G_TYPE_STRV,
     324                 :             :                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
     325                 :             : 
     326                 :           0 :   g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
     327                 :             : 
     328                 :             :   /**
     329                 :             :    * CcBarChart::data-changed:
     330                 :             :    *
     331                 :             :    * Emitted when the data in the widget is updated.
     332                 :             :    */
     333                 :           0 :   signals[SIGNAL_DATA_CHANGED] =
     334                 :           0 :     g_signal_new ("data-changed",
     335                 :             :                   G_TYPE_FROM_CLASS (klass),
     336                 :             :                   G_SIGNAL_RUN_FIRST,
     337                 :             :                   0, NULL, NULL,
     338                 :             :                   NULL,
     339                 :             :                   G_TYPE_NONE, 0);
     340                 :             : 
     341                 :             :   /**
     342                 :             :    * CcBarChart::bar-activated:
     343                 :             :    * @idx: index of the activated bar’s data entry
     344                 :             :    *
     345                 :             :    * Emitted when one of the bars in the chart is activated.
     346                 :             :    */
     347                 :           0 :   signals[SIGNAL_BAR_ACTIVATED] =
     348                 :           0 :     g_signal_new ("bar-activated",
     349                 :             :                   G_TYPE_FROM_CLASS (klass),
     350                 :             :                   G_SIGNAL_RUN_FIRST,
     351                 :             :                   0, NULL, NULL,
     352                 :             :                   NULL,
     353                 :             :                   G_TYPE_NONE, 1, G_TYPE_UINT);
     354                 :             : 
     355                 :             :   /**
     356                 :             :    * CcBarChart::activate-cursor-bar:
     357                 :             :    *
     358                 :             :    * Emitted when the bar under the cursor (the focused bar) is activated.
     359                 :             :    */
     360                 :           0 :   signals[SIGNAL_ACTIVATE_CURSOR_BAR] =
     361                 :           0 :     g_signal_new ("activate-cursor-bar",
     362                 :             :                   G_TYPE_FROM_CLASS (klass),
     363                 :             :                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
     364                 :             :                   0, NULL, NULL,
     365                 :             :                   NULL,
     366                 :             :                   G_TYPE_NONE, 0);
     367                 :             : 
     368                 :             :   /**
     369                 :             :    * CcBarChart::move-cursor:
     370                 :             :    * @chart: the chart on which the signal is emitted
     371                 :             :    * @step: the granularity of the move, as a #GtkMovementStep
     372                 :             :    * @count: the number of @step units to move
     373                 :             :    *
     374                 :             :    * Emitted when the user initiates a cursor movement.
     375                 :             :    *
     376                 :             :    * The default bindings for this signal are:
     377                 :             :    *
     378                 :             :    *  - ←, →, ↑, ↓: move by individual bars
     379                 :             :    *  - Home, End: move to the ends of the chart
     380                 :             :    */
     381                 :           0 :   signals[SIGNAL_MOVE_CURSOR] =
     382                 :           0 :     g_signal_new ("move-cursor",
     383                 :             :                   G_TYPE_FROM_CLASS (klass),
     384                 :             :                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
     385                 :             :                   0, NULL, NULL,
     386                 :             :                   NULL,
     387                 :             :                   G_TYPE_NONE, 2,
     388                 :             :                   GTK_TYPE_MOVEMENT_STEP, G_TYPE_INT);
     389                 :             : 
     390                 :           0 :   gtk_widget_class_set_activate_signal (widget_class, signals[SIGNAL_ACTIVATE_CURSOR_BAR]);
     391                 :             : 
     392                 :           0 :   gtk_widget_class_add_binding_signal (widget_class,
     393                 :             :                                        GDK_KEY_Home, 0,
     394                 :             :                                        "move-cursor",
     395                 :             :                                        "(ii)", GTK_MOVEMENT_BUFFER_ENDS, -1);
     396                 :           0 :   gtk_widget_class_add_binding_signal (widget_class,
     397                 :             :                                        GDK_KEY_KP_Home, 0,
     398                 :             :                                        "move-cursor",
     399                 :             :                                        "(ii)", GTK_MOVEMENT_BUFFER_ENDS, -1);
     400                 :           0 :   gtk_widget_class_add_binding_signal (widget_class,
     401                 :             :                                        GDK_KEY_End, 0,
     402                 :             :                                        "move-cursor",
     403                 :             :                                        "(ii)", GTK_MOVEMENT_BUFFER_ENDS, 1);
     404                 :           0 :   gtk_widget_class_add_binding_signal (widget_class,
     405                 :             :                                        GDK_KEY_KP_End, 0,
     406                 :             :                                        "move-cursor",
     407                 :             :                                        "(ii)", GTK_MOVEMENT_BUFFER_ENDS, 1);
     408                 :           0 :   gtk_widget_class_add_binding_signal (widget_class,
     409                 :             :                                        GDK_KEY_Up, 0,
     410                 :             :                                        "move-cursor",
     411                 :             :                                        "(ii)", GTK_MOVEMENT_LOGICAL_POSITIONS, -1);
     412                 :           0 :   gtk_widget_class_add_binding_signal (widget_class,
     413                 :             :                                        GDK_KEY_KP_Up, 0,
     414                 :             :                                        "move-cursor",
     415                 :             :                                        "(ii)", GTK_MOVEMENT_LOGICAL_POSITIONS, -1);
     416                 :           0 :   gtk_widget_class_add_binding_signal (widget_class,
     417                 :             :                                        GDK_KEY_Down, 0,
     418                 :             :                                        "move-cursor",
     419                 :             :                                        "(ii)", GTK_MOVEMENT_LOGICAL_POSITIONS, 1);
     420                 :           0 :   gtk_widget_class_add_binding_signal (widget_class,
     421                 :             :                                        GDK_KEY_KP_Down, 0,
     422                 :             :                                        "move-cursor",
     423                 :             :                                        "(ii)", GTK_MOVEMENT_LOGICAL_POSITIONS, 1);
     424                 :           0 :   gtk_widget_class_add_binding_signal (widget_class,
     425                 :             :                                        GDK_KEY_Left, 0,
     426                 :             :                                        "move-cursor",
     427                 :             :                                        "(ii)", GTK_MOVEMENT_VISUAL_POSITIONS, -1);
     428                 :           0 :   gtk_widget_class_add_binding_signal (widget_class,
     429                 :             :                                        GDK_KEY_KP_Left, 0,
     430                 :             :                                        "move-cursor",
     431                 :             :                                        "(ii)", GTK_MOVEMENT_VISUAL_POSITIONS, -1);
     432                 :           0 :   gtk_widget_class_add_binding_signal (widget_class,
     433                 :             :                                        GDK_KEY_Right, 0,
     434                 :             :                                        "move-cursor",
     435                 :             :                                        "(ii)", GTK_MOVEMENT_VISUAL_POSITIONS, 1);
     436                 :           0 :   gtk_widget_class_add_binding_signal (widget_class,
     437                 :             :                                        GDK_KEY_KP_Right, 0,
     438                 :             :                                        "move-cursor",
     439                 :             :                                        "(ii)", GTK_MOVEMENT_VISUAL_POSITIONS, 1);
     440                 :             : 
     441                 :           0 :   gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentControl/ui/cc-bar-chart.ui");
     442                 :             : 
     443                 :           0 :   gtk_widget_class_bind_template_callback (widget_class, activate_cursor_bar_cb);
     444                 :           0 :   gtk_widget_class_bind_template_callback (widget_class, move_cursor_cb);
     445                 :             : 
     446                 :           0 :   gtk_widget_class_set_css_name (widget_class, "bar-chart");
     447                 :           0 :   gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST);
     448                 :           0 : }
     449                 :             : 
     450                 :             : static void
     451                 :           0 : cc_bar_chart_init (CcBarChart *self)
     452                 :             : {
     453                 :           0 :   gtk_widget_init_template (GTK_WIDGET (self));
     454                 :             : 
     455                 :             :   /* Some default values */
     456                 :           0 :   self->cached_pixels_per_data = 1.0;
     457                 :           0 :   self->overlay_line_value = NAN;
     458                 :           0 : }
     459                 :             : 
     460                 :             : static void
     461                 :           0 : cc_bar_chart_get_property (GObject    *object,
     462                 :             :                            guint       property_id,
     463                 :             :                            GValue     *value,
     464                 :             :                            GParamSpec *pspec)
     465                 :             : {
     466                 :           0 :   CcBarChart *self = CC_BAR_CHART (object);
     467                 :             : 
     468   [ #  #  #  #  :           0 :   switch ((CcBarChartProperty) property_id)
                      # ]
     469                 :             :     {
     470                 :           0 :     case PROP_SELECTED_INDEX: {
     471                 :             :       size_t idx;
     472                 :           0 :       gboolean valid = cc_bar_chart_get_selected_index (self, &idx);
     473         [ #  # ]:           0 :       g_value_set_uint (value, valid ? idx : 0);
     474                 :           0 :       break;
     475                 :             :     }
     476                 :           0 :     case PROP_SELECTED_INDEX_SET:
     477                 :           0 :       g_value_set_boolean (value, cc_bar_chart_get_selected_index (self, NULL));
     478                 :           0 :       break;
     479                 :           0 :     case PROP_OVERLAY_LINE_VALUE:
     480                 :           0 :       g_value_set_double (value, cc_bar_chart_get_overlay_line_value (self));
     481                 :           0 :       break;
     482                 :           0 :     case PROP_DISCRETE_AXIS_LABELS:
     483                 :           0 :       g_value_set_boxed (value, cc_bar_chart_get_discrete_axis_labels (self, NULL));
     484                 :           0 :       break;
     485                 :           0 :     default:
     486                 :           0 :       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     487                 :           0 :       break;
     488                 :             :     }
     489                 :           0 : }
     490                 :             : 
     491                 :             : static void
     492                 :           0 : cc_bar_chart_set_property (GObject      *object,
     493                 :             :                            guint         property_id,
     494                 :             :                            const GValue *value,
     495                 :             :                            GParamSpec   *pspec)
     496                 :             : {
     497                 :           0 :   CcBarChart *self = CC_BAR_CHART (object);
     498                 :             : 
     499   [ #  #  #  #  :           0 :   switch ((CcBarChartProperty) property_id)
                      # ]
     500                 :             :     {
     501                 :           0 :     case PROP_SELECTED_INDEX:
     502                 :           0 :       cc_bar_chart_set_selected_index (self, TRUE, g_value_get_uint (value));
     503                 :           0 :       break;
     504                 :           0 :     case PROP_SELECTED_INDEX_SET:
     505                 :             :       /* Read only */
     506                 :             :       g_assert_not_reached ();
     507                 :             :       break;
     508                 :           0 :     case PROP_OVERLAY_LINE_VALUE:
     509                 :           0 :       cc_bar_chart_set_overlay_line_value (self, g_value_get_double (value));
     510                 :           0 :       break;
     511                 :           0 :     case PROP_DISCRETE_AXIS_LABELS:
     512                 :           0 :       cc_bar_chart_set_discrete_axis_labels (self, g_value_get_boxed (value));
     513                 :           0 :       break;
     514                 :           0 :     default:
     515                 :           0 :       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     516                 :             :     }
     517                 :           0 : }
     518                 :             : 
     519                 :             : static void
     520                 :           0 : cc_bar_chart_dispose (GObject *object)
     521                 :             : {
     522                 :           0 :   CcBarChart *self = CC_BAR_CHART (object);
     523                 :             : 
     524                 :           0 :   gtk_widget_dispose_template (GTK_WIDGET (object), CC_TYPE_BAR_CHART);
     525                 :             : 
     526         [ #  # ]:           0 :   g_clear_pointer (&self->cached_discrete_axis_labels, g_ptr_array_unref);
     527         [ #  # ]:           0 :   g_clear_pointer (&self->cached_continuous_axis_labels, g_ptr_array_unref);
     528         [ #  # ]:           0 :   g_clear_pointer (&self->cached_continuous_axis_grid_line_values, g_array_unref);
     529         [ #  # ]:           0 :   g_clear_pointer (&self->cached_groups, g_ptr_array_unref);
     530                 :             : 
     531                 :           0 :   G_OBJECT_CLASS (cc_bar_chart_parent_class)->dispose (object);
     532                 :           0 : }
     533                 :             : 
     534                 :             : static void
     535                 :           0 : cc_bar_chart_finalize (GObject *object)
     536                 :             : {
     537                 :           0 :   CcBarChart *self = CC_BAR_CHART (object);
     538                 :             : 
     539                 :           0 :   g_strfreev (self->discrete_axis_labels);
     540                 :             : 
     541         [ #  # ]:           0 :   if (self->continuous_axis_label_destroy_notify != NULL)
     542                 :           0 :     self->continuous_axis_label_destroy_notify (self->continuous_axis_label_user_data);
     543                 :             : 
     544         [ #  # ]:           0 :   if (self->continuous_axis_grid_line_destroy_notify != NULL)
     545                 :           0 :     self->continuous_axis_grid_line_destroy_notify (self->continuous_axis_grid_line_user_data);
     546                 :             : 
     547                 :           0 :   g_free (self->data);
     548                 :             : 
     549                 :           0 :   G_OBJECT_CLASS (cc_bar_chart_parent_class)->finalize (object);
     550                 :           0 : }
     551                 :             : 
     552                 :             : /* Various constants defining the widget appearance. These can’t be moved into
     553                 :             :  * CSS yet as GTK doesn’t expose enough of the CSS parsing machinery. */
     554                 :             : static const unsigned int GRID_LINE_WIDTH = 1;
     555                 :             : static const GdkRGBA GRID_LINE_COLOR = { .red = 0, .green = 0, .blue = 0, .alpha = 0.15 };
     556                 :             : static const GdkRGBA GRID_LINE_COLOR_DARK = { .red = 1, .green = 1, .blue = 1, .alpha = 0.15 };
     557                 :             : static const GdkRGBA GRID_LINE_COLOR_HC = { .red = 0, .green = 0, .blue = 0, .alpha = 0.5 };
     558                 :             : static const GdkRGBA GRID_LINE_COLOR_HC_DARK = { .red = 1, .green = 1, .blue = 1, .alpha = 0.5 };
     559                 :             : static const unsigned int MINIMUM_CHART_HEIGHT = 120;
     560                 :             : static const unsigned int NATURAL_CHART_HEIGHT = 300;
     561                 :             : static const unsigned int OVERLAY_LINE_WIDTH = 2;
     562                 :             : static const float OVERLAY_LINE_DASH[] = { 8, 6 };
     563                 :             : static const GdkRGBA OVERLAY_LINE_COLOR = { .red = 0, .green = 0, .blue = 0, .alpha = 0.5 };
     564                 :             : static const GdkRGBA OVERLAY_LINE_COLOR_DARK = { .red = 1, .green = 1, .blue = 1, .alpha = 0.5 };
     565                 :             : static const GdkRGBA OVERLAY_LINE_COLOR_HC = { .red = 0, .green = 0, .blue = 0, .alpha = 0.8 };
     566                 :             : static const GdkRGBA OVERLAY_LINE_COLOR_HC_DARK = { .red = 1, .green = 1, .blue = 1, .alpha = 0.8 };
     567                 :             : static const double GROUP_TO_SPACE_WIDTH_FILL_RATIO = 0.8;  /* proportion of additional width which gets allocated to bar groups, rather than the space between them */
     568                 :             : 
     569                 :             : static void
     570                 :           0 : cc_bar_chart_size_allocate (GtkWidget *widget,
     571                 :             :                             int        width,
     572                 :             :                             int        height,
     573                 :             :                             int        baseline)
     574                 :             : {
     575                 :           0 :   CcBarChart *self = CC_BAR_CHART (widget);
     576                 :             :   double latest_grid_line_value;
     577                 :           0 :   gboolean collision_detected = FALSE;
     578                 :             : 
     579                 :             :   /* Empty state. */
     580         [ #  # ]:           0 :   if (self->n_data == 0)
     581                 :           0 :     return;
     582                 :             : 
     583                 :           0 :   const double max_value = get_maximum_data_value (self, TRUE);
     584                 :             : 
     585                 :             :   /* Position the labels for the discrete axis in the correct places. */
     586   [ #  #  #  #  :           0 :   for (unsigned int i = 0; self->n_data > 0 && self->cached_discrete_axis_labels != NULL && i < self->cached_discrete_axis_labels->len; i++)
                   #  # ]
     587                 :             :     {
     588                 :             :       GtkAllocation child_alloc;
     589                 :             :       int spacing_start_x, spacing_finish_x;
     590                 :             : 
     591                 :             :       /* The label is allocated the full possible space, and its xalign and
     592                 :             :        * yalign are used to position the text correctly within that. */
     593                 :           0 :       calculate_group_x_bounds (self, i,
     594                 :             :                                 &spacing_start_x,
     595                 :             :                                 NULL, NULL,
     596                 :             :                                 &spacing_finish_x);
     597                 :             : 
     598                 :           0 :       child_alloc.x = spacing_start_x;
     599                 :           0 :       child_alloc.y = height - self->cached_discrete_axis_area_height;
     600                 :           0 :       child_alloc.width = spacing_finish_x - spacing_start_x;
     601                 :           0 :       child_alloc.height = self->cached_discrete_axis_area_height;
     602                 :             : 
     603                 :           0 :       gtk_widget_size_allocate (self->cached_discrete_axis_labels->pdata[i], &child_alloc, self->cached_discrete_axis_baseline);
     604                 :             :     }
     605                 :             : 
     606                 :             :   /* Calculate our continuous axis grid lines and labels */
     607                 :           0 :   ensure_cached_grid_lines_and_labels (self);
     608                 :             : 
     609         [ #  # ]:           0 :   if (self->cached_continuous_axis_grid_line_values != NULL)
     610                 :           0 :     latest_grid_line_value = g_array_index (self->cached_continuous_axis_grid_line_values, double, self->cached_continuous_axis_grid_line_values->len - 1);
     611                 :             :   else
     612                 :           0 :     latest_grid_line_value = max_value;
     613                 :             : 
     614                 :             :   /* Calculate the scale of data on the chart, given the available space to the
     615                 :             :    * topmost continuous axis grid line (factoring in half the height of the
     616                 :             :    * label extending beyond that). Space beyond our natural request is allocated
     617                 :             :    * to the chart area rather than the axis areas. */
     618                 :           0 :   g_assert (height > self->cached_discrete_axis_area_height);
     619                 :           0 :   self->cached_pixels_per_data = (height - self->cached_discrete_axis_area_height - self->cached_continuous_axis_label_height / 2) / (latest_grid_line_value + 1.0);
     620                 :           0 :   g_assert (self->cached_pixels_per_data > 0.0);
     621                 :             : 
     622                 :             :   /* Position the continuous axis labels in the correct places. In a subsequent
     623                 :             :    * step we work out collisions and hide labels based on index modulus to avoid
     624                 :             :    * drawing text on top of other text. See below. */
     625         [ #  # ]:           0 :   if (self->cached_continuous_axis_labels != NULL &&
     626         [ #  # ]:           0 :       self->cached_continuous_axis_grid_line_values != NULL)
     627                 :             :     {
     628         [ #  # ]:           0 :       for (unsigned int i = 0; i < self->cached_continuous_axis_labels->len; i++)
     629                 :             :         {
     630                 :           0 :           const double grid_line_value = g_array_index (self->cached_continuous_axis_grid_line_values, double, i);
     631                 :             :           GtkAllocation child_alloc;
     632                 :             :           int label_natural_height, label_natural_baseline;
     633                 :             : 
     634                 :           0 :           gtk_widget_measure (GTK_WIDGET (self->cached_continuous_axis_labels->pdata[i]),
     635                 :             :                               GTK_ORIENTATION_VERTICAL, self->cached_continuous_axis_area_width,
     636                 :             :                               NULL, &label_natural_height,
     637                 :             :                               NULL, &label_natural_baseline);
     638                 :             : 
     639         [ #  # ]:           0 :           if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
     640                 :           0 :             child_alloc.x = 0;
     641                 :             :           else
     642                 :           0 :             child_alloc.x = width - self->cached_continuous_axis_area_width;
     643                 :             :           /* centre the label vertically on the grid line position, and let the valign code
     644                 :             :            * in GtkLabel align the text baseline correctly with that */
     645                 :           0 :           child_alloc.y = value_to_widget_y (self, grid_line_value) - label_natural_height / 2;
     646                 :           0 :           child_alloc.width = self->cached_continuous_axis_area_width;
     647                 :           0 :           child_alloc.height = label_natural_height;
     648                 :             : 
     649                 :           0 :           gtk_widget_size_allocate (self->cached_continuous_axis_labels->pdata[i], &child_alloc, label_natural_baseline);
     650                 :             :         }
     651                 :             : 
     652                 :             :       /* Check for collisions. Compare pairs of continuous axis labels which are
     653                 :             :        * self->cached_continuous_axis_label_collision_modulus positions apart. If
     654                 :             :        * any of those pairs collide, increment the collision modulus and restart the
     655                 :             :        * check. */
     656                 :           0 :       self->cached_continuous_axis_label_collision_modulus = 1;
     657                 :             : 
     658                 :             :       do
     659                 :             :         {
     660                 :           0 :           collision_detected = FALSE;
     661                 :             : 
     662                 :           0 :           for (unsigned int i = self->cached_continuous_axis_label_collision_modulus;
     663         [ #  # ]:           0 :                i < self->cached_continuous_axis_labels->len;
     664                 :           0 :                i++)
     665                 :             :             {
     666                 :             :               graphene_rect_t child_allocs[2];
     667                 :             :               gboolean success;
     668                 :             : 
     669                 :           0 :               success = gtk_widget_compute_bounds (self->cached_continuous_axis_labels->pdata[i - self->cached_continuous_axis_label_collision_modulus],
     670                 :             :                                                    widget,
     671                 :             :                                                    &child_allocs[0]);
     672                 :           0 :               g_assert (success);
     673                 :           0 :               success = gtk_widget_compute_bounds (self->cached_continuous_axis_labels->pdata[i],
     674                 :             :                                                    widget,
     675                 :             :                                                    &child_allocs[1]);
     676                 :           0 :               g_assert (success);
     677                 :             : 
     678         [ #  # ]:           0 :               if (graphene_rect_intersection (&child_allocs[0], &child_allocs[1], NULL))
     679                 :             :                 {
     680                 :           0 :                   collision_detected = TRUE;
     681                 :           0 :                   g_assert (self->cached_continuous_axis_label_collision_modulus < G_MAXUINT);
     682                 :           0 :                   self->cached_continuous_axis_label_collision_modulus++;
     683                 :           0 :                   break;
     684                 :             :                 }
     685                 :             :             }
     686                 :             :         }
     687         [ #  # ]:           0 :       while (collision_detected);
     688                 :             : 
     689                 :             :       /* Hide continuous axis labels according to the collision modulus. */
     690         [ #  # ]:           0 :       for (unsigned int i = 0; i < self->cached_continuous_axis_labels->len; i++)
     691                 :             :         {
     692                 :           0 :           gtk_widget_set_child_visible (self->cached_continuous_axis_labels->pdata[i],
     693                 :           0 :                                         (i % self->cached_continuous_axis_label_collision_modulus) == 0);
     694                 :             :         }
     695                 :             :     }
     696                 :             : 
     697                 :             :   /* Chart bar groups */
     698   [ #  #  #  # ]:           0 :   for (unsigned int i = 0; self->cached_groups != NULL && i < self->cached_groups->len; i++)
     699                 :             :     {
     700                 :           0 :       CcBarChartGroup *group = CC_BAR_CHART_GROUP (self->cached_groups->pdata[i]);
     701                 :             :       int group_bottom_y, group_left_x, group_right_x;
     702                 :             :       GtkAllocation child_alloc;
     703                 :             : 
     704                 :           0 :       calculate_group_x_bounds (self, i,
     705                 :             :                                 NULL,
     706                 :             :                                 &group_left_x,
     707                 :             :                                 &group_right_x,
     708                 :             :                                 NULL);
     709                 :             : 
     710                 :             :       /* Position the bottom of the bar just above the axis grid line, to avoid
     711                 :             :        * them overlapping. */
     712                 :           0 :       group_bottom_y = value_to_widget_y (self, 0.0) - (GRID_LINE_WIDTH + (2 - 1)) / 2;
     713                 :             : 
     714                 :           0 :       child_alloc.x = group_left_x;
     715                 :           0 :       child_alloc.y = 0;
     716                 :           0 :       child_alloc.width = group_right_x - group_left_x;
     717                 :           0 :       child_alloc.height = group_bottom_y;
     718                 :             : 
     719                 :           0 :       cc_bar_chart_group_set_scale (group, self->cached_pixels_per_data);
     720                 :           0 :       gtk_widget_size_allocate (GTK_WIDGET (group), &child_alloc, -1);
     721                 :             :     }
     722                 :             : }
     723                 :             : 
     724                 :             : static void
     725                 :           0 : cc_bar_chart_measure (GtkWidget      *widget,
     726                 :             :                       GtkOrientation  orientation,
     727                 :             :                       int             for_size,
     728                 :             :                       int            *minimum,
     729                 :             :                       int            *natural,
     730                 :             :                       int            *minimum_baseline,
     731                 :             :                       int            *natural_baseline)
     732                 :             : {
     733                 :           0 :   CcBarChart *self = CC_BAR_CHART (widget);
     734                 :             : 
     735                 :             :   /* Empty state. */
     736         [ #  # ]:           0 :   if (self->n_data == 0)
     737                 :             :     {
     738                 :           0 :       *minimum = 0;
     739                 :           0 :       *natural = 0;
     740                 :           0 :       *minimum_baseline = -1;
     741                 :           0 :       *natural_baseline = -1;
     742                 :           0 :       return;
     743                 :             :     }
     744                 :             : 
     745                 :             :   /* Calculate our continuous axis labels for measuring. Even though some of
     746                 :             :    * them won’t be visible (according to
     747                 :             :    * self->cached_continuous_axis_label_collision_modulus), measure them all
     748                 :             :    * so that the width of the chart doesn’t jump about as the modulus changes. */
     749         [ #  # ]:           0 :   if (self->continuous_axis_label_callback != NULL)
     750                 :           0 :     ensure_cached_grid_lines_and_labels (self);
     751                 :             : 
     752         [ #  # ]:           0 :   if (orientation == GTK_ORIENTATION_HORIZONTAL)
     753                 :             :     {
     754                 :           0 :       int maximum_continuous_label_natural_width = 0;
     755                 :           0 :       int maximum_discrete_label_minimum_width = 0, maximum_discrete_label_natural_width = 0;
     756                 :           0 :       int maximum_bar_minimum_width = 0, maximum_bar_natural_width = 0;
     757                 :             : 
     758                 :             :       /* Measure the first and last continuous axis labels and work out their
     759                 :             :        * minimum widths. */
     760   [ #  #  #  # ]:           0 :       for (unsigned int i = 0; self->cached_continuous_axis_labels != NULL && i < self->cached_continuous_axis_labels->len; i++)
     761                 :             :         {
     762                 :           0 :           int label_natural_width = -1;
     763                 :             : 
     764                 :           0 :           gtk_widget_measure (GTK_WIDGET (self->cached_continuous_axis_labels->pdata[i]),
     765                 :             :                               GTK_ORIENTATION_HORIZONTAL, -1,
     766                 :             :                               NULL, &label_natural_width,
     767                 :             :                               NULL, NULL);
     768                 :             : 
     769                 :           0 :           maximum_continuous_label_natural_width = MAX (maximum_continuous_label_natural_width, label_natural_width);
     770                 :             :         }
     771                 :             : 
     772                 :             :       /* Measure the bar groups to get their minimum and natural widths too. */
     773         [ #  # ]:           0 :       for (unsigned int i = 0; i < self->cached_groups->len; i++)
     774                 :             :         {
     775                 :           0 :           int bar_natural_width = -1, bar_minimum_width = -1;
     776                 :             : 
     777                 :           0 :           gtk_widget_measure (GTK_WIDGET (self->cached_groups->pdata[i]),
     778                 :             :                               GTK_ORIENTATION_HORIZONTAL, -1,
     779                 :             :                               &bar_minimum_width, &bar_natural_width,
     780                 :             :                               NULL, NULL);
     781                 :             : 
     782                 :           0 :           maximum_bar_natural_width = MAX (maximum_bar_natural_width, bar_natural_width);
     783                 :           0 :           maximum_bar_minimum_width = MAX (maximum_bar_minimum_width, bar_minimum_width);
     784                 :             :         }
     785                 :             : 
     786                 :             :       /* Also measure the discrete axis labels to see if any of them are wider
     787                 :             :        * than the groups. */
     788   [ #  #  #  # ]:           0 :       for (unsigned int i = 0; self->cached_discrete_axis_labels != NULL && i < self->cached_discrete_axis_labels->len; i++)
     789                 :             :         {
     790                 :           0 :           int label_natural_width = -1, label_minimum_width = -1;
     791                 :             : 
     792                 :           0 :           gtk_widget_measure (GTK_WIDGET (self->cached_discrete_axis_labels->pdata[i]),
     793                 :             :                               GTK_ORIENTATION_HORIZONTAL, -1,
     794                 :             :                               &label_minimum_width, &label_natural_width,
     795                 :             :                               NULL, NULL);
     796                 :             : 
     797                 :           0 :           maximum_discrete_label_natural_width = MAX (maximum_discrete_label_natural_width, label_natural_width);
     798                 :           0 :           maximum_discrete_label_minimum_width = MAX (maximum_discrete_label_minimum_width, label_minimum_width);
     799                 :             :         }
     800                 :             : 
     801                 :           0 :       self->cached_minimum_group_width = MAX (maximum_bar_minimum_width, maximum_discrete_label_minimum_width);
     802                 :             : 
     803                 :             :       /* Don’t complicate things by allowing the continuous axis labels to get
     804                 :             :        * less than their natural width as an allocation. */
     805                 :           0 :       self->cached_continuous_axis_area_width = maximum_continuous_label_natural_width;
     806                 :             : 
     807                 :           0 :       *minimum = MAX (maximum_bar_minimum_width, maximum_discrete_label_minimum_width) * self->n_data + maximum_continuous_label_natural_width;
     808                 :           0 :       *natural = MAX (maximum_bar_natural_width, maximum_discrete_label_natural_width) * self->n_data + maximum_continuous_label_natural_width;
     809                 :           0 :       *minimum_baseline = -1;
     810                 :           0 :       *natural_baseline = -1;
     811                 :             :     }
     812         [ #  # ]:           0 :   else if (orientation == GTK_ORIENTATION_VERTICAL)
     813                 :             :     {
     814                 :           0 :       int maximum_discrete_label_natural_height = 0, maximum_discrete_label_natural_baseline = -1;
     815                 :           0 :       int continuous_label_minimum_height = 0, continuous_label_natural_height = 0;
     816                 :             : 
     817                 :             :       /* Measure all the discrete labels. */
     818   [ #  #  #  # ]:           0 :       for (unsigned int i = 0; self->cached_discrete_axis_labels != NULL && i < self->cached_discrete_axis_labels->len; i++)
     819                 :             :         {
     820                 :           0 :           int label_natural_height = -1, label_natural_baseline = -1;
     821                 :             : 
     822                 :           0 :           gtk_widget_measure (GTK_WIDGET (self->cached_discrete_axis_labels->pdata[i]),
     823                 :             :                               GTK_ORIENTATION_VERTICAL, -1,
     824                 :             :                               NULL, &label_natural_height,
     825                 :             :                               NULL, &label_natural_baseline);
     826                 :             : 
     827                 :           0 :           maximum_discrete_label_natural_height = MAX (maximum_discrete_label_natural_height, label_natural_height);
     828                 :           0 :           maximum_discrete_label_natural_baseline = MAX (maximum_discrete_label_natural_baseline, label_natural_baseline);
     829                 :             :         }
     830                 :             : 
     831                 :             :       /* Measure the last continuous axis label and work out its minimum height,
     832                 :             :        * because it will be vertically centred on the top-most grid line, which
     833                 :             :        * might be near enough the top of the chart to push the top of the text
     834                 :             :        * off the default allocation.
     835                 :             :        *
     836                 :             :        * There’s no need to do the same for the first continuous axis label,
     837                 :             :        * because it will never drop below the discrete axis labels unless the
     838                 :             :        * font sizes are ludicrously different. */
     839         [ #  # ]:           0 :       if (self->cached_continuous_axis_labels != NULL)
     840                 :             :         {
     841                 :           0 :           gtk_widget_measure (GTK_WIDGET (self->cached_continuous_axis_labels->pdata[self->cached_continuous_axis_labels->len - 1]),
     842                 :             :                               GTK_ORIENTATION_VERTICAL, -1,
     843                 :             :                               &continuous_label_minimum_height, &continuous_label_natural_height,
     844                 :             :                               NULL, NULL);
     845                 :             :         }
     846                 :             : 
     847                 :           0 :       self->cached_continuous_axis_label_height = continuous_label_natural_height;
     848                 :             : 
     849                 :             :       /* Don’t complicate things by allowing the discrete axis labels to get
     850                 :             :        * less than their natural height as an allocation. */
     851                 :           0 :       self->cached_discrete_axis_area_height = maximum_discrete_label_natural_height;
     852                 :           0 :       self->cached_discrete_axis_baseline = maximum_discrete_label_natural_baseline;
     853                 :             : 
     854                 :           0 :       *minimum = MINIMUM_CHART_HEIGHT + maximum_discrete_label_natural_height + continuous_label_minimum_height / 2;
     855                 :           0 :       *natural = NATURAL_CHART_HEIGHT + maximum_discrete_label_natural_height + continuous_label_natural_height / 2;
     856                 :           0 :       *minimum_baseline = -1;
     857                 :           0 :       *natural_baseline = -1;
     858                 :             :     }
     859                 :             :   else
     860                 :             :     {
     861                 :             :       g_assert_not_reached ();
     862                 :             :     }
     863                 :             : }
     864                 :             : 
     865                 :             : static void
     866                 :           0 : cc_bar_chart_snapshot (GtkWidget   *widget,
     867                 :             :                        GtkSnapshot *snapshot)
     868                 :             : {
     869                 :           0 :   CcBarChart *self = CC_BAR_CHART (widget);
     870                 :           0 :   const int width = gtk_widget_get_width (widget);
     871                 :             :   int left_axis_area_width, right_axis_area_width;
     872                 :             :   AdwStyleManager *style_manager;
     873                 :             : 
     874                 :             :   /* Empty state. */
     875         [ #  # ]:           0 :   if (self->n_data == 0)
     876                 :           0 :     return;
     877                 :             : 
     878                 :           0 :   style_manager = adw_style_manager_get_for_display (gtk_widget_get_display (widget));
     879                 :             : 
     880                 :           0 :   calculate_axis_area_widths (self, &left_axis_area_width, &right_axis_area_width);
     881                 :             : 
     882                 :             :   /* Continuous axis grid lines, should have been cached in size_allocate, but
     883                 :             :    * may not have been set yet. */
     884         [ #  # ]:           0 :   if (self->cached_continuous_axis_grid_line_values != NULL)
     885                 :             :     {
     886                 :           0 :       g_autoptr(GskPathBuilder) grid_line_builder = gsk_path_builder_new ();
     887                 :           0 :       g_autoptr(GskPath) grid_line_path = NULL;
     888                 :           0 :       GskStroke *grid_line_stroke = NULL;
     889                 :             :       const GdkRGBA *grid_line_color;
     890                 :             : 
     891                 :           0 :       grid_line_stroke = gsk_stroke_new (GRID_LINE_WIDTH);
     892                 :             : 
     893         [ #  # ]:           0 :       for (unsigned int i = 0; i < self->cached_continuous_axis_grid_line_values->len; i++)
     894                 :             :         {
     895                 :           0 :           const double value = g_array_index (self->cached_continuous_axis_grid_line_values, double, i);
     896                 :           0 :           int y = value_to_widget_y (self, value);
     897                 :             : 
     898                 :           0 :           gsk_path_builder_move_to (grid_line_builder, left_axis_area_width, y);
     899                 :           0 :           gsk_path_builder_line_to (grid_line_builder,
     900                 :           0 :                                     width - right_axis_area_width,
     901                 :             :                                     y);
     902                 :             :         }
     903                 :             : 
     904                 :           0 :       grid_line_path = gsk_path_builder_free_to_path (g_steal_pointer (&grid_line_builder));
     905                 :             : 
     906   [ #  #  #  # ]:           0 :       if (adw_style_manager_get_dark (style_manager) && adw_style_manager_get_high_contrast (style_manager))
     907                 :           0 :         grid_line_color = &GRID_LINE_COLOR_HC_DARK;
     908         [ #  # ]:           0 :       else if (adw_style_manager_get_dark (style_manager))
     909                 :           0 :         grid_line_color = &GRID_LINE_COLOR_DARK;
     910         [ #  # ]:           0 :       else if (adw_style_manager_get_high_contrast (style_manager))
     911                 :           0 :         grid_line_color = &GRID_LINE_COLOR_HC;
     912                 :             :       else
     913                 :           0 :         grid_line_color = &GRID_LINE_COLOR;
     914                 :             : 
     915                 :           0 :       gtk_snapshot_append_stroke (snapshot, grid_line_path, grid_line_stroke, grid_line_color);
     916                 :             : 
     917                 :           0 :       gsk_stroke_free (g_steal_pointer (&grid_line_stroke));
     918                 :             :     }
     919                 :             : 
     920                 :             :   /* Continuous axis labels, should have been cached in size_allocate, but may
     921                 :             :    * not have been set yet. */
     922   [ #  #  #  # ]:           0 :   for (unsigned int i = 0; self->cached_continuous_axis_labels != NULL && i < self->cached_continuous_axis_labels->len; i++)
     923                 :           0 :     gtk_widget_snapshot_child (widget, self->cached_continuous_axis_labels->pdata[i], snapshot);
     924                 :             : 
     925                 :             :   /* Discrete axis labels, should have been cached in size_allocate, but may not
     926                 :             :    * have been set yet */
     927   [ #  #  #  # ]:           0 :   for (unsigned int i = 0; self->cached_discrete_axis_labels != NULL && i < self->cached_discrete_axis_labels->len; i++)
     928                 :           0 :     gtk_widget_snapshot_child (widget, self->cached_discrete_axis_labels->pdata[i], snapshot);
     929                 :             : 
     930                 :             :   /* Bar groups, should have been cached in size_allocate, but may not have been set yet */
     931   [ #  #  #  # ]:           0 :   for (unsigned int i = 0; self->cached_groups != NULL && i < self->cached_groups->len; i++)
     932                 :           0 :     gtk_widget_snapshot_child (widget, self->cached_groups->pdata[i], snapshot);
     933                 :             : 
     934                 :             :   /* Overlay line */
     935         [ #  # ]:           0 :   if (!isnan (self->overlay_line_value))
     936                 :             :     {
     937                 :           0 :       g_autoptr(GskPathBuilder) overlay_builder = gsk_path_builder_new ();
     938                 :           0 :       g_autoptr(GskPath) overlay_path = NULL;
     939                 :           0 :       GskStroke *overlay_stroke = NULL;
     940                 :             :       int overlay_y;
     941                 :             :       const GdkRGBA *overlay_line_color;
     942                 :             : 
     943                 :           0 :       overlay_stroke = gsk_stroke_new (OVERLAY_LINE_WIDTH);
     944                 :           0 :       gsk_stroke_set_line_cap (overlay_stroke, GSK_LINE_CAP_SQUARE);
     945                 :           0 :       gsk_stroke_set_dash (overlay_stroke, OVERLAY_LINE_DASH, G_N_ELEMENTS (OVERLAY_LINE_DASH));
     946                 :             : 
     947                 :           0 :       overlay_y = value_to_widget_y (self, self->overlay_line_value);
     948                 :           0 :       gsk_path_builder_move_to (overlay_builder, left_axis_area_width, overlay_y);
     949                 :           0 :       gsk_path_builder_line_to (overlay_builder,
     950                 :           0 :                                 width - right_axis_area_width,
     951                 :             :                                 overlay_y);
     952                 :             : 
     953                 :           0 :       overlay_path = gsk_path_builder_free_to_path (g_steal_pointer (&overlay_builder));
     954                 :             : 
     955   [ #  #  #  # ]:           0 :       if (adw_style_manager_get_dark (style_manager) && adw_style_manager_get_high_contrast (style_manager))
     956                 :           0 :         overlay_line_color = &OVERLAY_LINE_COLOR_HC_DARK;
     957         [ #  # ]:           0 :       else if (adw_style_manager_get_dark (style_manager))
     958                 :           0 :         overlay_line_color = &OVERLAY_LINE_COLOR_DARK;
     959         [ #  # ]:           0 :       else if (adw_style_manager_get_high_contrast (style_manager))
     960                 :           0 :         overlay_line_color = &OVERLAY_LINE_COLOR_HC;
     961                 :             :       else
     962                 :           0 :         overlay_line_color = &OVERLAY_LINE_COLOR;
     963                 :             : 
     964                 :           0 :       gtk_snapshot_append_stroke (snapshot, overlay_path, overlay_stroke, overlay_line_color);
     965                 :             : 
     966                 :           0 :       gsk_stroke_free (g_steal_pointer (&overlay_stroke));
     967                 :             :     }
     968                 :             : }
     969                 :             : 
     970                 :             : static gboolean
     971                 :           0 : cc_bar_chart_focus (GtkWidget        *widget,
     972                 :             :                     GtkDirectionType  direction)
     973                 :             : {
     974                 :           0 :   CcBarChart *self = CC_BAR_CHART (widget);
     975                 :             :   GtkWidget *focus_child;
     976                 :           0 :   CcBarChartGroup *next_focus_group = NULL;
     977                 :             : 
     978                 :             :   /* Reverse the direction if in RTL mode, as the chart presents things on a
     979                 :             :    * left–right axis. */
     980         [ #  # ]:           0 :   if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
     981                 :             :     {
     982   [ #  #  #  #  :           0 :       switch (direction)
                      # ]
     983                 :             :         {
     984                 :           0 :         case GTK_DIR_TAB_BACKWARD:
     985                 :           0 :           direction = GTK_DIR_TAB_FORWARD;
     986                 :           0 :           break;
     987                 :           0 :         case GTK_DIR_TAB_FORWARD:
     988                 :           0 :           direction = GTK_DIR_TAB_BACKWARD;
     989                 :           0 :           break;
     990                 :           0 :         case GTK_DIR_LEFT:
     991                 :           0 :           direction = GTK_DIR_RIGHT;
     992                 :           0 :           break;
     993                 :           0 :         case GTK_DIR_RIGHT:
     994                 :           0 :           direction = GTK_DIR_LEFT;
     995                 :           0 :           break;
     996                 :           0 :         case GTK_DIR_UP:
     997                 :             :         case GTK_DIR_DOWN:
     998                 :             :         default:
     999                 :             :           /* No change. */
    1000                 :           0 :           break;
    1001                 :             :         }
    1002                 :             :     }
    1003                 :             : 
    1004                 :           0 :   focus_child = gtk_widget_get_focus_child (widget);
    1005                 :             : 
    1006         [ #  # ]:           0 :   if (focus_child != NULL)
    1007                 :             :     {
    1008                 :             :       /* Can the focus move around inside the currently focused child widget? */
    1009         [ #  # ]:           0 :       if (gtk_widget_child_focus (focus_child, direction))
    1010                 :           0 :         return TRUE;
    1011                 :             : 
    1012   [ #  #  #  # ]:           0 :       if (CC_IS_BAR_CHART_GROUP (focus_child) &&
    1013         [ #  # ]:           0 :           (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD))
    1014                 :           0 :         next_focus_group = get_adjacent_focusable_group (self, CC_BAR_CHART_GROUP (focus_child), -1);
    1015   [ #  #  #  # ]:           0 :       else if (CC_IS_BAR_CHART_GROUP (focus_child) &&
    1016         [ #  # ]:           0 :                (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD))
    1017                 :           0 :         next_focus_group = get_adjacent_focusable_group (self, CC_BAR_CHART_GROUP (focus_child), 1);
    1018                 :             :     }
    1019                 :             :   else
    1020                 :             :     {
    1021                 :             :       /* No current focus group. If a group is selected, focus on that. Otherwise,
    1022                 :             :        * focus on the first/last focusable group, depending on which direction
    1023                 :             :        * we’re coming in from. */
    1024         [ #  # ]:           0 :       if (self->selected_index_set)
    1025                 :           0 :         next_focus_group = self->cached_groups->pdata[self->selected_index];
    1026                 :             : 
    1027   [ #  #  #  # ]:           0 :       if (next_focus_group == NULL &&
    1028   [ #  #  #  # ]:           0 :           (direction == GTK_DIR_UP || direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD))
    1029                 :           0 :         next_focus_group = get_last_focusable_group (self);
    1030   [ #  #  #  # ]:           0 :       else if (next_focus_group == NULL &&
    1031   [ #  #  #  # ]:           0 :                (direction == GTK_DIR_DOWN || direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD))
    1032                 :           0 :         next_focus_group = get_first_focusable_group (self);
    1033                 :             :     }
    1034                 :             : 
    1035         [ #  # ]:           0 :   if (next_focus_group == NULL)
    1036                 :             :     {
    1037   [ #  #  #  # ]:           0 :       if (direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT)
    1038                 :             :         {
    1039         [ #  # ]:           0 :           if (gtk_widget_keynav_failed (widget, direction))
    1040                 :           0 :             return TRUE;
    1041                 :             :         }
    1042                 :             : 
    1043                 :           0 :       return FALSE;
    1044                 :             :     }
    1045                 :             : 
    1046                 :           0 :   return gtk_widget_child_focus (GTK_WIDGET (next_focus_group), direction);
    1047                 :             : }
    1048                 :             : 
    1049                 :             : static void
    1050                 :           0 : group_notify_selected_index_cb (GObject    *object,
    1051                 :             :                                 GParamSpec *pspec,
    1052                 :             :                                 gpointer    user_data)
    1053                 :             : {
    1054                 :           0 :   CcBarChart *self = CC_BAR_CHART (user_data);
    1055                 :           0 :   CcBarChartGroup *group = CC_BAR_CHART_GROUP (object);
    1056                 :             :   gboolean success;
    1057                 :           0 :   unsigned int group_idx = 0;
    1058                 :           0 :   size_t selected_idx = 0;
    1059                 :             : 
    1060                 :             :   /* Which group is this? */
    1061                 :           0 :   success = find_index_for_group (self, group, &group_idx);
    1062                 :           0 :   g_assert (success);
    1063                 :             : 
    1064   [ #  #  #  # ]:           0 :   if (cc_bar_chart_group_get_is_selected (group) ||
    1065                 :           0 :       cc_bar_chart_group_get_selected_index (group, NULL))
    1066                 :             :     {
    1067                 :             :       /* If the group is now selected, update our selection. */
    1068                 :           0 :       cc_bar_chart_set_selected_index (self, TRUE, group_idx);
    1069                 :             :     }
    1070         [ #  # ]:           0 :   else if (cc_bar_chart_get_selected_index (self, &selected_idx) &&
    1071         [ #  # ]:           0 :            selected_idx == group_idx)
    1072                 :             :     {
    1073                 :             :       /* Otherwise, if the group is no longer selected, but was the selected
    1074                 :             :        * group in our selection, clear that. */
    1075                 :           0 :       cc_bar_chart_set_selected_index (self, FALSE, 0);
    1076                 :             :     }
    1077                 :           0 : }
    1078                 :             : 
    1079                 :             : static void
    1080                 :           0 : bar_activate_cb (CcBarChartBar *bar,
    1081                 :             :                  gpointer       user_data)
    1082                 :             : {
    1083                 :           0 :   CcBarChart *self = CC_BAR_CHART (user_data);
    1084                 :           0 :   unsigned int idx = 0;
    1085                 :             : 
    1086                 :             :   /* Select and activate the bar */
    1087         [ #  # ]:           0 :   if (!find_index_for_bar (self, bar, &idx))
    1088                 :           0 :     return;
    1089                 :             : 
    1090                 :           0 :   gtk_widget_grab_focus (GTK_WIDGET (bar));
    1091                 :           0 :   cc_bar_chart_set_selected_index (self, TRUE, idx);
    1092                 :             : 
    1093                 :           0 :   g_signal_emit (self, signals[SIGNAL_BAR_ACTIVATED], 0, bar);
    1094                 :             : }
    1095                 :             : 
    1096                 :             : static void
    1097                 :           0 : activate_cursor_bar_cb (CcBarChart *self,
    1098                 :             :                         gpointer    user_data)
    1099                 :             : {
    1100         [ #  # ]:           0 :   if (self->selected_index_set)
    1101                 :           0 :     gtk_widget_activate (GTK_WIDGET (self->cached_groups->pdata[self->selected_index]));
    1102                 :           0 : }
    1103                 :             : 
    1104                 :             : static void
    1105                 :           0 : move_cursor_cb (CcBarChart      *self,
    1106                 :             :                 GtkMovementStep  step,
    1107                 :             :                 int              count)
    1108                 :             : {
    1109                 :           0 :   CcBarChartGroup *group = NULL, *selected_group = NULL;
    1110                 :           0 :   unsigned int idx = 0;
    1111                 :             :   int visual_count;
    1112                 :             : 
    1113                 :           0 :   g_assert (self->cached_groups != NULL);
    1114                 :             : 
    1115         [ #  # ]:           0 :   if (self->selected_index_set)
    1116                 :           0 :     selected_group = self->cached_groups->pdata[self->selected_index];
    1117                 :             : 
    1118         [ #  # ]:           0 :   if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
    1119                 :           0 :     visual_count = -count;
    1120                 :             :   else
    1121                 :           0 :     visual_count = count;
    1122                 :             : 
    1123   [ #  #  #  # ]:           0 :   switch (step)
    1124                 :             :     {
    1125                 :           0 :     case GTK_MOVEMENT_BUFFER_ENDS:
    1126         [ #  # ]:           0 :       if (count < 0)
    1127                 :           0 :         group = get_first_focusable_group (self);
    1128                 :             :       else
    1129                 :           0 :         group = get_last_focusable_group (self);
    1130                 :           0 :       break;
    1131                 :           0 :     case GTK_MOVEMENT_LOGICAL_POSITIONS:
    1132         [ #  # ]:           0 :       if (selected_group != NULL)
    1133         [ #  # ]:           0 :         group = get_adjacent_focusable_group (self, selected_group, (count < 0) ? -1 : 1);
    1134                 :           0 :       break;
    1135                 :           0 :     case GTK_MOVEMENT_VISUAL_POSITIONS:
    1136         [ #  # ]:           0 :       if (selected_group != NULL)
    1137         [ #  # ]:           0 :         group = get_adjacent_focusable_group (self, selected_group, (visual_count < 0) ? -1 : 1);
    1138                 :           0 :       break;
    1139                 :           0 :     case GTK_MOVEMENT_WORDS:
    1140                 :             :     case GTK_MOVEMENT_DISPLAY_LINES:
    1141                 :             :     case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
    1142                 :             :     case GTK_MOVEMENT_PARAGRAPHS:
    1143                 :             :     case GTK_MOVEMENT_PARAGRAPH_ENDS:
    1144                 :             :     case GTK_MOVEMENT_PAGES:
    1145                 :             :     case GTK_MOVEMENT_HORIZONTAL_PAGES:
    1146                 :             :     default:
    1147                 :             :       /* Not currently supported */
    1148                 :           0 :       return;
    1149                 :             :     }
    1150                 :             : 
    1151                 :             :   /* Did we fail to move anywhere? */
    1152   [ #  #  #  # ]:           0 :   if (group == NULL || group == selected_group)
    1153                 :             :     {
    1154                 :           0 :       GtkDirectionType direction = (count < 0) ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD;
    1155                 :             : 
    1156         [ #  # ]:           0 :       if (!gtk_widget_keynav_failed (GTK_WIDGET (self), direction))
    1157                 :             :         {
    1158                 :           0 :           GtkWidget *toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (self)));
    1159                 :             : 
    1160         [ #  # ]:           0 :           if (toplevel != NULL)
    1161                 :           0 :             gtk_widget_child_focus (toplevel, direction);
    1162                 :             :         }
    1163                 :             : 
    1164                 :           0 :       return;
    1165                 :             :     }
    1166                 :             : 
    1167         [ #  # ]:           0 :   if (find_index_for_group (self, group, &idx))
    1168                 :             :     {
    1169                 :           0 :       gtk_widget_grab_focus (GTK_WIDGET (group));
    1170                 :           0 :       cc_bar_chart_set_selected_index (self, TRUE, idx);
    1171                 :             :     }
    1172                 :             : }
    1173                 :             : 
    1174                 :             : static gboolean
    1175                 :           0 : find_index_for_group (CcBarChart      *self,
    1176                 :             :                       CcBarChartGroup *group,
    1177                 :             :                       unsigned int    *out_idx)
    1178                 :             : {
    1179                 :           0 :   g_assert (gtk_widget_is_ancestor (GTK_WIDGET (group), GTK_WIDGET (self)));
    1180                 :           0 :   g_assert (self->cached_groups != NULL);
    1181                 :             : 
    1182                 :           0 :   return g_ptr_array_find (self->cached_groups, group, out_idx);
    1183                 :             : }
    1184                 :             : 
    1185                 :             : static gboolean
    1186                 :           0 : find_index_for_bar (CcBarChart    *self,
    1187                 :             :                     CcBarChartBar *bar,
    1188                 :             :                     unsigned int  *out_idx)
    1189                 :             : {
    1190                 :           0 :   unsigned int bar_idx = 0;
    1191                 :             : 
    1192                 :           0 :   g_assert (gtk_widget_is_ancestor (GTK_WIDGET (bar), GTK_WIDGET (self)));
    1193                 :           0 :   g_assert (self->cached_groups != NULL);
    1194                 :             : 
    1195         [ #  # ]:           0 :   for (unsigned int i = 0; i < self->cached_groups->len; i++)
    1196                 :             :     {
    1197                 :           0 :       CcBarChartGroup *group = self->cached_groups->pdata[i];
    1198                 :           0 :       size_t n_bars = 0;
    1199                 :           0 :       CcBarChartBar * const *bars = cc_bar_chart_group_get_bars (group, &n_bars);
    1200                 :             : 
    1201         [ #  # ]:           0 :       for (size_t j = 0; j < n_bars; j++)
    1202                 :             :         {
    1203         [ #  # ]:           0 :           if (bars[j] == bar)
    1204                 :             :             {
    1205         [ #  # ]:           0 :               if (out_idx != NULL)
    1206                 :           0 :                 *out_idx = bar_idx;
    1207                 :           0 :               return TRUE;
    1208                 :             :             }
    1209                 :             :           else
    1210                 :             :             {
    1211                 :           0 :               bar_idx++;
    1212                 :             :             }
    1213                 :             :         }
    1214                 :             :     }
    1215                 :             : 
    1216         [ #  # ]:           0 :   if (out_idx != NULL)
    1217                 :           0 :     *out_idx = 0;
    1218                 :             : 
    1219                 :           0 :   return FALSE;
    1220                 :             : }
    1221                 :             : 
    1222                 :             : static gboolean
    1223                 :           0 : group_is_focusable (CcBarChartGroup *group)
    1224                 :             : {
    1225                 :           0 :   GtkWidget *widget = GTK_WIDGET (group);
    1226                 :             : 
    1227         [ #  # ]:           0 :   return (gtk_widget_is_visible (widget) &&
    1228         [ #  # ]:           0 :           gtk_widget_is_sensitive (widget) &&
    1229   [ #  #  #  # ]:           0 :           gtk_widget_get_focusable (widget) &&
    1230                 :           0 :           gtk_widget_get_can_focus (widget));
    1231                 :             : }
    1232                 :             : 
    1233                 :             : /* direction == -1 means get previous sensitive and visible group;
    1234                 :             :  * direction == 1 means get next one. */
    1235                 :             : static CcBarChartGroup *
    1236                 :           0 : get_adjacent_focusable_group (CcBarChart      *self,
    1237                 :             :                               CcBarChartGroup *group,
    1238                 :             :                               int              direction)
    1239                 :             : {
    1240                 :             :   unsigned int group_idx, i;
    1241                 :             : 
    1242                 :           0 :   g_assert (gtk_widget_is_ancestor (GTK_WIDGET (group), GTK_WIDGET (self)));
    1243                 :           0 :   g_assert (self->cached_groups != NULL);
    1244                 :           0 :   g_assert (direction == -1 || direction == 1);
    1245                 :             : 
    1246         [ #  # ]:           0 :   if (!find_index_for_group (self, group, &group_idx))
    1247                 :           0 :     return NULL;
    1248                 :             : 
    1249                 :           0 :   i = group_idx;
    1250                 :             : 
    1251   [ #  #  #  #  :           0 :   while (!((direction == -1 && i == 0) ||
                   #  # ]
    1252         [ #  # ]:           0 :            (direction == 1 && i >= self->cached_groups->len - 1)))
    1253                 :             :     {
    1254                 :             :       CcBarChartGroup *adjacent_group;
    1255                 :             : 
    1256                 :           0 :       i += direction;
    1257                 :           0 :       adjacent_group = self->cached_groups->pdata[i];
    1258                 :             : 
    1259         [ #  # ]:           0 :       if (group_is_focusable (adjacent_group))
    1260                 :           0 :         return adjacent_group;
    1261                 :             :     }
    1262                 :             : 
    1263                 :           0 :   return NULL;
    1264                 :             : }
    1265                 :             : 
    1266                 :             : static CcBarChartGroup *
    1267                 :           0 : get_first_focusable_group (CcBarChart *self)
    1268                 :             : {
    1269                 :           0 :   g_assert (self->cached_groups != NULL);
    1270                 :             : 
    1271         [ #  # ]:           0 :   for (unsigned int i = 0; i < self->cached_groups->len; i++)
    1272                 :             :     {
    1273                 :           0 :       CcBarChartGroup *group = self->cached_groups->pdata[i];
    1274                 :             : 
    1275         [ #  # ]:           0 :       if (group_is_focusable (group))
    1276                 :           0 :         return group;
    1277                 :             :     }
    1278                 :             : 
    1279                 :           0 :   return NULL;
    1280                 :             : }
    1281                 :             : 
    1282                 :             : static CcBarChartGroup *
    1283                 :           0 : get_last_focusable_group (CcBarChart *self)
    1284                 :             : {
    1285                 :           0 :   g_assert (self->cached_groups != NULL);
    1286                 :             : 
    1287         [ #  # ]:           0 :   for (unsigned int i = 0; i < self->cached_groups->len; i++)
    1288                 :             :     {
    1289                 :           0 :       CcBarChartGroup *group = self->cached_groups->pdata[self->cached_groups->len - 1 - i];
    1290                 :             : 
    1291         [ #  # ]:           0 :       if (group_is_focusable (group))
    1292                 :           0 :         return group;
    1293                 :             :     }
    1294                 :             : 
    1295                 :           0 :   return NULL;
    1296                 :             : }
    1297                 :             : 
    1298                 :             : static void
    1299                 :           0 : ensure_cached_grid_lines_and_labels (CcBarChart *self)
    1300                 :             : {
    1301                 :           0 :   const double max_value = get_maximum_data_value (self, TRUE);
    1302                 :             :   double latest_grid_line_value;
    1303                 :             : 
    1304                 :             :   /* Calculate our continuous axis grid lines. Use the user’s provided callback
    1305                 :             :    * to lay them out, until we’ve got enough to cover the maximum data value.
    1306                 :             :    * We always need at least two grid lines to define the top and bottom of the
    1307                 :             :    * plot. */
    1308         [ #  # ]:           0 :   if (self->cached_continuous_axis_grid_line_values == NULL &&
    1309         [ #  # ]:           0 :       self->continuous_axis_grid_line_callback != NULL)
    1310                 :             :     {
    1311                 :           0 :       self->cached_continuous_axis_grid_line_values = g_array_new (FALSE, FALSE, sizeof (double));
    1312                 :             : 
    1313                 :             :       do
    1314                 :             :         {
    1315                 :           0 :           latest_grid_line_value = self->continuous_axis_grid_line_callback (self,
    1316                 :           0 :                                                                              self->cached_continuous_axis_grid_line_values->len,
    1317                 :             :                                                                              self->continuous_axis_grid_line_user_data);
    1318                 :           0 :           g_assert (latest_grid_line_value >= 0.0);
    1319                 :           0 :           g_array_append_val (self->cached_continuous_axis_grid_line_values, latest_grid_line_value);
    1320                 :             :         }
    1321         [ #  # ]:           0 :       while (latest_grid_line_value <= max_value ||
    1322         [ #  # ]:           0 :              self->cached_continuous_axis_grid_line_values->len < 2);
    1323                 :             :     }
    1324                 :             : 
    1325                 :             :   /* Create one continuous axis label for each grid line. In a subsequent step
    1326                 :             :    * in cc_bar_chart_size_allocate() we position them all and we work out
    1327                 :             :    * collisions and hide labels based on index modulus to avoid drawing text on
    1328                 :             :    * top of other text. See cc_bar_chart_size_allocate(). */
    1329         [ #  # ]:           0 :   if (self->cached_continuous_axis_labels == NULL &&
    1330         [ #  # ]:           0 :       self->continuous_axis_label_callback != NULL &&
    1331         [ #  # ]:           0 :       self->cached_continuous_axis_grid_line_values != NULL)
    1332                 :             :     {
    1333                 :           0 :       self->cached_continuous_axis_labels = g_ptr_array_new_with_free_func ((GDestroyNotify) gtk_widget_unparent);
    1334                 :             : 
    1335         [ #  # ]:           0 :       for (unsigned int i = 0; i < self->cached_continuous_axis_grid_line_values->len; i++)
    1336                 :             :         {
    1337                 :           0 :           const double grid_line_value = g_array_index (self->cached_continuous_axis_grid_line_values, double, i);
    1338                 :           0 :           g_autofree char *label_text = format_continuous_axis_label (self, grid_line_value);
    1339                 :           0 :           GtkLabel *label = create_continuous_axis_label (label_text);
    1340                 :           0 :           gtk_widget_set_parent (GTK_WIDGET (label), GTK_WIDGET (self));
    1341                 :             :           /* don’t insert the label into the widget child order using
    1342                 :             :            * gtk_widget_insert_after(), as it shouldn’t be focusable */
    1343                 :           0 :           g_ptr_array_add (self->cached_continuous_axis_labels, label);
    1344                 :             :         }
    1345                 :             :     }
    1346                 :           0 : }
    1347                 :             : 
    1348                 :             : static inline void
    1349                 :           0 : calculate_axis_area_widths (CcBarChart *self,
    1350                 :             :                             int        *out_left_axis_area_width,
    1351                 :             :                             int        *out_right_axis_area_width)
    1352                 :             : {
    1353                 :             :   int left_axis_area_width, right_axis_area_width;
    1354                 :             : 
    1355                 :             :   /* The continuous axis is on the right of the plot area in LTR directions,
    1356                 :             :    * and on the left in RTL. */
    1357         [ #  # ]:           0 :   if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
    1358                 :             :     {
    1359                 :           0 :       left_axis_area_width = self->cached_continuous_axis_area_width;
    1360                 :           0 :       right_axis_area_width = 0;
    1361                 :             :     }
    1362                 :             :   else
    1363                 :             :     {
    1364                 :           0 :       left_axis_area_width = 0;
    1365                 :           0 :       right_axis_area_width = self->cached_continuous_axis_area_width;
    1366                 :             :     }
    1367                 :             : 
    1368         [ #  # ]:           0 :   if (out_left_axis_area_width != NULL)
    1369                 :           0 :     *out_left_axis_area_width = left_axis_area_width;
    1370         [ #  # ]:           0 :   if (out_right_axis_area_width != NULL)
    1371                 :           0 :     *out_right_axis_area_width = right_axis_area_width;
    1372                 :           0 : }
    1373                 :             : 
    1374                 :             : /* Calculate the x-coordinate bounds of a bar group and its spacing. This is
    1375                 :             :  * done individually for each group, rather than caching a single width for
    1376                 :             :  * groups and multiplying it, so that rounding errors don’t accumulate across
    1377                 :             :  * the width of the plot area. */
    1378                 :             : static inline void
    1379                 :           0 : calculate_group_x_bounds (CcBarChart   *self,
    1380                 :             :                           unsigned int  idx,
    1381                 :             :                           int          *out_spacing_start_x,
    1382                 :             :                           int          *out_group_start_x,
    1383                 :             :                           int          *out_group_finish_x,
    1384                 :             :                           int          *out_spacing_finish_x)
    1385                 :             : {
    1386                 :             :   int widget_width, plot_width, extra_plot_width;
    1387                 :             :   int left_axis_area_width, right_axis_area_width;
    1388                 :             :   int group_width, group_spacing;
    1389                 :             :   int spacing_start_x, group_start_x, group_finish_x, spacing_finish_x;
    1390                 :             : 
    1391                 :           0 :   g_assert (self->n_data > 0);
    1392                 :             : 
    1393                 :           0 :   calculate_axis_area_widths (self, &left_axis_area_width, &right_axis_area_width);
    1394                 :             : 
    1395                 :             :   /* If drawing RTL, reverse the bar positions. */
    1396         [ #  # ]:           0 :   if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
    1397                 :           0 :     idx = self->n_data - idx - 1;
    1398                 :             : 
    1399                 :           0 :   widget_width = gtk_widget_get_width (GTK_WIDGET (self));
    1400                 :           0 :   plot_width = widget_width - left_axis_area_width - right_axis_area_width;
    1401                 :           0 :   extra_plot_width = plot_width - self->cached_minimum_group_width * self->n_data;
    1402                 :             : 
    1403                 :           0 :   group_width = self->cached_minimum_group_width + (extra_plot_width / self->n_data) * GROUP_TO_SPACE_WIDTH_FILL_RATIO;
    1404                 :           0 :   group_spacing = (extra_plot_width / self->n_data) * (1.0 - GROUP_TO_SPACE_WIDTH_FILL_RATIO);
    1405                 :             : 
    1406                 :           0 :   spacing_start_x = left_axis_area_width + plot_width * idx / self->n_data;
    1407                 :           0 :   group_start_x = spacing_start_x + group_spacing / 2;
    1408                 :           0 :   group_finish_x = group_start_x + group_width;
    1409                 :           0 :   spacing_finish_x = left_axis_area_width + plot_width * (idx + 1) / self->n_data;
    1410                 :             : 
    1411                 :           0 :   g_assert (spacing_start_x <= group_start_x &&
    1412                 :             :             group_start_x <= group_finish_x &&
    1413                 :             :             group_finish_x <= spacing_finish_x);
    1414                 :             : 
    1415         [ #  # ]:           0 :   if (out_spacing_start_x != NULL)
    1416                 :           0 :     *out_spacing_start_x = spacing_start_x;
    1417         [ #  # ]:           0 :   if (out_group_start_x != NULL)
    1418                 :           0 :     *out_group_start_x = group_start_x;
    1419         [ #  # ]:           0 :   if (out_group_finish_x != NULL)
    1420                 :           0 :     *out_group_finish_x = group_finish_x;
    1421         [ #  # ]:           0 :   if (out_spacing_finish_x != NULL)
    1422                 :           0 :     *out_spacing_finish_x = spacing_finish_x;
    1423                 :           0 : }
    1424                 :             : 
    1425                 :             : /* Convert a value from the domain of self->data to widget coordinates. */
    1426                 :             : static int
    1427                 :           0 : value_to_widget_y (CcBarChart *self,
    1428                 :             :                    double      value)
    1429                 :             : {
    1430                 :           0 :   int height = gtk_widget_get_height (GTK_WIDGET (self));
    1431                 :             : 
    1432                 :             :   /* Negative values are not currently supported. */
    1433                 :           0 :   g_assert (value >= 0.0);
    1434                 :             : 
    1435                 :             :   /* The widget should be sized to accommodate all values in the data. */
    1436                 :           0 :   g_assert (self->cached_pixels_per_data * value <= height - self->cached_discrete_axis_area_height);
    1437                 :             : 
    1438                 :           0 :   return height - self->cached_discrete_axis_area_height - self->cached_pixels_per_data * value;
    1439                 :             : }
    1440                 :             : 
    1441                 :             : /* returns floating reference */
    1442                 :             : static GtkLabel *
    1443                 :           0 : create_discrete_axis_label (const char *text)
    1444                 :             : {
    1445                 :           0 :   GtkLabel *label = GTK_LABEL (gtk_label_new (text));
    1446                 :           0 :   gtk_label_set_xalign (label, 0.5);
    1447                 :           0 :   gtk_widget_add_css_class (GTK_WIDGET (label), "discrete-axis-label");
    1448                 :             : 
    1449                 :           0 :   return g_steal_pointer (&label);
    1450                 :             : }
    1451                 :             : 
    1452                 :             : /* returns floating reference */
    1453                 :             : static GtkLabel *
    1454                 :           0 : create_continuous_axis_label (const char *text)
    1455                 :             : {
    1456                 :           0 :   GtkLabel *label = GTK_LABEL (gtk_label_new (text));
    1457                 :           0 :   gtk_label_set_xalign (label, 0.0);
    1458                 :           0 :   gtk_label_set_yalign (label, 0.0);
    1459                 :           0 :   gtk_widget_set_valign (GTK_WIDGET (label), GTK_ALIGN_BASELINE_CENTER);
    1460                 :           0 :   gtk_widget_add_css_class (GTK_WIDGET (label), "continuous-axis-label");
    1461                 :             : 
    1462                 :           0 :   return g_steal_pointer (&label);
    1463                 :             : }
    1464                 :             : 
    1465                 :             : static char *
    1466                 :           0 : format_continuous_axis_label (CcBarChart *self,
    1467                 :             :                               double      value)
    1468                 :             : {
    1469                 :           0 :   g_autofree char *out = NULL;
    1470                 :             : 
    1471                 :           0 :   g_assert (self->continuous_axis_label_callback != NULL);
    1472                 :             : 
    1473                 :           0 :   out = self->continuous_axis_label_callback (self, value, self->continuous_axis_label_user_data);
    1474                 :           0 :   g_assert (out != NULL);
    1475                 :             : 
    1476                 :           0 :   return g_steal_pointer (&out);
    1477                 :             : }
    1478                 :             : 
    1479                 :             : static double
    1480                 :           0 : get_maximum_data_value (CcBarChart *self,
    1481                 :             :                         gboolean    include_overlay_line)
    1482                 :             : {
    1483                 :           0 :   double value = 0.0;
    1484                 :             : 
    1485                 :           0 :   g_assert (self->data != NULL);
    1486                 :             : 
    1487         [ #  # ]:           0 :   for (size_t i = 0; i < self->n_data; i++)
    1488                 :             :     {
    1489         [ #  # ]:           0 :       if (!isnan (self->data[i]))
    1490         [ #  # ]:           0 :         value = MAX (value, self->data[i]);
    1491                 :             :     }
    1492                 :             : 
    1493   [ #  #  #  # ]:           0 :   if (include_overlay_line && !isnan (self->overlay_line_value))
    1494         [ #  # ]:           0 :     value = MAX (value, self->overlay_line_value);
    1495                 :             : 
    1496                 :           0 :   return value;
    1497                 :             : }
    1498                 :             : 
    1499                 :             : static void
    1500                 :           0 : update_group_accessible_relations (CcBarChart *self)
    1501                 :             : {
    1502                 :           0 :   gtk_accessible_update_relation (GTK_ACCESSIBLE (self),
    1503                 :             :                                   GTK_ACCESSIBLE_RELATION_ROW_COUNT, self->n_data,
    1504                 :             :                                   -1);
    1505                 :             : 
    1506         [ #  # ]:           0 :   if (self->cached_groups != NULL)
    1507                 :             :     {
    1508         [ #  # ]:           0 :       for (unsigned int i = 0; i < self->cached_groups->len; i++)
    1509                 :             :         {
    1510                 :           0 :           CcBarChartGroup *group = self->cached_groups->pdata[i];
    1511                 :             : 
    1512                 :           0 :           gtk_accessible_update_relation (GTK_ACCESSIBLE (group),
    1513                 :             :                                           GTK_ACCESSIBLE_RELATION_ROW_INDEX, i,
    1514                 :             :                                           -1);
    1515                 :             :         }
    1516                 :             :     }
    1517                 :             : 
    1518         [ #  # ]:           0 :   if (self->cached_groups != NULL &&
    1519         [ #  # ]:           0 :       self->cached_discrete_axis_labels != NULL)
    1520                 :             :     {
    1521                 :           0 :       g_assert (self->cached_groups->len == self->cached_discrete_axis_labels->len);
    1522                 :             : 
    1523         [ #  # ]:           0 :       for (unsigned int i = 0; i < self->cached_groups->len; i++)
    1524                 :             :         {
    1525                 :           0 :           CcBarChartGroup *group = self->cached_groups->pdata[i];
    1526                 :           0 :           GtkLabel *label = self->cached_discrete_axis_labels->pdata[i];
    1527                 :           0 :           CcBarChartBar * const *bars = NULL;
    1528                 :           0 :           size_t n_bars = 0;
    1529                 :             : 
    1530                 :           0 :           gtk_accessible_update_relation (GTK_ACCESSIBLE (group),
    1531                 :             :                                           GTK_ACCESSIBLE_RELATION_LABELLED_BY, label, NULL,
    1532                 :             :                                           -1);
    1533                 :             : 
    1534                 :           0 :           bars = cc_bar_chart_group_get_bars (group, &n_bars);
    1535         [ #  # ]:           0 :           for (unsigned int j = 0; j < n_bars; j++)
    1536                 :             :             {
    1537                 :           0 :               gtk_accessible_update_relation (GTK_ACCESSIBLE (bars[j]),
    1538                 :             :                                               GTK_ACCESSIBLE_RELATION_LABELLED_BY, label, NULL,
    1539                 :             :                                               -1);
    1540                 :             :             }
    1541                 :             :         }
    1542                 :             :     }
    1543                 :           0 : }
    1544                 :             : 
    1545                 :             : /**
    1546                 :             :  * cc_bar_chart_new:
    1547                 :             :  *
    1548                 :             :  * Create a new #CcBarChart.
    1549                 :             :  *
    1550                 :             :  * Returns: (transfer full): the new #CcBarChart
    1551                 :             :  */
    1552                 :             : CcBarChart *
    1553                 :           0 : cc_bar_chart_new (void)
    1554                 :             : {
    1555                 :           0 :   return g_object_new (CC_TYPE_BAR_CHART, NULL);
    1556                 :             : }
    1557                 :             : 
    1558                 :             : /**
    1559                 :             :  * cc_bar_chart_get_discrete_axis_labels:
    1560                 :             :  * @self: a #CcBarChart
    1561                 :             :  * @out_n_discrete_axis_labels: (out) (optional): return location for the number
    1562                 :             :  *   of labels, or `NULL` to ignore
    1563                 :             :  *
    1564                 :             :  * Get the discrete axis labels for the chart.
    1565                 :             :  *
    1566                 :             :  * This will be `NULL` if no labels have been set yet, in which case `0` will be
    1567                 :             :  * returned in @out_n_discrete_axis_labels.
    1568                 :             :  *
    1569                 :             :  * See #CcBarChart:discrete-axis-labels.
    1570                 :             :  *
    1571                 :             :  * Returns: (nullable) (array zero-terminated=1 length=out_n_discrete_axis_labels) (transfer none): array
    1572                 :             :  *   of discrete axis labels
    1573                 :             :  */
    1574                 :             : const char * const *
    1575                 :           0 : cc_bar_chart_get_discrete_axis_labels (CcBarChart *self,
    1576                 :             :                                        size_t     *out_n_discrete_axis_labels)
    1577                 :             : {
    1578                 :           0 :   g_return_val_if_fail (CC_IS_BAR_CHART (self), NULL);
    1579                 :             : 
    1580         [ #  # ]:           0 :   if (out_n_discrete_axis_labels != NULL)
    1581                 :           0 :     *out_n_discrete_axis_labels = self->n_discrete_axis_labels;
    1582                 :             : 
    1583                 :           0 :   return (const char * const *) self->discrete_axis_labels;
    1584                 :             : }
    1585                 :             : 
    1586                 :             : /**
    1587                 :             :  * cc_bar_chart_set_discrete_axis_labels:
    1588                 :             :  * @self: a #CcBarChart
    1589                 :             :  * @labels: (array zero-terminated=1) (nullable) (transfer none): new set of
    1590                 :             :  *   discrete axis labels, or `NULL` to unset
    1591                 :             :  *
    1592                 :             :  * Set the discrete axis labels for the chart.
    1593                 :             :  *
    1594                 :             :  * This can be `NULL` if the labels are currently unknown.
    1595                 :             :  *
    1596                 :             :  * See #CcBarChart:discrete-axis-labels.
    1597                 :             :  */
    1598                 :             : void
    1599                 :           0 : cc_bar_chart_set_discrete_axis_labels (CcBarChart         *self,
    1600                 :             :                                        const char * const *labels)
    1601                 :             : {
    1602                 :           0 :   g_return_if_fail (CC_IS_BAR_CHART (self));
    1603                 :             : 
    1604   [ #  #  #  # ]:           0 :   if ((self->discrete_axis_labels == NULL && labels == NULL) ||
    1605   [ #  #  #  #  :           0 :       (self->discrete_axis_labels != NULL && labels != NULL &&
                   #  # ]
    1606                 :           0 :        g_strv_equal ((const char * const *) self->discrete_axis_labels, labels)))
    1607                 :           0 :     return;
    1608                 :             : 
    1609                 :           0 :   g_strfreev (self->discrete_axis_labels);
    1610                 :           0 :   self->discrete_axis_labels = g_strdupv ((char **) labels);
    1611         [ #  # ]:           0 :   self->n_discrete_axis_labels = (labels != NULL) ? g_strv_length ((char **) labels) : 0;
    1612                 :             : 
    1613                 :             :   /* Rebuild the cache */
    1614         [ #  # ]:           0 :   g_clear_pointer (&self->cached_discrete_axis_labels, g_ptr_array_unref);
    1615         [ #  # ]:           0 :   if (self->n_discrete_axis_labels > 0)
    1616                 :           0 :     self->cached_discrete_axis_labels = g_ptr_array_new_with_free_func ((GDestroyNotify) gtk_widget_unparent);
    1617                 :             : 
    1618         [ #  # ]:           0 :   for (size_t i = 0; i < self->n_discrete_axis_labels; i++)
    1619                 :             :     {
    1620                 :           0 :       GtkLabel *label = create_discrete_axis_label (self->discrete_axis_labels[i]);
    1621                 :           0 :       gtk_widget_set_parent (GTK_WIDGET (label), GTK_WIDGET (self));
    1622                 :             :       /* don’t insert the label into the widget child order using
    1623                 :             :        * gtk_widget_insert_after(), as it shouldn’t be focusable */
    1624                 :           0 :       g_ptr_array_add (self->cached_discrete_axis_labels, label);
    1625                 :             :     }
    1626                 :             : 
    1627                 :           0 :   update_group_accessible_relations (self);
    1628                 :             : 
    1629                 :             :   /* Re-render */
    1630                 :           0 :   gtk_widget_queue_resize (GTK_WIDGET (self));
    1631                 :             : 
    1632                 :           0 :   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DISCRETE_AXIS_LABELS]);
    1633                 :             : }
    1634                 :             : 
    1635                 :             : /**
    1636                 :             :  * cc_bar_chart_set_continuous_axis_label_callback:
    1637                 :             :  * @self: a #CcBarChart
    1638                 :             :  * @callback: (nullable): callback to generate continuous axis labels, or
    1639                 :             :  *   `NULL` to disable them
    1640                 :             :  * @user_data: (nullable) (closure callback): user data for @callback
    1641                 :             :  * @destroy_notify: (nullable) (destroy callback): destroy function for @user_data
    1642                 :             :  *
    1643                 :             :  * Set the callback to generate labels for the continuous axis.
    1644                 :             :  *
    1645                 :             :  * This is called multiple times when sizing and rendering the chart, to
    1646                 :             :  * generate the labels for the continuous axis. Grid lines and marks are
    1647                 :             :  * generated, then some of them are converted to textual labels using @callback.
    1648                 :             :  */
    1649                 :             : void
    1650                 :           0 : cc_bar_chart_set_continuous_axis_label_callback (CcBarChart              *self,
    1651                 :             :                                                  CcBarChartLabelCallback  callback,
    1652                 :             :                                                  void                    *user_data,
    1653                 :             :                                                  GDestroyNotify           destroy_notify)
    1654                 :             : {
    1655                 :           0 :   g_return_if_fail (CC_IS_BAR_CHART (self));
    1656                 :             : 
    1657         [ #  # ]:           0 :   if (self->continuous_axis_label_callback == callback &&
    1658         [ #  # ]:           0 :       self->continuous_axis_label_user_data == user_data &&
    1659         [ #  # ]:           0 :       self->continuous_axis_label_destroy_notify == destroy_notify)
    1660                 :           0 :     return;
    1661                 :             : 
    1662         [ #  # ]:           0 :   if (self->continuous_axis_label_destroy_notify != NULL)
    1663                 :           0 :     self->continuous_axis_label_destroy_notify (self->continuous_axis_label_user_data);
    1664                 :             : 
    1665                 :           0 :   self->continuous_axis_label_callback = callback;
    1666                 :           0 :   self->continuous_axis_label_user_data = user_data;
    1667                 :           0 :   self->continuous_axis_label_destroy_notify = destroy_notify;
    1668                 :             : 
    1669                 :             :   /* Clear the old cache */
    1670         [ #  # ]:           0 :   g_clear_pointer (&self->cached_continuous_axis_labels, g_ptr_array_unref);
    1671                 :             : 
    1672                 :             :   /* Re-render */
    1673                 :           0 :   gtk_widget_queue_resize (GTK_WIDGET (self));
    1674                 :             : }
    1675                 :             : 
    1676                 :             : /**
    1677                 :             :  * cc_bar_chart_set_continuous_axis_grid_line_callback:
    1678                 :             :  * @self: a #CcBarChart
    1679                 :             :  * @callback: (nullable): callback to generate continuous axis grid lines, or
    1680                 :             :  *   `NULL` to disable them
    1681                 :             :  * @user_data: (nullable) (closure callback): user data for @callback
    1682                 :             :  * @destroy_notify: (nullable) (destroy callback): destroy function for @user_data
    1683                 :             :  *
    1684                 :             :  * Set the callback to generate grid lines for the continuous axis.
    1685                 :             :  *
    1686                 :             :  * This is called multiple times when sizing and rendering the chart, to
    1687                 :             :  * generate the grid lines for the continuous axis. See the documentation for
    1688                 :             :  * #CcBarChartGridLineCallback for further details.
    1689                 :             :  */
    1690                 :             : void
    1691                 :           0 : cc_bar_chart_set_continuous_axis_grid_line_callback (CcBarChart                 *self,
    1692                 :             :                                                      CcBarChartGridLineCallback  callback,
    1693                 :             :                                                      void                       *user_data,
    1694                 :             :                                                      GDestroyNotify              destroy_notify)
    1695                 :             : {
    1696                 :           0 :   g_return_if_fail (CC_IS_BAR_CHART (self));
    1697                 :             : 
    1698         [ #  # ]:           0 :   if (self->continuous_axis_grid_line_callback == callback &&
    1699         [ #  # ]:           0 :       self->continuous_axis_grid_line_user_data == user_data &&
    1700         [ #  # ]:           0 :       self->continuous_axis_grid_line_destroy_notify == destroy_notify)
    1701                 :           0 :     return;
    1702                 :             : 
    1703         [ #  # ]:           0 :   if (self->continuous_axis_grid_line_destroy_notify != NULL)
    1704                 :           0 :     self->continuous_axis_grid_line_destroy_notify (self->continuous_axis_grid_line_user_data);
    1705                 :             : 
    1706                 :           0 :   self->continuous_axis_grid_line_callback = callback;
    1707                 :           0 :   self->continuous_axis_grid_line_user_data = user_data;
    1708                 :           0 :   self->continuous_axis_grid_line_destroy_notify = destroy_notify;
    1709                 :             : 
    1710                 :             :   /* Clear the old cache, including the labels */
    1711         [ #  # ]:           0 :   g_clear_pointer (&self->cached_continuous_axis_grid_line_values, g_array_unref);
    1712         [ #  # ]:           0 :   g_clear_pointer (&self->cached_continuous_axis_labels, g_ptr_array_unref);
    1713                 :             : 
    1714                 :             :   /* Re-render */
    1715                 :           0 :   gtk_widget_queue_resize (GTK_WIDGET (self));
    1716                 :             : }
    1717                 :             : 
    1718                 :             : /**
    1719                 :             :  * cc_bar_chart_get_data:
    1720                 :             :  * @self: a #CcBarChart
    1721                 :             :  * @out_n_data: (out) (not optional): return location for the number of data
    1722                 :             :  *
    1723                 :             :  * Get the data for the bar chart.
    1724                 :             :  *
    1725                 :             :  * If no data is currently set, `NULL` will be returned and @out_n_data will be
    1726                 :             :  * set to `0`.
    1727                 :             :  *
    1728                 :             :  * Returns: (array length=out_n_data) (nullable) (transfer none): data for the
    1729                 :             :  *   chart, or `NULL` if it’s not currently set
    1730                 :             :  */
    1731                 :             : const double *
    1732                 :           0 : cc_bar_chart_get_data (CcBarChart *self,
    1733                 :             :                        size_t     *out_n_data)
    1734                 :             : {
    1735                 :           0 :   g_return_val_if_fail (CC_IS_BAR_CHART (self), NULL);
    1736                 :           0 :   g_return_val_if_fail (out_n_data != NULL, NULL);
    1737                 :             : 
    1738                 :           0 :   *out_n_data = self->n_data;
    1739                 :             : 
    1740                 :             :   /* Normalise to `NULL` */
    1741         [ #  # ]:           0 :   return (self->n_data != 0) ? self->data : NULL;
    1742                 :             : }
    1743                 :             : 
    1744                 :             : /**
    1745                 :             :  * cc_bar_chart_set_data:
    1746                 :             :  * @self: a #CcBarChart
    1747                 :             :  * @data: (array length=n_data) (nullable) (transfer none): data for the bar
    1748                 :             :  *   chart, or `NULL` to unset
    1749                 :             :  * @n_data: number of data
    1750                 :             :  *
    1751                 :             :  * Set the data for the bar chart.
    1752                 :             :  *
    1753                 :             :  * To clear the data for the chart, pass `NULL` for @data and `0` for @n_data.
    1754                 :             :  */
    1755                 :             : void
    1756                 :           0 : cc_bar_chart_set_data (CcBarChart   *self,
    1757                 :             :                        const double *data,
    1758                 :             :                        size_t        n_data)
    1759                 :             : {
    1760                 :           0 :   g_return_if_fail (CC_IS_BAR_CHART (self));
    1761                 :           0 :   g_return_if_fail (n_data == 0 || data != NULL);
    1762                 :           0 :   g_return_if_fail (n_data <= G_MAXSIZE / sizeof (*data));
    1763                 :             : 
    1764                 :             :   /* Normalise input. */
    1765         [ #  # ]:           0 :   if (n_data == 0)
    1766                 :           0 :     data = NULL;
    1767                 :             : 
    1768   [ #  #  #  # ]:           0 :   if (self->data == NULL && data == NULL)
    1769                 :           0 :     return;
    1770                 :             : 
    1771   [ #  #  #  #  :           0 :   if (self->data != NULL && data != NULL && self->n_data == n_data &&
                   #  # ]
    1772         [ #  # ]:           0 :       memcmp (self->data, data, n_data * sizeof (*data)) == 0)
    1773                 :           0 :     return;
    1774                 :             : 
    1775         [ #  # ]:           0 :   g_clear_pointer (&self->data, g_free);
    1776                 :           0 :   self->data = g_memdup2 (data, n_data * sizeof (*data));
    1777                 :           0 :   self->n_data = n_data;
    1778                 :             : 
    1779                 :             :   /* Clear the cached bars, and also the grid lines and labels which are calculated based on the data. */
    1780         [ #  # ]:           0 :   g_clear_pointer (&self->cached_groups, g_ptr_array_unref);
    1781         [ #  # ]:           0 :   g_clear_pointer (&self->cached_continuous_axis_grid_line_values, g_array_unref);
    1782         [ #  # ]:           0 :   g_clear_pointer (&self->cached_continuous_axis_labels, g_ptr_array_unref);
    1783                 :             : 
    1784                 :             :   /* Also clear the selection index. */
    1785                 :           0 :   self->selected_index_set = FALSE;
    1786                 :           0 :   self->selected_index = 0;
    1787                 :             : 
    1788                 :             :   /* Rebuild the cache. Currently we support exactly at most one bar per group.
    1789                 :             :    * There will be zero bars in groups where the data is NAN (i.e. not provided). */
    1790         [ #  # ]:           0 :   if (self->n_data > 0)
    1791                 :           0 :     self->cached_groups = g_ptr_array_new_with_free_func ((GDestroyNotify) gtk_widget_unparent);
    1792                 :             : 
    1793         [ #  # ]:           0 :   for (size_t i = 0; i < self->n_data; i++)
    1794                 :             :     {
    1795                 :           0 :       CcBarChartGroup *group = cc_bar_chart_group_new ();
    1796         [ #  # ]:           0 :       CcBarChartGroup *previous_group = (i > 0) ? self->cached_groups->pdata[i - 1] : NULL;
    1797                 :           0 :       g_autofree char *accessible_label = format_continuous_axis_label (self, self->data[i]);
    1798                 :             : 
    1799                 :           0 :       g_signal_connect (group, "notify::selected-index", G_CALLBACK (group_notify_selected_index_cb), self);
    1800                 :           0 :       g_signal_connect (group, "notify::is-selected", G_CALLBACK (group_notify_selected_index_cb), self);
    1801                 :           0 :       cc_bar_chart_group_set_selectable (group, isnan (self->data[i]));
    1802                 :           0 :       cc_bar_chart_group_set_scale (group, self->cached_pixels_per_data);
    1803                 :             : 
    1804         [ #  # ]:           0 :       if (!isnan (self->data[i]))
    1805                 :             :         {
    1806                 :           0 :           CcBarChartBar *bar = cc_bar_chart_bar_new (self->data[i], accessible_label);
    1807                 :           0 :           cc_bar_chart_group_insert_bar (group, -1, bar);
    1808                 :           0 :           g_signal_connect (bar, "activate", G_CALLBACK (bar_activate_cb), self);
    1809                 :             :         }
    1810                 :             : 
    1811                 :           0 :       gtk_widget_set_parent (GTK_WIDGET (group), GTK_WIDGET (self));
    1812                 :           0 :       gtk_widget_insert_after (GTK_WIDGET (group), GTK_WIDGET (self), GTK_WIDGET (previous_group));
    1813                 :           0 :       g_ptr_array_add (self->cached_groups, group);
    1814                 :             :     }
    1815                 :             : 
    1816                 :           0 :   update_group_accessible_relations (self);
    1817                 :             : 
    1818                 :             :   /* Re-render */
    1819                 :           0 :   gtk_widget_queue_resize (GTK_WIDGET (self));
    1820                 :             : 
    1821                 :           0 :   g_signal_emit (self, signals[SIGNAL_DATA_CHANGED], 0);
    1822                 :             : }
    1823                 :             : 
    1824                 :             : /**
    1825                 :             :  * cc_bar_chart_get_overlay_line_value:
    1826                 :             :  * @self: a #CcBarChart
    1827                 :             :  *
    1828                 :             :  * Get the value of #CcBarChart:overlay-line-value.
    1829                 :             :  *
    1830                 :             :  * Returns: value to render an overlay line at, or `NAN` if unset
    1831                 :             :  */
    1832                 :             : double
    1833                 :           0 : cc_bar_chart_get_overlay_line_value (CcBarChart *self)
    1834                 :             : {
    1835                 :           0 :   g_return_val_if_fail (CC_IS_BAR_CHART (self), NAN);
    1836                 :             : 
    1837                 :           0 :   return self->overlay_line_value;
    1838                 :             : }
    1839                 :             : 
    1840                 :             : /**
    1841                 :             :  * cc_bar_chart_set_overlay_line_value:
    1842                 :             :  * @self: a #CcBarChart
    1843                 :             :  * @value: value to render an overlay line at, or `NAN` to not render one
    1844                 :             :  *
    1845                 :             :  * Set the value of #CcBarChart:overlay-line-value.
    1846                 :             :  */
    1847                 :             : void
    1848                 :           0 : cc_bar_chart_set_overlay_line_value (CcBarChart *self,
    1849                 :             :                                      double      value)
    1850                 :             : {
    1851                 :           0 :   g_return_if_fail (CC_IS_BAR_CHART (self));
    1852                 :             : 
    1853   [ #  #  #  # ]:           0 :   if ((isnan (self->overlay_line_value) && isnan (value)) ||
    1854         [ #  # ]:           0 :       self->overlay_line_value == value)
    1855                 :           0 :     return;
    1856                 :             : 
    1857                 :           0 :   self->overlay_line_value = value;
    1858                 :             : 
    1859                 :             :   /* Clear the cached grid lines and labels as the overlay line might have been
    1860                 :             :    * the highest data value. */
    1861         [ #  # ]:           0 :   g_clear_pointer (&self->cached_continuous_axis_grid_line_values, g_array_unref);
    1862         [ #  # ]:           0 :   g_clear_pointer (&self->cached_continuous_axis_labels, g_ptr_array_unref);
    1863                 :             : 
    1864                 :             :   /* Re-render */
    1865                 :           0 :   gtk_widget_queue_resize (GTK_WIDGET (self));
    1866                 :             : 
    1867                 :           0 :   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_OVERLAY_LINE_VALUE]);
    1868                 :             : }
    1869                 :             : 
    1870                 :             : /**
    1871                 :             :  * cc_bar_chart_get_selected_index:
    1872                 :             :  * @self: a #CcBarChart
    1873                 :             :  * @out_index: (out) (optional): return location for the selected index, or
    1874                 :             :  *   `NULL` to ignore
    1875                 :             :  *
    1876                 :             :  * Get the currently selected data index.
    1877                 :             :  *
    1878                 :             :  * If nothing is currently selected, @out_index will be set to `0` and `FALSE`
    1879                 :             :  * will be returned.
    1880                 :             :  *
    1881                 :             :  * Returns: `TRUE` if something is currently selected, `FALSE` otherwise
    1882                 :             :  */
    1883                 :             : gboolean
    1884                 :           0 : cc_bar_chart_get_selected_index (CcBarChart *self,
    1885                 :             :                                  size_t     *out_index)
    1886                 :             : {
    1887                 :           0 :   g_return_val_if_fail (CC_IS_BAR_CHART (self), FALSE);
    1888                 :             : 
    1889         [ #  # ]:           0 :   if (out_index != NULL)
    1890         [ #  # ]:           0 :     *out_index = self->selected_index_set ? self->selected_index : 0;
    1891                 :             : 
    1892                 :           0 :   return self->selected_index_set;
    1893                 :             : }
    1894                 :             : 
    1895                 :             : /**
    1896                 :             :  * cc_bar_chart_set_selected_index:
    1897                 :             :  * @self: a #CcBarChart
    1898                 :             :  * @is_selected: `TRUE` if something should be selected, `FALSE` if everything
    1899                 :             :  *   should be unselected
    1900                 :             :  * @idx: index of the data to select, ignored if @is_selected is `FALSE`
    1901                 :             :  *
    1902                 :             :  * Set the currently selected data index, or unselect everything.
    1903                 :             :  *
    1904                 :             :  * If @is_selected is `TRUE`, the data at @idx will be selected. If @is_selected
    1905                 :             :  * is `FALSE`, @idx will be ignored and all data will be unselected.
    1906                 :             :  */
    1907                 :             : void
    1908                 :           0 : cc_bar_chart_set_selected_index (CcBarChart *self,
    1909                 :             :                                  gboolean    is_selected,
    1910                 :             :                                  size_t      idx)
    1911                 :             : {
    1912                 :           0 :   g_return_if_fail (CC_IS_BAR_CHART (self));
    1913                 :           0 :   g_return_if_fail (!is_selected || idx < self->n_data);
    1914                 :             : 
    1915         [ #  # ]:           0 :   if (self->selected_index_set == is_selected &&
    1916   [ #  #  #  # ]:           0 :       (!self->selected_index_set || self->selected_index == idx))
    1917                 :           0 :     return;
    1918                 :             : 
    1919                 :             :   /* Clear the old selection. */
    1920         [ #  # ]:           0 :   if (self->selected_index_set)
    1921                 :             :     {
    1922                 :           0 :       g_assert (self->cached_groups != NULL);
    1923                 :           0 :       g_signal_handlers_block_by_func (self->cached_groups->pdata[self->selected_index],
    1924                 :             :                                        group_notify_selected_index_cb, self);
    1925                 :           0 :       cc_bar_chart_group_set_is_selected (self->cached_groups->pdata[self->selected_index], FALSE);
    1926                 :           0 :       g_signal_handlers_unblock_by_func (self->cached_groups->pdata[self->selected_index],
    1927                 :             :                                          group_notify_selected_index_cb, self);
    1928                 :             :     }
    1929                 :             : 
    1930                 :           0 :   self->selected_index_set = is_selected;
    1931         [ #  # ]:           0 :   self->selected_index = is_selected ? idx : 0;
    1932                 :             : 
    1933                 :             :   /* Set the new selection. */
    1934         [ #  # ]:           0 :   if (is_selected)
    1935                 :             :     {
    1936                 :           0 :       size_t n_bars = 0;
    1937                 :             : 
    1938                 :           0 :       g_assert (self->cached_groups != NULL);
    1939                 :           0 :       g_signal_handlers_block_by_func (self->cached_groups->pdata[idx],
    1940                 :             :                                        group_notify_selected_index_cb, self);
    1941                 :           0 :       cc_bar_chart_group_get_bars (self->cached_groups->pdata[idx], &n_bars);
    1942         [ #  # ]:           0 :       if (n_bars > 0)
    1943                 :           0 :         cc_bar_chart_group_set_selected_index (self->cached_groups->pdata[idx], TRUE, 0);
    1944                 :             :       else
    1945                 :           0 :         cc_bar_chart_group_set_is_selected (self->cached_groups->pdata[idx], TRUE);
    1946                 :           0 :       g_signal_handlers_unblock_by_func (self->cached_groups->pdata[idx],
    1947                 :             :                                          group_notify_selected_index_cb, self);
    1948                 :             :     }
    1949                 :             : 
    1950                 :             :   /* Re-render */
    1951                 :           0 :   gtk_widget_queue_draw (GTK_WIDGET (self));
    1952                 :             : 
    1953                 :           0 :   g_object_freeze_notify (G_OBJECT (self));
    1954                 :           0 :   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SELECTED_INDEX]);
    1955                 :           0 :   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SELECTED_INDEX_SET]);
    1956                 :           0 :   g_object_thaw_notify (G_OBJECT (self));
    1957                 :             : }
        

Generated by: LCOV version 2.0-1