1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald |
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 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 | |
18 | /* |
19 | * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS |
20 | * file for a list of people on the GTK+ Team. See the ChangeLog |
21 | * files for a list of changes. These files are distributed with |
22 | * GTK+ at ftp://ftp.gtk.org/pub/gtk/. |
23 | */ |
24 | |
25 | /** |
26 | * GtkFixed: |
27 | * |
28 | * `GtkFixed` places its child widgets at fixed positions and with fixed sizes. |
29 | * |
30 | * `GtkFixed` performs no automatic layout management. |
31 | * |
32 | * For most applications, you should not use this container! It keeps |
33 | * you from having to learn about the other GTK containers, but it |
34 | * results in broken applications. With `GtkFixed`, the following |
35 | * things will result in truncated text, overlapping widgets, and |
36 | * other display bugs: |
37 | * |
38 | * - Themes, which may change widget sizes. |
39 | * |
40 | * - Fonts other than the one you used to write the app will of course |
41 | * change the size of widgets containing text; keep in mind that |
42 | * users may use a larger font because of difficulty reading the |
43 | * default, or they may be using a different OS that provides different fonts. |
44 | * |
45 | * - Translation of text into other languages changes its size. Also, |
46 | * display of non-English text will use a different font in many |
47 | * cases. |
48 | * |
49 | * In addition, `GtkFixed` does not pay attention to text direction and |
50 | * thus may produce unwanted results if your app is run under right-to-left |
51 | * languages such as Hebrew or Arabic. That is: normally GTK will order |
52 | * containers appropriately for the text direction, e.g. to put labels to |
53 | * the right of the thing they label when using an RTL language, but it can’t |
54 | * do that with `GtkFixed`. So if you need to reorder widgets depending on |
55 | * the text direction, you would need to manually detect it and adjust child |
56 | * positions accordingly. |
57 | * |
58 | * Finally, fixed positioning makes it kind of annoying to add/remove |
59 | * UI elements, since you have to reposition all the other elements. This |
60 | * is a long-term maintenance problem for your application. |
61 | * |
62 | * If you know none of these things are an issue for your application, |
63 | * and prefer the simplicity of `GtkFixed`, by all means use the |
64 | * widget. But you should be aware of the tradeoffs. |
65 | */ |
66 | |
67 | #include "config.h" |
68 | |
69 | #include "gtkfixed.h" |
70 | |
71 | #include "gtkfixedlayout.h" |
72 | #include "gtkintl.h" |
73 | #include "gtkprivate.h" |
74 | #include "gtkwidgetprivate.h" |
75 | #include "gtkbuildable.h" |
76 | |
77 | typedef struct { |
78 | GtkLayoutManager *layout; |
79 | } GtkFixedPrivate; |
80 | |
81 | static void gtk_fixed_buildable_iface_init (GtkBuildableIface *iface); |
82 | |
83 | G_DEFINE_TYPE_WITH_CODE (GtkFixed, gtk_fixed, GTK_TYPE_WIDGET, |
84 | G_ADD_PRIVATE (GtkFixed) |
85 | G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, |
86 | gtk_fixed_buildable_iface_init)) |
87 | |
88 | static void |
89 | gtk_fixed_compute_expand (GtkWidget *widget, |
90 | gboolean *hexpand_p, |
91 | gboolean *vexpand_p) |
92 | { |
93 | GtkWidget *w; |
94 | gboolean hexpand = FALSE; |
95 | gboolean vexpand = FALSE; |
96 | |
97 | for (w = gtk_widget_get_first_child (widget); |
98 | w != NULL; |
99 | w = gtk_widget_get_next_sibling (widget: w)) |
100 | { |
101 | hexpand = hexpand || gtk_widget_compute_expand (widget: w, orientation: GTK_ORIENTATION_HORIZONTAL); |
102 | vexpand = vexpand || gtk_widget_compute_expand (widget: w, orientation: GTK_ORIENTATION_VERTICAL); |
103 | } |
104 | |
105 | *hexpand_p = hexpand; |
106 | *vexpand_p = vexpand; |
107 | } |
108 | |
109 | static GtkSizeRequestMode |
110 | gtk_fixed_get_request_mode (GtkWidget *widget) |
111 | { |
112 | GtkWidget *w; |
113 | int wfh = 0, hfw = 0; |
114 | |
115 | for (w = gtk_widget_get_first_child (widget); |
116 | w != NULL; |
117 | w = gtk_widget_get_next_sibling (widget: w)) |
118 | { |
119 | GtkSizeRequestMode mode = gtk_widget_get_request_mode (widget: w); |
120 | |
121 | switch (mode) |
122 | { |
123 | case GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH: |
124 | hfw ++; |
125 | break; |
126 | case GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT: |
127 | wfh ++; |
128 | break; |
129 | case GTK_SIZE_REQUEST_CONSTANT_SIZE: |
130 | default: |
131 | break; |
132 | } |
133 | } |
134 | |
135 | if (hfw == 0 && wfh == 0) |
136 | return GTK_SIZE_REQUEST_CONSTANT_SIZE; |
137 | else |
138 | return wfh > hfw ? |
139 | GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT : |
140 | GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; |
141 | } |
142 | |
143 | static void |
144 | gtk_fixed_dispose (GObject *object) |
145 | { |
146 | GtkWidget *child; |
147 | |
148 | while ((child = gtk_widget_get_first_child (GTK_WIDGET (object)))) |
149 | gtk_fixed_remove (GTK_FIXED (object), widget: child); |
150 | |
151 | G_OBJECT_CLASS (gtk_fixed_parent_class)->dispose (object); |
152 | } |
153 | |
154 | static void |
155 | gtk_fixed_class_init (GtkFixedClass *klass) |
156 | { |
157 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
158 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
159 | |
160 | object_class->dispose = gtk_fixed_dispose; |
161 | |
162 | widget_class->compute_expand = gtk_fixed_compute_expand; |
163 | widget_class->get_request_mode = gtk_fixed_get_request_mode; |
164 | |
165 | gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_FIXED_LAYOUT); |
166 | } |
167 | |
168 | static GtkBuildableIface *parent_buildable_iface; |
169 | |
170 | static void |
171 | gtk_fixed_buildable_add_child (GtkBuildable *buildable, |
172 | GtkBuilder *builder, |
173 | GObject *child, |
174 | const char *type) |
175 | { |
176 | if (GTK_IS_WIDGET (child)) |
177 | gtk_fixed_put (GTK_FIXED (buildable), GTK_WIDGET (child), x: 0, y: 0); |
178 | else |
179 | parent_buildable_iface->add_child (buildable, builder, child, type); |
180 | } |
181 | |
182 | static void |
183 | gtk_fixed_buildable_iface_init (GtkBuildableIface *iface) |
184 | { |
185 | parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface); |
186 | |
187 | iface->add_child = gtk_fixed_buildable_add_child; |
188 | } |
189 | |
190 | static void |
191 | gtk_fixed_init (GtkFixed *self) |
192 | { |
193 | GtkFixedPrivate *priv = gtk_fixed_get_instance_private (self); |
194 | |
195 | gtk_widget_set_overflow (GTK_WIDGET (self), overflow: GTK_OVERFLOW_HIDDEN); |
196 | |
197 | priv->layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); |
198 | } |
199 | |
200 | /** |
201 | * gtk_fixed_new: |
202 | * |
203 | * Creates a new `GtkFixed`. |
204 | * |
205 | * Returns: a new `GtkFixed`. |
206 | */ |
207 | GtkWidget* |
208 | gtk_fixed_new (void) |
209 | { |
210 | return g_object_new (GTK_TYPE_FIXED, NULL); |
211 | } |
212 | |
213 | /** |
214 | * gtk_fixed_put: |
215 | * @fixed: a `GtkFixed` |
216 | * @widget: the widget to add |
217 | * @x: the horizontal position to place the widget at |
218 | * @y: the vertical position to place the widget at |
219 | * |
220 | * Adds a widget to a `GtkFixed` at the given position. |
221 | */ |
222 | void |
223 | gtk_fixed_put (GtkFixed *fixed, |
224 | GtkWidget *widget, |
225 | double x, |
226 | double y) |
227 | { |
228 | GtkFixedPrivate *priv = gtk_fixed_get_instance_private (self: fixed); |
229 | GtkFixedLayoutChild *child_info; |
230 | GskTransform *transform = NULL; |
231 | |
232 | g_return_if_fail (GTK_IS_FIXED (fixed)); |
233 | g_return_if_fail (GTK_IS_WIDGET (widget)); |
234 | g_return_if_fail (_gtk_widget_get_parent (widget) == NULL); |
235 | |
236 | gtk_widget_set_parent (widget, GTK_WIDGET (fixed)); |
237 | |
238 | child_info = GTK_FIXED_LAYOUT_CHILD (ptr: gtk_layout_manager_get_layout_child (manager: priv->layout, child: widget)); |
239 | |
240 | transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (x, y)); |
241 | gtk_fixed_layout_child_set_transform (child: child_info, transform); |
242 | gsk_transform_unref (self: transform); |
243 | } |
244 | |
245 | /** |
246 | * gtk_fixed_get_child_position: |
247 | * @fixed: a `GtkFixed` |
248 | * @widget: a child of @fixed |
249 | * @x: (out): the horizontal position of the @widget |
250 | * @y: (out): the vertical position of the @widget |
251 | * |
252 | * Retrieves the translation transformation of the |
253 | * given child `GtkWidget` in the `GtkFixed`. |
254 | * |
255 | * See also: [method@Gtk.Fixed.get_child_transform]. |
256 | */ |
257 | void |
258 | gtk_fixed_get_child_position (GtkFixed *fixed, |
259 | GtkWidget *widget, |
260 | double *x, |
261 | double *y) |
262 | { |
263 | g_return_if_fail (GTK_IS_FIXED (fixed)); |
264 | g_return_if_fail (GTK_IS_WIDGET (widget)); |
265 | g_return_if_fail (x != NULL); |
266 | g_return_if_fail (y != NULL); |
267 | g_return_if_fail (gtk_widget_get_parent (widget) == GTK_WIDGET (fixed)); |
268 | |
269 | gtk_widget_translate_coordinates (src_widget: widget, GTK_WIDGET (fixed), src_x: 0, src_y: 0, dest_x: x, dest_y: y); |
270 | } |
271 | |
272 | /** |
273 | * gtk_fixed_set_child_transform: |
274 | * @fixed: a `GtkFixed` |
275 | * @widget: a `GtkWidget`, child of @fixed |
276 | * @transform: (nullable): the transformation assigned to @widget |
277 | * to reset @widget's transform |
278 | * |
279 | * Sets the transformation for @widget. |
280 | * |
281 | * This is a convenience function that retrieves the |
282 | * [class@Gtk.FixedLayoutChild] instance associated to |
283 | * @widget and calls [method@Gtk.FixedLayoutChild.set_transform]. |
284 | */ |
285 | void |
286 | gtk_fixed_set_child_transform (GtkFixed *fixed, |
287 | GtkWidget *widget, |
288 | GskTransform *transform) |
289 | { |
290 | GtkFixedPrivate *priv = gtk_fixed_get_instance_private (self: fixed); |
291 | GtkFixedLayoutChild *child_info; |
292 | |
293 | g_return_if_fail (GTK_IS_FIXED (fixed)); |
294 | g_return_if_fail (GTK_IS_WIDGET (widget)); |
295 | g_return_if_fail (gtk_widget_get_parent (widget) == GTK_WIDGET (fixed)); |
296 | |
297 | child_info = GTK_FIXED_LAYOUT_CHILD (ptr: gtk_layout_manager_get_layout_child (manager: priv->layout, child: widget)); |
298 | gtk_fixed_layout_child_set_transform (child: child_info, transform); |
299 | } |
300 | |
301 | /** |
302 | * gtk_fixed_get_child_transform: |
303 | * @fixed: a `GtkFixed` |
304 | * @widget: a `GtkWidget`, child of @fixed |
305 | * |
306 | * Retrieves the transformation for @widget set using |
307 | * gtk_fixed_set_child_transform(). |
308 | * |
309 | * Returns: (transfer none) (nullable): a `GskTransform` |
310 | */ |
311 | GskTransform * |
312 | gtk_fixed_get_child_transform (GtkFixed *fixed, |
313 | GtkWidget *widget) |
314 | { |
315 | GtkFixedPrivate *priv = gtk_fixed_get_instance_private (self: fixed); |
316 | GtkFixedLayoutChild *child_info; |
317 | |
318 | g_return_val_if_fail (GTK_IS_FIXED (fixed), NULL); |
319 | g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); |
320 | g_return_val_if_fail (gtk_widget_get_parent (widget) == GTK_WIDGET (fixed), NULL); |
321 | |
322 | child_info = GTK_FIXED_LAYOUT_CHILD (ptr: gtk_layout_manager_get_layout_child (manager: priv->layout, child: widget)); |
323 | return gtk_fixed_layout_child_get_transform (child: child_info); |
324 | } |
325 | |
326 | /** |
327 | * gtk_fixed_move: |
328 | * @fixed: a `GtkFixed` |
329 | * @widget: the child widget |
330 | * @x: the horizontal position to move the widget to |
331 | * @y: the vertical position to move the widget to |
332 | * |
333 | * Sets a translation transformation to the given @x and @y |
334 | * coordinates to the child @widget of the `GtkFixed`. |
335 | */ |
336 | void |
337 | gtk_fixed_move (GtkFixed *fixed, |
338 | GtkWidget *widget, |
339 | double x, |
340 | double y) |
341 | { |
342 | GtkFixedPrivate *priv = gtk_fixed_get_instance_private (self: fixed); |
343 | GtkFixedLayoutChild *child_info; |
344 | GskTransform *transform = NULL; |
345 | |
346 | g_return_if_fail (GTK_IS_FIXED (fixed)); |
347 | g_return_if_fail (GTK_IS_WIDGET (widget)); |
348 | g_return_if_fail (gtk_widget_get_parent (widget) == GTK_WIDGET (fixed)); |
349 | |
350 | child_info = GTK_FIXED_LAYOUT_CHILD (ptr: gtk_layout_manager_get_layout_child (manager: priv->layout, child: widget)); |
351 | |
352 | transform = gsk_transform_translate (next: transform, point: &GRAPHENE_POINT_INIT (x, y)); |
353 | gtk_fixed_layout_child_set_transform (child: child_info, transform); |
354 | gsk_transform_unref (self: transform); |
355 | } |
356 | |
357 | /** |
358 | * gtk_fixed_remove: |
359 | * @fixed: a `GtkFixed` |
360 | * @widget: the child widget to remove |
361 | * |
362 | * Removes a child from @fixed. |
363 | */ |
364 | void |
365 | gtk_fixed_remove (GtkFixed *fixed, |
366 | GtkWidget *widget) |
367 | { |
368 | g_return_if_fail (GTK_IS_FIXED (fixed)); |
369 | g_return_if_fail (GTK_IS_WIDGET (widget)); |
370 | g_return_if_fail (gtk_widget_get_parent (widget) == GTK_WIDGET (fixed)); |
371 | |
372 | gtk_widget_unparent (widget); |
373 | } |
374 | |