-
Notifications
You must be signed in to change notification settings - Fork 3
/
custom-container.page.in
298 lines (221 loc) · 20.5 KB
/
custom-container.page.in
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
<page xmlns="http://projectmallard.org/1.0/"
type="topic"
id="custom-container">
<info>
<credit type="author maintainer copyright">
<name>Philip Chimento</name>
<email>philip.chimento@gmail.com</email>
<years>2008-2012</years>
</credit>
<license href="http://creativecommons.org/licenses/by-nc-sa/3.0/">
<p>This work is licensed under a <link href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License</link>.</p>
</license>
<link type="guide" xref="index" group="#default"/>
<desc>A container that does its own size allocation</desc>
</info>
<title>Custom container</title>
<p>Size allocation is not a very difficult procedure in GTK.
Widgets report their preferred size from the bottom of the widget tree up, with each container figuring out the preferred sizes of its children and then calculating its own preferred size using that information.
This can be done simply by using <code>gtk_widget_get_preferred_height()</code> and <code>gtk_widget_get_preferred_width()</code>, but you can also have the height depend on the width or the width depend on the height.
Once the total preferred size is known, the available space is allocated from the top down: each container divides up the space it receives among its children by calling their <code>gtk_widget_size_allocate()</code> methods.
What's more, you can customize the process for any widget by overriding these methods.</p>
<note style="warning">
<title>GTK 2</title>
<p>This is rather different from the way things worked in GTK 2, and there are probably plenty of GTK 2 instructions still floating around on the Internet.</p>
</note>
<p>In most cases, such as writing a composite widget or adding functionality to an existing container, you can get away with subclassing a container like <code>GtkGrid</code>.
That way, you don't have to deal with size negotiation and other container stuff directly, since it's already implemented in your parent class.
But what if you want a container that arranges its children differently than all the existing GTK containers?
Then you'll have to write a new container with its own size request and allocation algorithms.</p>
<p>It wasn't easy to think of a realistic-yet-simple example, since anytime you really have to do this, you're probably attempting something complicated.
Therefore, we will write a fairly useless container which arranges all its children in a square, inspired by '80's video clips with cheesy split screen effects, like <em>Heat of the Moment</em>.
The container will find an integer <span xmlns:m="http://www.w3.org/1998/Math/MathML"><m:math><m:mrow><m:mi>n</m:mi></m:mrow></m:math></span> so that</p>
<table><tr><td>
<p><span xmlns:m="http://www.w3.org/1998/Math/MathML"><m:math><m:mrow>
<m:msup><m:mi>n</m:mi><m:mn>2</m:mn></m:msup>
<m:mo>≥<!--geq--></m:mo>
<m:mi>V</m:mi>
</m:mrow></m:math></span></p>
</td></tr></table>
<p>where <span xmlns:m="http://www.w3.org/1998/Math/MathML"><m:math><m:mrow><m:mi>V</m:mi></m:mrow></m:math></span> is the number of visible children of the container. The container will then divide its space into an <span xmlns:m="http://www.w3.org/1998/Math/MathML"><m:math><m:mrow><m:mi>n</m:mi><m:mo>×<!--Times--></m:mo><m:mi>n</m:mi></m:mrow></m:math></span> grid, which it will fill from left to right and top to bottom.</p>
<section id="header-file">
<title>Header file</title>
<p>We will create a subclass of <code>GtkContainer</code> called <code>PSquare</code>. The header file, <file href="../psquare/psquare.h">psquare.h</file>, is a textbook case if there ever was one, so I won't reprint it here. It exports only two functions, <code>p_square_get_type()</code> and <code>p_square_new()</code>, since all of the interesting stuff happens in overridden <code>GtkContainer</code> functions.</p>
</section>
<section id="class-boilerplate">
<title>Class boilerplate</title>
<p>We will briefly go over the required GObject stuff in the <file href="../psquare/psquare.c">psquare.c</file> file, since it's good to know about the <code>G_DEFINE_TYPE</code> macros and the <code>g_type_class_add_private()</code> mechanism.
Not all online resources mention them.</p>
<p><code>G_DEFINE_TYPE()</code> is a handy macro that saves you a lot of typing.
It writes your <code>p_square_get_type()</code> function for you, which you then export in your header file; it also declares a static <code>p_square_parent_class</code> variable, a static <code>p_square_class_init()</code> function, and a static <code>p_square_init()</code> function.</p>
<!--{{{psquare/psquare.c:29}}}-->
<p>The <code>parent_class</code> variable is there to avoid a costly call to <code>g_type_class_peek_parent()</code> every time you need to chain up to a method in the parent class (often in <code>finalize</code>).
You need to write the class and instance initialization functions yourself.</p>
<!--{{{psquare/psquare.c:32-50}}}-->
<p>The class initialization function <code>p_square_class_init</code> overrides several methods in ancestor classes.
If you are not familiar with the GObject way of doing this, then take a good look now.
Our test program will not use the <code>forall</code> and <code>child_type</code> methods, but a peek at <file href="http://git.gnome.org/browse/gtk+/tree/gtk/gtkcontainer.c">gtkcontainer.c</file> will reveal that they are set to <code>NULL</code> in the <code>GtkContainer</code> class initializer; in other words, they are pure virtual functions, and are required to be overridden.</p>
<note>
<p>To find out what methods there are to override in the parent class, you have to look at the source code of <code>GtkContainer</code>.
There isn't really a good way to tell from the API documentation, which is unfortunate.
</p>
</note>
<p>The other thing the class initializer does is register <code>PSquare</code>'s private indirection member.
This is probably the most effective way of hiding your implementation details when you write a class.
This is achieved by defining a private member structure and telling GObject to allocate space for it in each instance of that class, using <code>g_type_class_add_private()</code>.
That way, each object has private data stored in it, but you can only access that data if the composition of your private structure is exposed.
The private structure is defined in the following code.</p>
<!--{{{psquare/psquare.c:6-14}}}-->
<p>You might prefer to put these lines in a file called something like <file>psquare-private.h</file>.
That way, any implementation file that includes it will be able to access the private data, kind of like the <code>friend</code> directive in C++.
It also comes in handy if the implementation of your class is very big and you want to split it over several files.</p>
<p>The macro <code>P_SQUARE_PRIVATE()</code> gives us the private data of a <code>PSquare</code>.
Our only private member is the list of our container's children.
We need to keep track of this list ourselves, since <code>GtkContainer</code> won't do it for us.</p>
<p>The instance initialization function <code>p_square_init()</code> initializes the public and private members of our object:</p>
<code mime="text/x-csrc"><![CDATA[
static void
p_square_init(PSquare *square)
{
gtk_widget_set_has_window(GTK_WIDGET(square), FALSE);
/* Initialize private members */
PSquarePrivate *priv = P_SQUARE_PRIVATE(square);
priv->children = NULL;
}
]]></code>
<p>The <code>gtk_widget_set_has_window()</code> call means that <code>Psquare</code> doesn't have a <code>GdkWindow</code> associated with it; we don't do any drawing ourselves, we just arrange the container's children.
The private member <code>children</code> is initialized to <code>NULL</code>, which is an empty <code>GList</code>, as you should know.</p>
<p>You should also know that it is customary to create a widget with a <code>p_square_new()</code> function, which returns a new instance of the class, cast to a <code>GtkWidget</code> pointer.
Here it is:</p>
<!--{{{psquare/psquare.c:68-72}}}-->
</section>
<section id="overriding-container">
<title>Overriding <code>GtkContainer</code> methods</title>
<p>Now for the overridden ancestor methods.
The <code>GtkContainer</code> ones are fairly trivial, so we'll do them first.
The <code>child_type</code> method, specifying what types of children can be added to the container, simply returns <code>GTK_TYPE_WIDGET</code>.
The <code>forall</code> pointer in the <code>GtkContainerClass</code> structure doesn't correspond exactly to a <code>GtkContainer</code> method, but <code>gtk_container_forall()</code> and <code>gtk_container_foreach()</code> are use it in their implementations.
One iterates over all children including "internal" ones (such as the <code>GtkButton</code>s in a dialog that aren't meant to be manipulated by your program), the other iterates over the container's children but skips internal ones.
The class <code>forall</code> function takes a flag telling whether to include internal children or not.
<code>PSquare</code> doesn't have any internal children, so we just ignore that.</p>
<!--{{{psquare/psquare.c:396-999}}}-->
<p>The next things to implement are the <code>add</code> and <code>remove</code> methods.
Note that you don't have to have them if, for example, your container has a different mechanism for adding widgets; the default <code>GtkContainer</code> implementations do nothing, and print a warning message saying that the method is not implemented.</p>
<!--{{{psquare/psquare.c:350-367}}}-->
<p>It may surprise you that this function is not longer.
However, as the comment says, all the real work is done in <code>gtk_widget_set_parent()</code>: sinking the child's floating reference, redrawing the child, emitting the appropriate signals, etc.
We still have to redraw the container ourselves.
The corresponding method to remove a child is much the same, with all the real work being done in <code>gtk_widget_unparent()</code>.</p>
<!--{{{psquare/psquare.c:370-391}}}-->
<p>Note that we don't have to write <code>p_square_destroy()</code> or <code>p_square_finalize()</code> methods to unref the child widgets, since the <code>GtkContainer</code> code already does that when the container is destroyed.</p>
</section>
<section id="size-negotiation">
<title>Size negotiation</title>
<p>Now that we have all that out of the way, we can get to the meat of this section.
We will determine how much width our container needs by adding up the width of each column.
The width of a column will be equal to the width of the widest widget in that column.
The container height will be determined likewise.
We will also respect the <code>border_width</code> property of <code>GtkContainer</code>.</p>
<p>There are two functions with which the container reports its size: one for the width, and one for the height.
They are passed pointers to two integers, which they must fill with the minimum size (the absolute minimum they need to function) and the natural size (at which they can function comfortably.)</p>
<!--{{{psquare/psquare.c:17-20}}}-->
<p>In our case, these two functions will work nearly the same, so we will write them both in terms of a third function, <code>get_size()</code>.
This function gets an extra <code>GtkOrientation</code> parameter, called <code>direction</code>:
<code>GTK_ORIENTATION_HORIZONTAL</code> if we are requesting the width, <code>GTK_ORIENTATION_VERTICAL</code> if we are requesting the height.</p>
<code mime="text/x-csrc"><![CDATA[
static void get_size(PSquare *self, GtkOrientation direction,
int *minimal, int *natural);
]]></code>
<p>We start by setting the <code>minimal</code> and <code>natural</code> parameters to their minimum value, twice the border width.
Then we compute the size of the side of our square, or in other words, the number of rows and columns.
We call it <code>n_groups</code> because it doesn't matter whether we are talking about rows or columns here.
If the number of groups is zero, our work is done.</p>
<p>Then, we iterate over the children, asking them for their sizes, in the function <code>get_group_sizes()</code>.
The <code>GtkRequestedSize</code> structure stores both the minimum and the natural size requests.
Once we have got this information, we add them all up to find the total size we want to request.</p>
<!--{{{psquare/psquare.c:186-210}}}-->
<p>The function <code>get_n_columns_and_rows()</code> is shown below.
Note that the container might still have children, but the number of groups will be zero if they are all invisible; we don't layout unshown widgets.</p>
<!--{{{psquare/psquare.c:84-98}}}-->
<!--{{{psquare/psquare.c:74-81}}}-->
<p>Next we describe <code>get_group_sizes()</code>, since that is where most of the real work of finding the preferred size is done.
</p>
<p>First, we allocate arrays to store all the group sizes (that is, the column widths or row heights.)
When we get all the preferred sizes of the container's children, we will store a running maximum for each group in this array.</p>
<!--{{{psquare/psquare.c:111}}}-->
<p>We then iterate over the children again, asking them for their preferred sizes.
If we are in width mode, we ask for the width, or the height in height mode.
We also set the variable <code>group_num</code> depending on width or height mode; this is the row or column number the current child widget is in.</p>
<p>Then, if the child's size is larger than the size we have stored for the group we're in, we replace the stored size by the child's size.
We return this <code>sizes</code> array.</p>
<!--{{{psquare/psquare.c:115-139}}}-->
</section>
<section id="size-allocation">
<title>Size allocation</title>
<p>The <code>size_allocate</code> method is similar.
It receives a pointer to a <code>GtkAllocation</code> structure, which contains the size that the widget will have to deal with.</p>
<!--{{{psquare/psquare.c:21-22}}}-->
<p>The first thing it must do is store the allocation in the widget structure.</p>
<!--{{{psquare/psquare.c:273}}}-->
<p>The next section is very similar to the code in the <code>size_request</code> method.
First we obtain the number of columns and rows:</p>
<!--{{{psquare/psquare.c:276-279}}}-->
<p>They are the same, but I'm using two separate <code>n_columns</code> and <code>n_rows</code> variables for clarity in the code.
Again, if there are no visible widgets in the container, our work is now done.</p>
<p>Now we have to distribute the container's allocated space to the children.
Our policy will be to divide any extra width equally among the columns, and any extra height equally among the rows.
If there is too little space, it will be taken equally from each column or row.
First we calculate the space surplus or deficit per column and row.
We do this with two variables, <code>extra_width</code> and <code>extra_height</code>.
We start them off equal to the total width or height, then we will subtract all the space that we need.
The first thing is to subtract the container's border width.</p>
<!--{{{psquare/psquare.c:282-285}}}-->
<p>Then we get all the column widths with the <code>get_group_sizes()</code> function described above.
We calculate the actual space to allocate to each column, by adding the extra space (which can be negative) to each column.
This happens in the <code>distribute_extra_space()</code> function, which we will get to in a moment.</p>
<p>After we have figured out the width of each column, we can calculate the height of each row. This follows the same procedure, except that instead of <code>get_group_sizes()</code> we write a new function called <code>get_group_sizes_for_sizes()</code>, which gets the appropriate height for a certain width, or vice versa.
We will skip showing that function here — it's the same as <code>get_group_sizes()</code> except that it calls <code>gtk_widget_get_preferred_height_for_width()</code> and <code>..._width_for_height()</code>, instead of <code>gtk_widget_get_preferred_height()</code> and <code>..._width()</code>, respectively.
If you want to see it, look in the <file href="../psquare/psquare.c">psquare.c</file> file.</p>
<!--{{{psquare/psquare.c:287-306}}}-->
<p>Here is the <code>distribute_extra_space()</code> function, which adds the extra space (which can be negative) to each group (column or row).
GTK provides a convenience function to distribute any extra space (which <em>can't</em> be negative) among a group of widgets so that they each get as much as their natural size, so we do this first if we have space left over.
Then if there's still space left, or if we had a shortage in the first place, we distribute the surplus or shortage equally among the groups.
We have to watch out that we don't allocate less than zero space to any widget, so if any space is less than zero, we borrow pixels one by one from any other nonzero columns until the space is zero.</p>
<!--{{{psquare/psquare.c:213-242}}}-->
<p>Back to the size allocation method.
We will keep track of the <span xmlns:m="http://www.w3.org/1998/Math/MathML"><m:math><m:mrow><m:mo>(</m:mo><m:mi>x</m:mi><m:mo>,</m:mo><m:mi>y</m:mi><m:mo>)</m:mo></m:mrow></m:math></span> coordinates at which we will place the top left corner of our next child widget.
Note that the <code>x</code> and <code>y</code> members of the <code>GtkAllocation</code> structure contain (I think) screen coordinates, not offsets from the top left of the containing widget, so you always need to start from those values.</p>
<!--{{{psquare/psquare.c:308-311}}}-->
<p>Then we iterate once more over the visible children.
For each child, we allocate a <code>GtkAllocation</code> structure on the stack, fill it with the correct <span xmlns:m="http://www.w3.org/1998/Math/MathML"><m:math><m:mrow><m:mo>(</m:mo><m:mi>x</m:mi><m:mo>,</m:mo><m:mi>y</m:mi><m:mo>)</m:mo></m:mrow></m:math></span> coordinates and size, and notify the child by calling <code>gtk_widget_size_allocate()</code>.
Then we update our <span xmlns:m="http://www.w3.org/1998/Math/MathML"><m:math><m:mrow><m:mi>x</m:mi></m:mrow></m:math></span> and <span xmlns:m="http://www.w3.org/1998/Math/MathML"><m:math><m:mrow><m:mi>y</m:mi></m:mrow></m:math></span> coordinates appropriately for the next child, moving down a row and returning to the left edge of the container when we reach the end of a row.</p>
<!--{{{psquare/psquare.c:313-336}}}-->
</section>
<section id="example">
<title>Example application</title>
<p>We will create a simple example application to showcase our new container.
You will find <file href="../psquare/test-psquare.c">test-psquare.c</file> in the code distribution, along with a simple <file href="../psquare/Makefile">Makefile</file>.
We create a toplevel window and require it to be a specific size (although you can resize it to be larger while the program is running), using <code>gtk_widget_set_size_request()</code>.
Since the window will stay the same size, then the widgets in the container will have more than enough space if there are only a few, but as more and more widgets are added, they will have to start squeezing themselves into spaces smaller than their size requests.</p>
<p>We will add a toolbar to the window with add and remove buttons, to add a random widget to the container (using <code>gtk_container_add()</code>) or remove the last one added (with <code>gtk_container_remove()</code>).</p>
<p>If you compile the example application and run it, try adding lots of widgets to the container.
You'll notice that a <code>GtkEntry</code> requests a lot of width, and so columns with <code>GtkEntry</code>s in them tend to crowd out other columns when horizontal space gets tight.
You shouldn't get any warnings about negative size allocations, however.</p>
</section>
<section id="exercises">
<title>Exercises</title>
<list>
<item>
<p>Change <code>p_square_size_allocate()</code> so that it allocates any extra space according to the proportion of the total space that the column or row takes up.
Likewise, if there is not enough space, then narrow columns and short rows lose less space, and wide columns and tall rows lose more.</p>
</item>
<item>
<p>Change <code>PSquare</code> so that instead of filling its cells from left to right and top to bottom, it fills them in a clockwise spiral starting at the top left.</p>
</item>
<item>
<p>Implement child properties for <code>PSquare</code> children: for example, <code>fill-horizontal</code> and <code>fill-vertical</code> properties, which control whether the child widget fills all of its allocated space or sticks to its own request; and a property that controls how the child aligns itself if it does not fill the entire cell.
You could implement this last one as two properties, for horizontal and vertical alignment, or you could use one value of type <code>GtkAnchorType</code>.</p>
</item>
</list>
</section>
</page>