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 | |
38 | struct _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 | |
50 | struct _GtkColumnViewTitleClass |
51 | { |
52 | GtkWidgetClass parent_class; |
53 | }; |
54 | |
55 | G_DEFINE_TYPE (GtkColumnViewTitle, gtk_column_view_title, GTK_TYPE_WIDGET) |
56 | |
57 | static int |
58 | get_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 | |
68 | static int |
69 | unadjust_width (GtkWidget *widget, |
70 | int width) |
71 | { |
72 | GtkCssStyle *style; |
73 | int widget_margins; |
74 | int ; |
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 | |
88 | static void |
89 | gtk_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 | |
128 | static void |
129 | gtk_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 | |
150 | static void |
151 | gtk_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 | |
163 | static void |
164 | gtk_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 | |
178 | static void |
179 | gtk_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 | |
187 | static void |
188 | activate_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 | |
203 | static void |
204 | (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 | |
235 | static void |
236 | click_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 | |
253 | static void |
254 | gtk_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 | |
278 | GtkWidget * |
279 | gtk_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 | |
291 | void |
292 | gtk_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 | |
327 | GtkColumnViewColumn * |
328 | gtk_column_view_title_get_column (GtkColumnViewTitle *self) |
329 | { |
330 | return self->column; |
331 | } |
332 | |