1/*
2 * Copyright © 2019 Benjamin Otte
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Benjamin Otte <otte@gnome.org>
18 */
19
20#include "config.h"
21
22#include "gtkcolumnviewtitleprivate.h"
23
24#include "gtkcolumnviewprivate.h"
25#include "gtkcolumnviewcolumnprivate.h"
26#include "gtkcolumnviewsorterprivate.h"
27#include "gtkintl.h"
28#include "gtklabel.h"
29#include "gtkwidgetprivate.h"
30#include "gtkbox.h"
31#include "gtkbuiltiniconprivate.h"
32#include "gtkgestureclick.h"
33#include "gtkpopovermenu.h"
34#include "gtknative.h"
35#include "gtkcssnodeprivate.h"
36#include "gtkcssnumbervalueprivate.h"
37
38struct _GtkColumnViewTitle
39{
40 GtkWidget parent_instance;
41
42 GtkColumnViewColumn *column;
43
44 GtkWidget *box;
45 GtkWidget *title;
46 GtkWidget *sort;
47 GtkWidget *popup_menu;
48};
49
50struct _GtkColumnViewTitleClass
51{
52 GtkWidgetClass parent_class;
53};
54
55G_DEFINE_TYPE (GtkColumnViewTitle, gtk_column_view_title, GTK_TYPE_WIDGET)
56
57static int
58get_number (GtkCssValue *value)
59{
60 double d = _gtk_css_number_value_get (number: value, one_hundred_percent: 100);
61
62 if (d < 1)
63 return ceil (x: d);
64 else
65 return floor (x: d);
66}
67
68static int
69unadjust_width (GtkWidget *widget,
70 int width)
71{
72 GtkCssStyle *style;
73 int widget_margins;
74 int css_extra;
75
76 style = gtk_css_node_get_style (cssnode: gtk_widget_get_css_node (widget));
77 css_extra = get_number (value: style->size->margin_left) +
78 get_number (value: style->size->margin_right) +
79 get_number (value: style->border->border_left_width) +
80 get_number (value: style->border->border_right_width) +
81 get_number (value: style->size->padding_left) +
82 get_number (value: style->size->padding_right);
83 widget_margins = widget->priv->margin.left + widget->priv->margin.right;
84
85 return MAX (0, width - widget_margins - css_extra);
86}
87
88static void
89gtk_column_view_title_measure (GtkWidget *widget,
90 GtkOrientation orientation,
91 int for_size,
92 int *minimum,
93 int *natural,
94 int *minimum_baseline,
95 int *natural_baseline)
96{
97 GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (widget);
98 GtkWidget *child = gtk_widget_get_first_child (widget);
99 int fixed_width = gtk_column_view_column_get_fixed_width (self: self->column);
100 int unadj_width;
101
102 unadj_width = unadjust_width (widget, width: fixed_width);
103
104 if (orientation == GTK_ORIENTATION_VERTICAL)
105 {
106 if (fixed_width > -1)
107 {
108 if (for_size == -1)
109 for_size = unadj_width;
110 else
111 for_size = MIN (for_size, unadj_width);
112 }
113 }
114
115 if (child)
116 gtk_widget_measure (widget: child, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline);
117
118 if (orientation == GTK_ORIENTATION_HORIZONTAL)
119 {
120 if (fixed_width > -1)
121 {
122 *minimum = 0;
123 *natural = unadj_width;
124 }
125 }
126}
127
128static void
129gtk_column_view_title_size_allocate (GtkWidget *widget,
130 int width,
131 int height,
132 int baseline)
133{
134 GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (widget);
135 GtkWidget *child = gtk_widget_get_first_child (widget);
136
137 if (child)
138 {
139 int min;
140
141 gtk_widget_measure (widget: child, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: height, minimum: &min, NULL, NULL, NULL);
142
143 gtk_widget_allocate (widget: child, MAX (min, width), height, baseline, NULL);
144 }
145
146 if (self->popup_menu)
147 gtk_popover_present (GTK_POPOVER (self->popup_menu));
148}
149
150static void
151gtk_column_view_title_dispose (GObject *object)
152{
153 GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (object);
154
155 g_clear_pointer (&self->box, gtk_widget_unparent);
156 g_clear_pointer (&self->popup_menu, gtk_widget_unparent);
157
158 g_clear_object (&self->column);
159
160 G_OBJECT_CLASS (gtk_column_view_title_parent_class)->dispose (object);
161}
162
163static void
164gtk_column_view_title_class_init (GtkColumnViewTitleClass *klass)
165{
166 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
167 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
168
169 widget_class->measure = gtk_column_view_title_measure;
170 widget_class->size_allocate = gtk_column_view_title_size_allocate;
171
172 gobject_class->dispose = gtk_column_view_title_dispose;
173
174 gtk_widget_class_set_css_name (widget_class, I_("button"));
175 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_COLUMN_HEADER);
176}
177
178static void
179gtk_column_view_title_resize_func (GtkWidget *widget)
180{
181 GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (widget);
182
183 if (self->column)
184 gtk_column_view_column_queue_resize (self: self->column);
185}
186
187static void
188activate_sort (GtkColumnViewTitle *self)
189{
190 GtkSorter *sorter;
191 GtkColumnView *view;
192 GtkColumnViewSorter *view_sorter;
193
194 sorter = gtk_column_view_column_get_sorter (self: self->column);
195 if (sorter == NULL)
196 return;
197
198 view = gtk_column_view_column_get_column_view (self: self->column);
199 view_sorter = GTK_COLUMN_VIEW_SORTER (ptr: gtk_column_view_get_sorter (self: view));
200 gtk_column_view_sorter_add_column (self: view_sorter, column: self->column);
201}
202
203static void
204show_menu (GtkColumnViewTitle *self,
205 double x,
206 double y)
207{
208 if (!self->popup_menu)
209 {
210 GMenuModel *model;
211
212 model = gtk_column_view_column_get_header_menu (self: self->column);
213 if (!model)
214 return;
215
216 self->popup_menu = gtk_popover_menu_new_from_model (model);
217 gtk_widget_set_parent (widget: self->popup_menu, GTK_WIDGET (self));
218 gtk_popover_set_position (GTK_POPOVER (self->popup_menu), position: GTK_POS_BOTTOM);
219
220 gtk_popover_set_has_arrow (GTK_POPOVER (self->popup_menu), FALSE);
221 gtk_widget_set_halign (widget: self->popup_menu, align: GTK_ALIGN_START);
222 }
223
224 if (x != -1 && y != -1)
225 {
226 GdkRectangle rect = { x, y, 1, 1 };
227 gtk_popover_set_pointing_to (GTK_POPOVER (self->popup_menu), rect: &rect);
228 }
229 else
230 gtk_popover_set_pointing_to (GTK_POPOVER (self->popup_menu), NULL);
231
232 gtk_popover_popup (GTK_POPOVER (self->popup_menu));
233}
234
235static void
236click_released_cb (GtkGestureClick *gesture,
237 guint n_press,
238 double x,
239 double y,
240 GtkWidget *widget)
241{
242 GtkColumnViewTitle *self = GTK_COLUMN_VIEW_TITLE (widget);
243 guint button;
244
245 button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
246
247 if (button == GDK_BUTTON_PRIMARY)
248 activate_sort (self);
249 else if (button == GDK_BUTTON_SECONDARY)
250 show_menu (self, x, y);
251}
252
253static void
254gtk_column_view_title_init (GtkColumnViewTitle *self)
255{
256 GtkWidget *widget = GTK_WIDGET (self);
257 GtkGesture *gesture;
258
259 widget->priv->resize_func = gtk_column_view_title_resize_func;
260
261 gtk_widget_set_overflow (widget, overflow: GTK_OVERFLOW_HIDDEN);
262
263 self->box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0);
264 gtk_widget_set_parent (widget: self->box, parent: widget);
265
266 self->title = gtk_label_new (NULL);
267 gtk_box_append (GTK_BOX (self->box), child: self->title);
268
269 self->sort = gtk_builtin_icon_new (css_name: "sort-indicator");
270 gtk_box_append (GTK_BOX (self->box), child: self->sort);
271
272 gesture = gtk_gesture_click_new ();
273 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), button: 0);
274 g_signal_connect (gesture, "released", G_CALLBACK (click_released_cb), self);
275 gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
276}
277
278GtkWidget *
279gtk_column_view_title_new (GtkColumnViewColumn *column)
280{
281 GtkColumnViewTitle *title;
282
283 title = g_object_new (GTK_TYPE_COLUMN_VIEW_TITLE, NULL);
284
285 title->column = g_object_ref (column);
286 gtk_column_view_title_update (self: title);
287
288 return GTK_WIDGET (title);
289}
290
291void
292gtk_column_view_title_update (GtkColumnViewTitle *self)
293{
294 GtkSorter *sorter;
295 GtkColumnView *view;
296 GtkColumnViewSorter *view_sorter;
297 gboolean inverted;
298 GtkColumnViewColumn *active;
299
300 gtk_label_set_label (GTK_LABEL (self->title), str: gtk_column_view_column_get_title (self: self->column));
301
302 sorter = gtk_column_view_column_get_sorter (self: self->column);
303
304 if (sorter)
305 {
306 view = gtk_column_view_column_get_column_view (self: self->column);
307 view_sorter = GTK_COLUMN_VIEW_SORTER (ptr: gtk_column_view_get_sorter (self: view));
308 active = gtk_column_view_sorter_get_sort_column (self: view_sorter, inverted: &inverted);
309
310 gtk_widget_show (widget: self->sort);
311 gtk_widget_remove_css_class (widget: self->sort, css_class: "ascending");
312 gtk_widget_remove_css_class (widget: self->sort, css_class: "descending");
313 gtk_widget_remove_css_class (widget: self->sort, css_class: "unsorted");
314 if (self->column != active)
315 gtk_widget_add_css_class (widget: self->sort, css_class: "unsorted");
316 else if (inverted)
317 gtk_widget_add_css_class (widget: self->sort, css_class: "descending");
318 else
319 gtk_widget_add_css_class (widget: self->sort, css_class: "ascending");
320 }
321 else
322 gtk_widget_hide (widget: self->sort);
323
324 g_clear_pointer (&self->popup_menu, gtk_widget_unparent);
325}
326
327GtkColumnViewColumn *
328gtk_column_view_title_get_column (GtkColumnViewTitle *self)
329{
330 return self->column;
331}
332

source code of gtk/gtk/gtkcolumnviewtitle.c