Use gtk_widget_destroy() instead of gtk_container_remove() if possible.
[lxde/lxpanel.git] / src / icon-grid.c
1 /**
2 * Copyright (c) 2009 LxDE Developers, see the file AUTHORS for details.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program 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
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #include <gtk/gtk.h>
20 #include <string.h>
21
22 #include "icon-grid.h"
23
24 /* Properties */
25 enum {
26 PROP_0,
27 PROP_ORIENTATION,
28 PROP_SPACING,
29 PROP_CONSTRAIN_WIDTH
30 };
31
32 /* Representative of an icon grid. This is a manager that packs widgets into a rectangular grid whose size adapts to conditions. */
33 struct _PanelIconGrid
34 {
35 GtkContainer container; /* Parent widget */
36 GList * children; /* List of icon grid elements */
37 GtkOrientation orientation; /* Desired orientation */
38 gint child_width; /* Desired child width */
39 gint child_height; /* Desired child height */
40 gint spacing; /* Desired spacing between grid elements */
41 gint border; /* Desired border around grid elements */
42 gint target_dimension; /* Desired dimension perpendicular to orientation */
43 gboolean constrain_width; /* True if width should be constrained by allocated space */
44 int rows; /* Computed layout rows */
45 int columns; /* Computed layout columns */
46 int constrained_child_width; /* Child width constrained by allocation */
47 };
48
49 struct _PanelIconGridClass
50 {
51 GtkContainerClass parent_class;
52 };
53
54 static void panel_icon_grid_size_request(GtkWidget *widget, GtkRequisition *requisition);
55
56 /* Establish the widget placement of an icon grid. */
57 static void panel_icon_grid_size_allocate(GtkWidget *widget,
58 GtkAllocation *allocation)
59 {
60 PanelIconGrid *ig = PANEL_ICON_GRID(widget);
61 GtkRequisition req;
62 GtkAllocation child_allocation;
63 int child_width;
64 int child_height;
65 GtkTextDirection direction;
66 int limit;
67 int x_initial;
68 int x_delta;
69 int x, y;
70 GList *ige;
71 GtkWidget *child;
72
73 /* Get and save the desired container geometry. */
74 if (ig->orientation == GTK_ORIENTATION_HORIZONTAL && allocation->height > 1)
75 ig->target_dimension = allocation->height;
76 else if (ig->orientation == GTK_ORIENTATION_VERTICAL && allocation->width > 1)
77 ig->target_dimension = allocation->width;
78 child_width = ig->child_width;
79 child_height = ig->child_height;
80
81 /* Calculate required size without borders */
82 panel_icon_grid_size_request(widget, &req);
83 req.width -= 2 * ig->border;
84 req.height -= 2 * ig->border;
85
86 /* Get the constrained child geometry if the allocated geometry is insufficient.
87 * All children are still the same size and share equally in the deficit. */
88 ig->constrained_child_width = ig->child_width;
89 if ((ig->columns != 0) && (ig->rows != 0) && (allocation->width > 1))
90 {
91 if (req.width > allocation->width)
92 ig->constrained_child_width = child_width = (allocation->width - ((ig->columns - 1) * ig->spacing)) / ig->columns;
93 if (req.height > allocation->height)
94 child_height = (allocation->height - ((ig->rows - 1) * ig->spacing)) / ig->rows;
95 }
96
97 /* Initialize parameters to control repositioning each visible child. */
98 direction = gtk_widget_get_direction(widget);
99 limit = ig->border + ((ig->orientation == GTK_ORIENTATION_HORIZONTAL)
100 ? (ig->rows * (child_height + ig->spacing))
101 : (ig->columns * (child_width + ig->spacing)));
102 x_initial = ((direction == GTK_TEXT_DIR_RTL)
103 ? allocation->width - child_width - ig->border
104 : ig->border);
105 x_delta = child_width + ig->spacing;
106 if (direction == GTK_TEXT_DIR_RTL) x_delta = - x_delta;
107
108 /* Reposition each visible child. */
109 x = x_initial;
110 y = ig->border;
111 for (ige = ig->children; ige != NULL; ige = ige->next)
112 {
113 child = ige->data;
114 if (gtk_widget_get_visible(child))
115 {
116 /* Do necessary operations on the child. */
117 gtk_widget_size_request(child, &req);
118 child_allocation.x = x;
119 child_allocation.y = y;
120 child_allocation.width = child_width;
121 child_allocation.height = child_height;
122 if (!gtk_widget_get_has_window (widget))
123 {
124 child_allocation.x += allocation->x;
125 child_allocation.y += allocation->y;
126 }
127 gtk_widget_size_allocate(child, &child_allocation);
128
129 /* Advance to the next grid position. */
130 if (ig->orientation == GTK_ORIENTATION_HORIZONTAL)
131 {
132 y += child_height + ig->spacing;
133 if (y >= limit)
134 {
135 y = ig->border;
136 x += x_delta;
137 }
138 }
139 else
140 {
141 x += x_delta;
142 if ((direction == GTK_TEXT_DIR_RTL) ? (x <= 0) : (x >= limit))
143 {
144 x = x_initial;
145 y += child_height + ig->spacing;
146 }
147 }
148 }
149 }
150 }
151
152 /* Establish the geometry of an icon grid. */
153 static void panel_icon_grid_size_request(GtkWidget *widget,
154 GtkRequisition *requisition)
155 {
156 PanelIconGrid *ig = PANEL_ICON_GRID(widget);
157 int visible_children = 0;
158 GList *ige;
159 int target_dimension = ig->target_dimension;
160 gint old_rows = ig->rows;
161 gint old_columns = ig->columns;
162
163 /* Count visible children. */
164 for (ige = ig->children; ige != NULL; ige = ige->next)
165 if (gtk_widget_get_visible(ige->data))
166 visible_children += 1;
167
168 if (ig->orientation == GTK_ORIENTATION_HORIZONTAL)
169 {
170 /* In horizontal orientation, fit as many rows into the available height as possible.
171 * Then allocate as many columns as necessary. Guard against zerodivides. */
172 ig->rows = 0;
173 if ((ig->child_height + ig->spacing) != 0)
174 ig->rows = (target_dimension + ig->spacing - ig->border * 2) / (ig->child_height + ig->spacing);
175 if (ig->rows == 0)
176 ig->rows = 1;
177 ig->columns = (visible_children + (ig->rows - 1)) / ig->rows;
178 if ((ig->columns == 1) && (ig->rows > visible_children))
179 ig->rows = visible_children;
180 }
181 else
182 {
183 /* In vertical orientation, fit as many columns into the available width as possible.
184 * Then allocate as many rows as necessary. Guard against zerodivides. */
185 ig->columns = 0;
186 if ((ig->child_width + ig->spacing) != 0)
187 ig->columns = (target_dimension + ig->spacing - ig->border * 2) / (ig->child_width + ig->spacing);
188 if (ig->columns == 0)
189 ig->columns = 1;
190 ig->rows = (visible_children + (ig->columns - 1)) / ig->columns;
191 if ((ig->rows == 1) && (ig->columns > visible_children))
192 ig->columns = visible_children;
193 }
194
195 /* Compute the requisition. */
196 if ((ig->columns == 0) || (ig->rows == 0))
197 {
198 requisition->width = 1;
199 requisition->height = 1;
200 gtk_widget_hide(widget); /* Necessary to get the plugin to disappear */
201 }
202 else
203 {
204 int column_spaces = ig->columns - 1;
205 int row_spaces = ig->rows - 1;
206 if (column_spaces < 0) column_spaces = 0;
207 if (row_spaces < 0) row_spaces = 0;
208 requisition->width = ig->child_width * ig->columns + column_spaces * ig->spacing + 2 * ig->border;
209 requisition->height = ig->child_height * ig->rows + row_spaces * ig->spacing + 2 * ig->border;
210 gtk_widget_show(widget);
211 }
212 if (ig->rows != old_rows || ig->columns != old_columns)
213 gtk_widget_queue_resize(widget);
214 }
215
216 /* Handler for "size-request" event on the icon grid element. */
217 static void icon_grid_element_size_request(GtkWidget * widget, GtkRequisition * requisition, PanelIconGrid * ig)
218 {
219 /* This is our opportunity to request space for the element. */
220 requisition->width = ig->child_width;
221 if ((ig->constrain_width) && (ig->constrained_child_width > 1))
222 requisition->width = ig->constrained_child_width;
223 requisition->height = ig->child_height;
224 }
225
226 /* Add an icon grid element and establish its initial visibility. */
227 static void panel_icon_grid_add(GtkContainer *container, GtkWidget *widget)
228 {
229 PanelIconGrid *ig = PANEL_ICON_GRID(container);
230
231 /* Insert at the tail of the child list. This keeps the graphics in the order they were added. */
232 ig->children = g_list_append(ig->children, widget);
233
234 /* Add the widget to the layout container. */
235 g_signal_connect(G_OBJECT(widget), "size-request",
236 G_CALLBACK(icon_grid_element_size_request), container);
237 gtk_widget_set_parent(widget, GTK_WIDGET(container));
238 gtk_widget_queue_resize(GTK_WIDGET(container));
239 }
240
241 void panel_icon_grid_set_constrain_width(PanelIconGrid * ig, gboolean constrain_width)
242 {
243 ig->constrain_width = constrain_width;
244 }
245
246 /* Remove an icon grid element. */
247 static void panel_icon_grid_remove(GtkContainer *container, GtkWidget *widget)
248 {
249 PanelIconGrid *ig = PANEL_ICON_GRID(container);
250 GList *children = ig->children;
251 GtkWidget *child;
252
253 while (children)
254 {
255 child = children->data;
256 if (widget == child)
257 {
258 gboolean was_visible = gtk_widget_get_visible(widget);
259
260 /* The child is found. Remove from child list and layout container. */
261 g_signal_handlers_disconnect_by_func(widget,
262 icon_grid_element_size_request,
263 container);
264 gtk_widget_unparent (widget);
265 ig->children = g_list_remove_link(ig->children, children);
266 g_list_free(children);
267
268 /* Do a relayout if needed. */
269 if (was_visible)
270 gtk_widget_queue_resize(GTK_WIDGET(ig));
271 break;
272 }
273 children = children->next;
274 }
275 }
276
277 /* Get the index of an icon grid element. */
278 gint panel_icon_grid_get_child_position(PanelIconGrid * ig, GtkWidget * child)
279 {
280 g_return_val_if_fail(PANEL_IS_ICON_GRID(ig), -1);
281
282 return g_list_index(ig->children, child);
283 }
284
285 /* Reorder an icon grid element. */
286 void panel_icon_grid_reorder_child(PanelIconGrid * ig, GtkWidget * child, gint position)
287 {
288 GList *old_link;
289 GList *new_link;
290 gint old_position;
291
292 g_return_if_fail(PANEL_IS_ICON_GRID(ig));
293 g_return_if_fail(GTK_IS_WIDGET(child));
294
295 old_link = ig->children;
296 old_position = 0;
297 while (old_link)
298 {
299 if (old_link->data == child)
300 break;
301 old_link = old_link->next;
302 old_position++;
303 }
304
305 g_return_if_fail(old_link != NULL);
306
307 if (position == old_position)
308 return;
309
310 /* Remove the child from its current position. */
311 ig->children = g_list_delete_link(ig->children, old_link);
312 if (position < 0)
313 new_link = NULL;
314 else
315 new_link = g_list_nth(ig->children, position);
316
317 /* If the child was found, insert it at the new position. */
318 ig->children = g_list_insert_before(ig->children, new_link, child);
319
320 /* Do a relayout. */
321 if (gtk_widget_get_visible(child) && gtk_widget_get_visible(GTK_WIDGET(ig)))
322 gtk_widget_queue_resize(child);
323 }
324
325 /* Change the geometry of an icon grid. */
326 void panel_icon_grid_set_geometry(PanelIconGrid * ig,
327 GtkOrientation orientation, gint child_width, gint child_height, gint spacing, gint border, gint target_dimension)
328 {
329 gboolean changed = (ig->orientation != orientation ||
330 ig->child_width != child_width ||
331 ig->child_height != child_height ||
332 ig->spacing != spacing ||
333 ig->border != border ||
334 ig->target_dimension != target_dimension);
335
336 ig->orientation = orientation;
337 ig->child_width = child_width;
338 ig->constrained_child_width = child_width;
339 ig->child_height = child_height;
340 ig->spacing = spacing;
341 ig->border = border;
342 ig->target_dimension = target_dimension;
343 if (changed)
344 gtk_widget_queue_resize(GTK_WIDGET(ig));
345 }
346
347 G_DEFINE_TYPE_WITH_CODE(PanelIconGrid, panel_icon_grid, GTK_TYPE_CONTAINER,
348 G_IMPLEMENT_INTERFACE(GTK_TYPE_ORIENTABLE, NULL));
349
350 static void panel_icon_grid_set_property(GObject *object, guint prop_id,
351 const GValue *value, GParamSpec *pspec)
352 {
353 PanelIconGrid *ig = PANEL_ICON_GRID(object);
354 gint spacing;
355
356 switch (prop_id)
357 {
358 case PROP_ORIENTATION:
359 ig->orientation = g_value_get_enum(value);
360 gtk_widget_queue_resize(GTK_WIDGET(ig));
361 break;
362 case PROP_SPACING:
363 spacing = g_value_get_int(value);
364 if (spacing != ig->spacing)
365 {
366 ig->spacing = spacing;
367 g_object_notify(object, "spacing");
368 gtk_widget_queue_resize(GTK_WIDGET(ig));
369 }
370 break;
371 case PROP_CONSTRAIN_WIDTH:
372 panel_icon_grid_set_constrain_width(ig, g_value_get_boolean(value));
373 break;
374 default:
375 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
376 break;
377 }
378 }
379
380 static void panel_icon_grid_get_property(GObject *object, guint prop_id,
381 GValue *value, GParamSpec *pspec)
382 {
383 PanelIconGrid *ig = PANEL_ICON_GRID(object);
384
385 switch (prop_id)
386 {
387 case PROP_ORIENTATION:
388 g_value_set_enum(value, ig->orientation);
389 break;
390 case PROP_SPACING:
391 g_value_set_int(value, ig->spacing);
392 break;
393 case PROP_CONSTRAIN_WIDTH:
394 g_value_set_boolean(value, ig->constrain_width);
395 break;
396 default:
397 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
398 break;
399 }
400 }
401
402 static void panel_icon_grid_forall(GtkContainer *container,
403 gboolean include_internals,
404 GtkCallback callback,
405 gpointer callback_data)
406 {
407 PanelIconGrid *ig = PANEL_ICON_GRID(container);
408 GList *children = ig->children;
409 GtkWidget *child;
410
411 while (children)
412 {
413 child = children->data;
414 children = children->next;
415 (* callback)(child, callback_data);
416 }
417 }
418
419 static GType panel_icon_grid_child_type(GtkContainer *container)
420 {
421 return GTK_TYPE_WIDGET;
422 }
423
424 static void panel_icon_grid_class_init(PanelIconGridClass *class)
425 {
426 GObjectClass *object_class = G_OBJECT_CLASS(class);
427 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(class);
428 GtkContainerClass *container_class = GTK_CONTAINER_CLASS(class);
429
430 object_class->set_property = panel_icon_grid_set_property;
431 object_class->get_property = panel_icon_grid_get_property;
432
433 widget_class->size_request = panel_icon_grid_size_request;
434 widget_class->size_allocate = panel_icon_grid_size_allocate;
435
436 container_class->add = panel_icon_grid_add;
437 container_class->remove = panel_icon_grid_remove;
438 container_class->forall = panel_icon_grid_forall;
439 container_class->child_type = panel_icon_grid_child_type;
440
441 g_object_class_override_property(object_class,
442 PROP_ORIENTATION,
443 "orientation");
444 g_object_class_install_property(object_class,
445 PROP_SPACING,
446 g_param_spec_int("spacing",
447 "Spacing",
448 "The amount of space between children",
449 0,
450 G_MAXINT,
451 0,
452 G_PARAM_READWRITE));
453 g_object_class_install_property(object_class,
454 PROP_CONSTRAIN_WIDTH,
455 g_param_spec_boolean("constrain-width",
456 "Constrain width",
457 "Whether to constrain width by allocated space",
458 FALSE, G_PARAM_READWRITE));
459 }
460
461 static void panel_icon_grid_init(PanelIconGrid *ig)
462 {
463 gtk_widget_set_has_window(GTK_WIDGET(ig), FALSE);
464 gtk_widget_set_redraw_on_allocate(GTK_WIDGET(ig), FALSE);
465
466 ig->orientation = GTK_ORIENTATION_HORIZONTAL;
467 }
468
469 /* Establish an icon grid in a specified container widget.
470 * The icon grid manages the contents of the container.
471 * The orientation, geometry of the elements, and spacing can be varied. All elements are the same size. */
472 GtkWidget * panel_icon_grid_new(
473 GtkOrientation orientation, gint child_width, gint child_height, gint spacing, gint border, gint target_dimension)
474 {
475 /* Create a structure representing the icon grid and collect the parameters. */
476 PanelIconGrid * ig = g_object_new(PANEL_TYPE_ICON_GRID,
477 "orientation", orientation,
478 "spacing", spacing, NULL);
479
480 ig->child_width = child_width;
481 ig->constrained_child_width = child_width;
482 ig->child_height = child_height;
483 ig->border = border;
484 ig->target_dimension = target_dimension;
485
486 return (GtkWidget *)ig;
487 }