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 <glib.h>
25 : : #include <glib-object.h>
26 : : #include <gtk/gtk.h>
27 : : #include <math.h>
28 : :
29 : : #include "cc-bar-chart-bar.h"
30 : :
31 : : /**
32 : : * CcBarChartBar:
33 : : *
34 : : * #CcBarChartBar is an individual bar in a #CcBarChart.
35 : : *
36 : : * Bars may only be placed into a #CcBarChart as children of #CcBarChartGroup.
37 : : *
38 : : * A bar may be selected, indicated by #CcBarChartBar:is-selected. They may also
39 : : * be activated by gtk_widget_activate(), which will result in
40 : : * #CcBarChartBar::activate being emitted. By default this selects the bar.
41 : : *
42 : : * # CSS nodes
43 : : *
44 : : * |[<!-- language="plain" -->
45 : : * bar[:hover][:selected]
46 : : * ]|
47 : : *
48 : : * #CcBarChartBar uses a single CSS node named `bar`. A bar may have a `:hover`
49 : : * or `:selected` pseudo-selector to indicate whether it’s selected or being
50 : : * hovered over with the mouse.
51 : : *
52 : : * # Accessibility
53 : : *
54 : : * #CcBarChartBar uses the %GTK_ACCESSIBLE_ROLE_LIST_ITEM role.
55 : : *
56 : : * A textual description for its value must be provided.
57 : : */
58 : : struct _CcBarChartBar {
59 : : GtkWidget parent_instance;
60 : :
61 : : /* Configured state: */
62 : : double value;
63 : : char *accessible_description;
64 : : };
65 : :
66 [ # # # # : 0 : G_DEFINE_TYPE (CcBarChartBar, cc_bar_chart_bar, GTK_TYPE_WIDGET)
# # ]
67 : :
68 : : typedef enum {
69 : : PROP_VALUE = 1,
70 : : PROP_ACCESSIBLE_DESCRIPTION,
71 : : } CcBarChartBarProperty;
72 : :
73 : : static GParamSpec *props[PROP_ACCESSIBLE_DESCRIPTION + 1];
74 : :
75 : : typedef enum {
76 : : SIGNAL_ACTIVATE,
77 : : } CcBarChartBarSignal;
78 : :
79 : : static guint signals[SIGNAL_ACTIVATE + 1];
80 : :
81 : : static void cc_bar_chart_bar_get_property (GObject *object,
82 : : guint property_id,
83 : : GValue *value,
84 : : GParamSpec *pspec);
85 : : static void cc_bar_chart_bar_set_property (GObject *object,
86 : : guint property_id,
87 : : const GValue *value,
88 : : GParamSpec *pspec);
89 : : static void cc_bar_chart_bar_dispose (GObject *object);
90 : : static void cc_bar_chart_bar_finalize (GObject *object);
91 : : static void cc_bar_chart_bar_measure (GtkWidget *widget,
92 : : GtkOrientation orientation,
93 : : int for_size,
94 : : int *minimum,
95 : : int *natural,
96 : : int *minimum_baseline,
97 : : int *natural_baseline);
98 : :
99 : : static void
100 : 0 : cc_bar_chart_bar_class_init (CcBarChartBarClass *klass)
101 : : {
102 : 0 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
103 : 0 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
104 : :
105 : 0 : object_class->get_property = cc_bar_chart_bar_get_property;
106 : 0 : object_class->set_property = cc_bar_chart_bar_set_property;
107 : 0 : object_class->dispose = cc_bar_chart_bar_dispose;
108 : 0 : object_class->finalize = cc_bar_chart_bar_finalize;
109 : :
110 : 0 : widget_class->measure = cc_bar_chart_bar_measure;
111 : :
112 : : /**
113 : : * CcBarChartBar:value:
114 : : *
115 : : * The data value represented by the bar.
116 : : *
117 : : * Currently, only non-negative real numbers are supported.
118 : : */
119 : 0 : props[PROP_VALUE] =
120 : 0 : g_param_spec_double ("value",
121 : : NULL, NULL,
122 : : 0.0, G_MAXDOUBLE, 0.0,
123 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
124 : :
125 : : /**
126 : : * CcBarChartBar:accessible-description:
127 : : *
128 : : * An accessible label for the bar.
129 : : *
130 : : * This should succinctly describe the value of the bar, including any
131 : : * necessary units.
132 : : */
133 : 0 : props[PROP_ACCESSIBLE_DESCRIPTION] =
134 : 0 : g_param_spec_string ("accessible-description",
135 : : NULL, NULL,
136 : : NULL,
137 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
138 : :
139 : 0 : g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
140 : :
141 : : /**
142 : : * CcBarChartBar::activate:
143 : : *
144 : : * This is a keybinding signal, which will cause this bar to be activated.
145 : : *
146 : : * If you want to be notified when the user activates a bar (by key or not),
147 : : * use the #CcBarChart::bar-activated signal on the bar’s parent #CcBarChart.
148 : : */
149 : 0 : signals[SIGNAL_ACTIVATE] =
150 : 0 : g_signal_new ("activate",
151 : : G_TYPE_FROM_CLASS (klass),
152 : : G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
153 : : 0, NULL, NULL,
154 : : NULL,
155 : : G_TYPE_NONE, 0);
156 : :
157 : 0 : gtk_widget_class_set_activate_signal (widget_class, signals[SIGNAL_ACTIVATE]);
158 : :
159 : 0 : gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentControl/ui/cc-bar-chart-bar.ui");
160 : :
161 : 0 : gtk_widget_class_set_css_name (widget_class, "bar");
162 : 0 : gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST_ITEM);
163 : 0 : }
164 : :
165 : : static void
166 : 0 : cc_bar_chart_bar_init (CcBarChartBar *self)
167 : : {
168 : 0 : gtk_widget_init_template (GTK_WIDGET (self));
169 : 0 : }
170 : :
171 : : static void
172 : 0 : cc_bar_chart_bar_get_property (GObject *object,
173 : : guint property_id,
174 : : GValue *value,
175 : : GParamSpec *pspec)
176 : : {
177 : 0 : CcBarChartBar *self = CC_BAR_CHART_BAR (object);
178 : :
179 [ # # # ]: 0 : switch ((CcBarChartBarProperty) property_id)
180 : : {
181 : 0 : case PROP_VALUE:
182 : 0 : g_value_set_double (value, cc_bar_chart_bar_get_value (self));
183 : 0 : break;
184 : 0 : case PROP_ACCESSIBLE_DESCRIPTION:
185 : 0 : g_value_set_string (value, cc_bar_chart_bar_get_accessible_description (self));
186 : 0 : break;
187 : 0 : default:
188 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
189 : 0 : break;
190 : : }
191 : 0 : }
192 : :
193 : : static void
194 : 0 : cc_bar_chart_bar_set_property (GObject *object,
195 : : guint property_id,
196 : : const GValue *value,
197 : : GParamSpec *pspec)
198 : : {
199 : 0 : CcBarChartBar *self = CC_BAR_CHART_BAR (object);
200 : :
201 [ # # # ]: 0 : switch ((CcBarChartBarProperty) property_id)
202 : : {
203 : 0 : case PROP_VALUE:
204 : 0 : cc_bar_chart_bar_set_value (self, g_value_get_double (value));
205 : 0 : break;
206 : 0 : case PROP_ACCESSIBLE_DESCRIPTION:
207 : 0 : cc_bar_chart_bar_set_accessible_description (self, g_value_get_string (value));
208 : 0 : break;
209 : 0 : default:
210 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
211 : : }
212 : 0 : }
213 : :
214 : : static void
215 : 0 : cc_bar_chart_bar_dispose (GObject *object)
216 : : {
217 : 0 : gtk_widget_dispose_template (GTK_WIDGET (object), CC_TYPE_BAR_CHART_BAR);
218 : :
219 : 0 : G_OBJECT_CLASS (cc_bar_chart_bar_parent_class)->dispose (object);
220 : 0 : }
221 : :
222 : : static void
223 : 0 : cc_bar_chart_bar_finalize (GObject *object)
224 : : {
225 : 0 : CcBarChartBar *self = CC_BAR_CHART_BAR (object);
226 : :
227 : 0 : g_free (self->accessible_description);
228 : :
229 : 0 : G_OBJECT_CLASS (cc_bar_chart_bar_parent_class)->finalize (object);
230 : 0 : }
231 : :
232 : : static const unsigned int MINIMUM_BAR_WIDTH = 10;
233 : : static const unsigned int NATURAL_BAR_WIDTH = 40;
234 : :
235 : : static void
236 : 0 : cc_bar_chart_bar_measure (GtkWidget *widget,
237 : : GtkOrientation orientation,
238 : : int for_size,
239 : : int *minimum,
240 : : int *natural,
241 : : int *minimum_baseline,
242 : : int *natural_baseline)
243 : : {
244 [ # # ]: 0 : if (orientation == GTK_ORIENTATION_HORIZONTAL)
245 : : {
246 : 0 : *minimum = MINIMUM_BAR_WIDTH;
247 : 0 : *natural = NATURAL_BAR_WIDTH;
248 : : }
249 [ # # ]: 0 : else if (orientation == GTK_ORIENTATION_VERTICAL)
250 : : {
251 : 0 : *minimum = 0;
252 : 0 : *natural = 0;
253 : : }
254 : : else
255 : : {
256 : : g_assert_not_reached ();
257 : : }
258 : 0 : }
259 : :
260 : : /**
261 : : * cc_bar_chart_bar_new:
262 : : * @value: the value the bar represents
263 : : * @accessible_description: an accessible textual description for @value
264 : : *
265 : : * Create a new #CcBarChartBar.
266 : : *
267 : : * Returns: (transfer full): the new #CcBarChartBar
268 : : */
269 : : CcBarChartBar *
270 : 0 : cc_bar_chart_bar_new (double value,
271 : : const char *accessible_description)
272 : : {
273 : 0 : g_return_val_if_fail (!isnan (value), NULL);
274 : 0 : g_return_val_if_fail (accessible_description != NULL, NULL);
275 : :
276 : 0 : return g_object_new (CC_TYPE_BAR_CHART_BAR,
277 : : "value", value,
278 : : "accessible-description", accessible_description,
279 : : NULL);
280 : : }
281 : :
282 : : /**
283 : : * cc_bar_chart_bar_get_value:
284 : : * @self: a #CcBarChartBar
285 : : *
286 : : * Get the value of #CcBarChartBar:value.
287 : : *
288 : : * Returns: value represented by the bar
289 : : */
290 : : double
291 : 0 : cc_bar_chart_bar_get_value (CcBarChartBar *self)
292 : : {
293 : 0 : g_return_val_if_fail (CC_IS_BAR_CHART_BAR (self), NAN);
294 : :
295 : 0 : return self->value;
296 : : }
297 : :
298 : : /**
299 : : * cc_bar_chart_bar_set_value:
300 : : * @self: a #CcBarChartBar
301 : : * @value: value represented by the bar
302 : : *
303 : : * Set the value of #CcBarChartBar:value.
304 : : */
305 : : void
306 : 0 : cc_bar_chart_bar_set_value (CcBarChartBar *self,
307 : : double value)
308 : : {
309 : 0 : g_return_if_fail (CC_IS_BAR_CHART_BAR (self));
310 : 0 : g_return_if_fail (!isnan (value));
311 : 0 : g_return_if_fail (value >= 0.0); /* negative values aren’t currently supported */
312 : :
313 [ # # ]: 0 : if (self->value == value)
314 : 0 : return;
315 : :
316 : 0 : self->value = value;
317 : :
318 : : /* Re-render */
319 : 0 : gtk_widget_queue_resize (GTK_WIDGET (self));
320 : :
321 : 0 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VALUE]);
322 : : }
323 : :
324 : : /**
325 : : * cc_bar_chart_bar_get_accessible_description:
326 : : * @self: a #CcBarChartBar
327 : : *
328 : : * Get the value of #CcBarChartBar:accessible-description.
329 : : *
330 : : * Returns: accessible textual description for the bar’s value
331 : : */
332 : : const char *
333 : 0 : cc_bar_chart_bar_get_accessible_description (CcBarChartBar *self)
334 : : {
335 : 0 : g_return_val_if_fail (CC_IS_BAR_CHART_BAR (self), NULL);
336 : :
337 : 0 : return self->accessible_description;
338 : : }
339 : :
340 : : /**
341 : : * cc_bar_chart_bar_set_accessible_description:
342 : : * @self: a #CcBarChartBar
343 : : * @accessible_description: accessible textual description for the bar’s value
344 : : *
345 : : * Set the value of #CcBarChartBar:accessible-description.
346 : : */
347 : : void
348 : 0 : cc_bar_chart_bar_set_accessible_description (CcBarChartBar *self,
349 : : const char *accessible_description)
350 : : {
351 : 0 : g_return_if_fail (CC_IS_BAR_CHART_BAR (self));
352 : 0 : g_return_if_fail (accessible_description != NULL);
353 : :
354 [ # # ]: 0 : if (!g_set_str (&self->accessible_description, accessible_description))
355 : 0 : return;
356 : :
357 : 0 : gtk_accessible_update_property (GTK_ACCESSIBLE (self),
358 : : GTK_ACCESSIBLE_PROPERTY_DESCRIPTION,
359 : : self->accessible_description,
360 : : -1);
361 : :
362 : 0 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACCESSIBLE_DESCRIPTION]);
363 : : }
|