d9355537fc4b23a5f7939ff2dbad83658110d828
[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 <gtk/gtkprivate.h>
21 #include <string.h>
22
23 #include "icon-grid.h"
24 #include "private.h"
25
26 static gboolean icon_grid_placement(IconGrid * ig);
27 static void icon_grid_geometry(IconGrid * ig, gboolean layout);
28 static void icon_grid_element_size_request(GtkWidget * widget, GtkRequisition * requisition, IconGridElement * ige);
29 static void icon_grid_size_request(GtkWidget * widget, GtkRequisition * requisition, IconGrid * ig);
30 static void icon_grid_size_allocate(GtkWidget * widget, GtkAllocation * allocation, IconGrid * ig);
31 static void icon_grid_demand_resize(IconGrid * ig);
32
33 /* Establish the widget placement of an icon grid. */
34 static gboolean icon_grid_placement(IconGrid * ig)
35 {
36 /* Make sure the container is visible. */
37 gtk_widget_show(ig->container);
38
39 /* Erase the window. */
40 GdkWindow * window = ig->widget->window;
41 if (window != NULL)
42 panel_determine_background_pixmap(ig->panel, ig->widget, window);
43
44 /* Get and save the desired container geometry. */
45 ig->container_width = ig->container->allocation.width;
46 ig->container_height = ig->container->allocation.height;
47 int child_width = ig->child_width;
48 int child_height = ig->child_height;
49
50 /* Get the required container geometry if all elements get the client's desired allocation. */
51 int container_width_needed = (ig->columns * (child_width + ig->spacing)) - ig->spacing;
52 int container_height_needed = (ig->rows * (child_height + ig->spacing)) - ig->spacing;
53
54 /* Get the constrained child geometry if the allocated geometry is insufficient.
55 * All children are still the same size and share equally in the deficit. */
56 ig->constrained_child_width = ig->child_width;
57 if ((ig->columns != 0) && (ig->rows != 0) && (ig->container_width > 1))
58 {
59 if (container_width_needed > ig->container_width)
60 ig->constrained_child_width = child_width = (ig->container_width - ((ig->columns - 1) * ig->spacing)) / ig->columns;
61 if (container_height_needed > ig->container_height)
62 child_height = (ig->container_height - ((ig->rows - 1) * ig->spacing)) / ig->rows;
63 }
64
65 /* Initialize parameters to control repositioning each visible child. */
66 GtkTextDirection direction = gtk_widget_get_direction(ig->container);
67 int limit = ig->border + ((ig->orientation == GTK_ORIENTATION_HORIZONTAL)
68 ? (ig->rows * (child_height + ig->spacing))
69 : (ig->columns * (child_width + ig->spacing)));
70 int x_initial = ((direction == GTK_TEXT_DIR_RTL)
71 ? ig->widget->allocation.width - child_width - ig->border
72 : ig->border);
73 int x_delta = child_width + ig->spacing;
74 if (direction == GTK_TEXT_DIR_RTL) x_delta = - x_delta;
75
76 /* Reposition each visible child. */
77 int x = x_initial;
78 int y = ig->border;
79 gboolean contains_sockets = FALSE;
80 IconGridElement * ige;
81 for (ige = ig->child_list; ige != NULL; ige = ige->flink)
82 {
83 if (ige->visible)
84 {
85 /* Do necessary operations on the child. */
86 gtk_widget_show(ige->widget);
87 if (((child_width != ige->widget->allocation.width) || (child_height != ige->widget->allocation.height))
88 && (child_width > 0) && (child_height > 0))
89 {
90 GtkAllocation alloc;
91 alloc.x = x;
92 alloc.y = y;
93 alloc.width = child_width;
94 alloc.height = child_height;
95 gtk_widget_size_allocate(ige->widget, &alloc);
96 gtk_widget_queue_resize(ige->widget); /* Get labels to redraw ellipsized */
97 }
98 gtk_fixed_move(GTK_FIXED(ig->widget), ige->widget, x, y);
99 gtk_widget_queue_draw(ige->widget);
100
101 /* Note if a socket is placed. */
102 if (GTK_IS_SOCKET(ige->widget))
103 contains_sockets = TRUE;
104
105 /* Advance to the next grid position. */
106 if (ig->orientation == GTK_ORIENTATION_HORIZONTAL)
107 {
108 y += child_height + ig->spacing;
109 if (y >= limit)
110 {
111 y = ig->border;
112 x += x_delta;
113 }
114 }
115 else
116 {
117 x += x_delta;
118 if ((direction == GTK_TEXT_DIR_RTL) ? (x <= 0) : (x >= limit))
119 {
120 x = x_initial;
121 y += child_height + ig->spacing;
122 }
123 }
124 }
125 }
126
127 /* Redraw the container. */
128 if (window != NULL)
129 gdk_window_invalidate_rect(window, NULL, TRUE);
130 gtk_widget_queue_draw(ig->container);
131
132 /* If the icon grid contains sockets, do special handling to get the background erased. */
133 if (contains_sockets)
134 plugin_widget_set_background(ig->widget, ig->panel);
135 return FALSE;
136 }
137
138 /* Establish the geometry of an icon grid. */
139 static void icon_grid_geometry(IconGrid * ig, gboolean layout)
140 {
141 /* Count visible children. */
142 int visible_children = 0;
143 IconGridElement * ige;
144 for (ige = ig->child_list; ige != NULL; ige = ige->flink)
145 if (ige->visible)
146 visible_children += 1;
147
148 int original_rows = ig->rows;
149 int original_columns = ig->columns;
150 int target_dimension = ig->target_dimension;
151 if (ig->orientation == GTK_ORIENTATION_HORIZONTAL)
152 {
153 /* In horizontal orientation, fit as many rows into the available height as possible.
154 * Then allocate as many columns as necessary. Guard against zerodivides. */
155 if (ig->container->allocation.height > 1)
156 target_dimension = ig->container->allocation.height;
157 ig->rows = 0;
158 if ((ig->child_height + ig->spacing) != 0)
159 ig->rows = (target_dimension + ig->spacing - ig->border * 2) / (ig->child_height + ig->spacing);
160 if (ig->rows == 0)
161 ig->rows = 1;
162 ig->columns = (visible_children + (ig->rows - 1)) / ig->rows;
163 if ((ig->columns == 1) && (ig->rows > visible_children))
164 ig->rows = visible_children;
165 }
166 else
167 {
168 /* In vertical orientation, fit as many columns into the available width as possible.
169 * Then allocate as many rows as necessary. Guard against zerodivides. */
170 if (ig->container->allocation.width > 1)
171 target_dimension = ig->container->allocation.width;
172 ig->columns = 0;
173 if ((ig->child_width + ig->spacing) != 0)
174 ig->columns = (target_dimension + ig->spacing - ig->border * 2) / (ig->child_width + ig->spacing);
175 if (ig->columns == 0)
176 ig->columns = 1;
177 ig->rows = (visible_children + (ig->columns - 1)) / ig->columns;
178 if ((ig->rows == 1) && (ig->columns > visible_children))
179 ig->columns = visible_children;
180 }
181
182 /* If the table geometry or child composition changed, redo the placement of children in table cells.
183 * This is gated by having a valid table allocation and by the "layout" parameter, which prevents a recursive loop.
184 * We do the placement later, also to prevent a recursive loop. */
185 if ((layout)
186 && (( ! ig->actual_dimension)
187 || (ig->rows != original_rows)
188 || (ig->columns != original_columns)
189 || (ig->container_width != ig->container->allocation.width)
190 || (ig->container_height != ig->container->allocation.height)
191 || (ig->children_changed)))
192 {
193 ig->actual_dimension = TRUE;
194 ig->children_changed = FALSE;
195 g_idle_add((GSourceFunc) icon_grid_placement, ig);
196 }
197 }
198
199 /* Handler for "size-request" event on the icon grid element. */
200 static void icon_grid_element_size_request(GtkWidget * widget, GtkRequisition * requisition, IconGridElement * ige)
201 {
202 /* This is our opportunity to request space for the element. */
203 IconGrid * ig = ige->ig;
204 requisition->width = ig->child_width;
205 if ((ig->constrain_width) && (ig->actual_dimension) && (ig->constrained_child_width > 1))
206 requisition->width = ig->constrained_child_width;
207 requisition->height = ig->child_height;
208 }
209
210 /* Handler for "size-request" event on the icon grid's container. */
211 static void icon_grid_size_request(GtkWidget * widget, GtkRequisition * requisition, IconGrid * ig)
212 {
213 /* This is our opportunity to request space for the layout container.
214 * Compute the geometry. Do not lay out children at this time to avoid a recursive loop. */
215 icon_grid_geometry(ig, FALSE);
216
217 /* Compute the requisition. */
218 if ((ig->columns == 0) || (ig->rows == 0))
219 {
220 requisition->width = 1;
221 requisition->height = 1;
222 gtk_widget_hide(ig->widget); /* Necessary to get the plugin to disappear */
223 }
224 else
225 {
226 int column_spaces = ig->columns - 1;
227 int row_spaces = ig->rows - 1;
228 if (column_spaces < 0) column_spaces = 0;
229 if (row_spaces < 0) row_spaces = 0;
230 requisition->width = ig->child_width * ig->columns + column_spaces * ig->spacing + 2 * ig->border;
231 requisition->height = ig->child_height * ig->rows + row_spaces * ig->spacing + 2 * ig->border;
232 gtk_widget_show(ig->widget);
233 }
234 }
235
236 /* Handler for "size-allocate" event on the icon grid's container. */
237 static void icon_grid_size_allocate(GtkWidget * widget, GtkAllocation * allocation, IconGrid * ig)
238 {
239 /* This is our notification that there is a resize of the entire panel.
240 * Compute the geometry and recompute layout if the geometry changed. */
241 icon_grid_geometry(ig, TRUE);
242 }
243
244 /* Initiate a resize. */
245 static void icon_grid_demand_resize(IconGrid * ig)
246 {
247 ig->children_changed = TRUE;
248 GtkRequisition req;
249 icon_grid_size_request(NULL, &req, ig);
250
251 if ((ig->rows != 0) || (ig->columns != 0))
252 icon_grid_placement(ig);
253 }
254
255 /* Establish an icon grid in a specified container widget.
256 * The icon grid manages the contents of the container.
257 * The orientation, geometry of the elements, and spacing can be varied. All elements are the same size. */
258 IconGrid * icon_grid_new(
259 Panel * panel, GtkWidget * container,
260 GtkOrientation orientation, gint child_width, gint child_height, gint spacing, gint border, gint target_dimension)
261 {
262 /* Create a structure representing the icon grid and collect the parameters. */
263 IconGrid * ig = g_new0(IconGrid, 1);
264 ig->panel = panel;
265 ig->container = container;
266 ig->orientation = orientation;
267 ig->child_width = child_width;
268 ig->constrained_child_width = child_width;
269 ig->child_height = child_height;
270 ig->spacing = spacing;
271 ig->border = border;
272 ig->target_dimension = target_dimension;
273
274 /* Create a layout container. */
275 ig->widget = gtk_fixed_new();
276 g_object_add_weak_pointer(G_OBJECT(ig->widget), (gpointer*)&ig->widget);
277 GTK_WIDGET_SET_FLAGS(ig->widget, GTK_NO_WINDOW);
278 gtk_widget_set_redraw_on_allocate(ig->widget, FALSE);
279 gtk_container_add(GTK_CONTAINER(ig->container), ig->widget);
280 gtk_widget_show(ig->widget);
281
282 /* Connect signals. */
283 g_signal_connect(G_OBJECT(ig->widget), "size-request", G_CALLBACK(icon_grid_size_request), (gpointer) ig);
284 g_signal_connect(G_OBJECT(container), "size-request", G_CALLBACK(icon_grid_size_request), (gpointer) ig);
285 g_signal_connect(G_OBJECT(container), "size-allocate", G_CALLBACK(icon_grid_size_allocate), (gpointer) ig);
286 return ig;
287 }
288
289 /* Add an icon grid element and establish its initial visibility. */
290 void icon_grid_add(IconGrid * ig, GtkWidget * child, gboolean visible)
291 {
292 /* Create and initialize a structure representing the child. */
293 IconGridElement * ige = g_new0(IconGridElement, 1);
294 ige->ig = ig;
295 ige->widget = child;
296 ige->visible = visible;
297
298 /* Insert at the tail of the child list. This keeps the graphics in the order they were added. */
299 if (ig->child_list == NULL)
300 ig->child_list = ige;
301 else
302 {
303 IconGridElement * ige_cursor;
304 for (ige_cursor = ig->child_list; ige_cursor->flink != NULL; ige_cursor = ige_cursor->flink) ;
305 ige_cursor->flink = ige;
306 }
307
308 /* Add the widget to the layout container. */
309 if (visible)
310 gtk_widget_show(ige->widget);
311 gtk_fixed_put(GTK_FIXED(ig->widget), ige->widget, 0, 0);
312 g_signal_connect(G_OBJECT(child), "size-request", G_CALLBACK(icon_grid_element_size_request), (gpointer) ige);
313
314 /* Do a relayout. */
315 icon_grid_demand_resize(ig);
316 }
317
318 extern void icon_grid_set_constrain_width(IconGrid * ig, gboolean constrain_width)
319 {
320 ig->constrain_width = constrain_width;
321 }
322
323 /* Remove an icon grid element. */
324 void icon_grid_remove(IconGrid * ig, GtkWidget * child)
325 {
326 IconGridElement * ige_pred = NULL;
327 IconGridElement * ige;
328 for (ige = ig->child_list; ige != NULL; ige_pred = ige, ige = ige->flink)
329 {
330 if (ige->widget == child)
331 {
332 /* The child is found. Remove from child list and layout container. */
333 gtk_widget_hide(ige->widget);
334 gtk_container_remove(GTK_CONTAINER(ig->widget), ige->widget);
335
336 if (ige_pred == NULL)
337 ig->child_list = ige->flink;
338 else
339 ige_pred->flink = ige->flink;
340
341 /* Do a relayout. */
342 icon_grid_demand_resize(ig);
343 break;
344 }
345 }
346 }
347
348 /* Reorder an icon grid element. */
349 extern void icon_grid_reorder_child(IconGrid * ig, GtkWidget * child, gint position)
350 {
351 /* Remove the child from its current position. */
352 IconGridElement * ige_pred = NULL;
353 IconGridElement * ige;
354 for (ige = ig->child_list; ige != NULL; ige_pred = ige, ige = ige->flink)
355 {
356 if (ige->widget == child)
357 {
358 if (ige_pred == NULL)
359 ig->child_list = ige->flink;
360 else
361 ige_pred->flink = ige->flink;
362 break;
363 }
364 }
365
366 /* If the child was found, insert it at the new position. */
367 if (ige != NULL)
368 {
369 if (ig->child_list == NULL)
370 {
371 ige->flink = NULL;
372 ig->child_list = ige;
373 }
374 else if (position == 0)
375 {
376 ige->flink = ig->child_list;
377 ig->child_list = ige;
378 }
379 else
380 {
381 int local_position = position - 1;
382 IconGridElement * ige_pred;
383 for (
384 ige_pred = ig->child_list;
385 ((ige_pred != NULL) && (local_position > 0));
386 local_position -= 1, ige_pred = ige_pred->flink) ;
387 ige->flink = ige_pred->flink;
388 ige_pred->flink = ige;
389 }
390
391 /* Do a relayout. */
392 if (ige->visible)
393 icon_grid_demand_resize(ig);
394 }
395 }
396
397 /* Change the geometry of an icon grid. */
398 void icon_grid_set_geometry(IconGrid * ig,
399 GtkOrientation orientation, gint child_width, gint child_height, gint spacing, gint border, gint target_dimension)
400 {
401 ig->orientation = orientation;
402 ig->child_width = child_width;
403 ig->constrained_child_width = child_width;
404 ig->child_height = child_height;
405 ig->spacing = spacing;
406 ig->border = border;
407 ig->target_dimension = target_dimension;
408 icon_grid_demand_resize(ig);
409 }
410
411 /* Change the visibility of an icon grid element. */
412 void icon_grid_set_visible(IconGrid * ig, GtkWidget * child, gboolean visible)
413 {
414 IconGridElement * ige;
415 for (ige = ig->child_list; ige != NULL; ige = ige->flink)
416 {
417 if (ige->widget == child)
418 {
419 if (ige->visible != visible)
420 {
421 /* Found, and the visibility changed. Do a relayout. */
422 ige->visible = visible;
423 if ( ! ige->visible)
424 gtk_widget_hide(ige->widget);
425 icon_grid_demand_resize(ig);
426 }
427 break;
428 }
429 }
430 }
431
432 /* Deallocate the icon grid structures. */
433 void icon_grid_free(IconGrid * ig)
434 {
435 /* Hide the layout container. */
436 if (ig->widget != NULL)
437 {
438 g_object_remove_weak_pointer(G_OBJECT(ig->widget), (gpointer*)&ig->widget);
439 gtk_widget_hide(ig->widget);
440 }
441
442 /* Free all memory. */
443 IconGridElement * ige = ig->child_list;
444 while (ige != NULL)
445 {
446 IconGridElement * ige_succ = ige->flink;
447 g_free(ige);
448 ige = ige_succ;
449 }
450 g_free(ig);
451 }