Adding upstream version 0.5.12.
[debian/lxpanel.git] / src / icon-grid.c
CommitLineData
2ba86315
DB
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 "panel.h"
25#include "plugin.h"
26
27static gboolean icon_grid_placement(IconGrid * ig);
28static void icon_grid_geometry(IconGrid * ig, gboolean layout);
29static void icon_grid_element_size_request(GtkWidget * widget, GtkRequisition * requisition, IconGridElement * ige);
30static void icon_grid_size_request(GtkWidget * widget, GtkRequisition * requisition, IconGrid * ig);
31static void icon_grid_size_allocate(GtkWidget * widget, GtkAllocation * allocation, IconGrid * ig);
32static void icon_grid_demand_resize(IconGrid * ig);
33
34/* Establish the widget placement of an icon grid. */
35static gboolean icon_grid_placement(IconGrid * ig)
36{
37 /* Make sure the container is visible. */
38 gtk_widget_show(ig->container);
39
40 /* Erase the window. */
41 GdkWindow * window = ig->widget->window;
42 if (window != NULL)
43 panel_determine_background_pixmap(ig->panel, ig->widget, window);
44
45 /* Get and save the desired container geometry. */
46 ig->container_width = ig->container->allocation.width;
47 ig->container_height = ig->container->allocation.height;
48 int child_width = ig->child_width;
49 int child_height = ig->child_height;
50
51 /* Get the required container geometry if all elements get the client's desired allocation. */
52 int container_width_needed = (ig->columns * (child_width + ig->spacing)) - ig->spacing;
53 int container_height_needed = (ig->rows * (child_height + ig->spacing)) - ig->spacing;
54
55 /* Get the constrained child geometry if the allocated geometry is insufficient.
56 * All children are still the same size and share equally in the deficit. */
57 ig->constrained_child_width = ig->child_width;
58 if ((ig->columns != 0) && (ig->rows != 0) && (ig->container_width > 1))
59 {
60 if (container_width_needed > ig->container_width)
61 ig->constrained_child_width = child_width = (ig->container_width - ((ig->columns - 1) * ig->spacing)) / ig->columns;
62 if (container_height_needed > ig->container_height)
63 child_height = (ig->container_height - ((ig->rows - 1) * ig->spacing)) / ig->rows;
64 }
65
66 /* Initialize parameters to control repositioning each visible child. */
67 GtkTextDirection direction = gtk_widget_get_direction(ig->container);
68 int limit = ig->border + ((ig->orientation == GTK_ORIENTATION_HORIZONTAL)
69 ? (ig->rows * (child_height + ig->spacing))
70 : (ig->columns * (child_width + ig->spacing)));
71 int x_initial = ((direction == GTK_TEXT_DIR_RTL)
72 ? ig->widget->allocation.width - child_width - ig->border
73 : ig->border);
74 int x_delta = child_width + ig->spacing;
75 if (direction == GTK_TEXT_DIR_RTL) x_delta = - x_delta;
76
77 /* Reposition each visible child. */
78 int x = x_initial;
79 int y = ig->border;
80 gboolean contains_sockets = FALSE;
81 IconGridElement * ige;
82 for (ige = ig->child_list; ige != NULL; ige = ige->flink)
83 {
84 if (ige->visible)
85 {
86 /* Do necessary operations on the child. */
87 gtk_widget_show(ige->widget);
88 if (((child_width != ige->widget->allocation.width) || (child_height != ige->widget->allocation.height))
89 && (child_width > 0) && (child_height > 0))
90 {
91 GtkAllocation alloc;
92 alloc.x = x;
93 alloc.y = y;
94 alloc.width = child_width;
95 alloc.height = child_height;
96 gtk_widget_size_allocate(ige->widget, &alloc);
97 gtk_widget_queue_resize(ige->widget); /* Get labels to redraw ellipsized */
98 }
99 gtk_fixed_move(GTK_FIXED(ig->widget), ige->widget, x, y);
100 gtk_widget_queue_draw(ige->widget);
101
102 /* Note if a socket is placed. */
103 if (GTK_IS_SOCKET(ige->widget))
104 contains_sockets = TRUE;
105
106 /* Advance to the next grid position. */
107 if (ig->orientation == GTK_ORIENTATION_HORIZONTAL)
108 {
109 y += child_height + ig->spacing;
110 if (y >= limit)
111 {
112 y = ig->border;
113 x += x_delta;
114 }
115 }
116 else
117 {
118 x += x_delta;
119 if ((direction == GTK_TEXT_DIR_RTL) ? (x <= 0) : (x >= limit))
120 {
121 x = x_initial;
122 y += child_height + ig->spacing;
123 }
124 }
125 }
126 }
127
128 /* Redraw the container. */
129 if (window != NULL)
130 gdk_window_invalidate_rect(window, NULL, TRUE);
131 gtk_widget_queue_draw(ig->container);
132
133 /* If the icon grid contains sockets, do special handling to get the background erased. */
134 if (contains_sockets)
135 plugin_widget_set_background(ig->widget, ig->panel);
136 return FALSE;
137}
138
139/* Establish the geometry of an icon grid. */
140static void icon_grid_geometry(IconGrid * ig, gboolean layout)
141{
142 /* Count visible children. */
143 int visible_children = 0;
144 IconGridElement * ige;
145 for (ige = ig->child_list; ige != NULL; ige = ige->flink)
146 if (ige->visible)
147 visible_children += 1;
148
149 int original_rows = ig->rows;
150 int original_columns = ig->columns;
151 int target_dimension = ig->target_dimension;
152 if (ig->orientation == GTK_ORIENTATION_HORIZONTAL)
153 {
154 /* In horizontal orientation, fit as many rows into the available height as possible.
155 * Then allocate as many columns as necessary. Guard against zerodivides. */
156 if (ig->container->allocation.height > 1)
157 target_dimension = ig->container->allocation.height;
158 ig->rows = 0;
159 if ((ig->child_height + ig->spacing) != 0)
160 ig->rows = (target_dimension + ig->spacing - ig->border * 2) / (ig->child_height + ig->spacing);
161 if (ig->rows == 0)
162 ig->rows = 1;
163 ig->columns = (visible_children + (ig->rows - 1)) / ig->rows;
164 if ((ig->columns == 1) && (ig->rows > visible_children))
165 ig->rows = visible_children;
166 }
167 else
168 {
169 /* In vertical orientation, fit as many columns into the available width as possible.
170 * Then allocate as many rows as necessary. Guard against zerodivides. */
171 if (ig->container->allocation.width > 1)
172 target_dimension = ig->container->allocation.width;
173 ig->columns = 0;
174 if ((ig->child_width + ig->spacing) != 0)
175 ig->columns = (target_dimension + ig->spacing - ig->border * 2) / (ig->child_width + ig->spacing);
176 if (ig->columns == 0)
177 ig->columns = 1;
178 ig->rows = (visible_children + (ig->columns - 1)) / ig->columns;
179 if ((ig->rows == 1) && (ig->columns > visible_children))
180 ig->columns = visible_children;
181 }
182
183 /* If the table geometry or child composition changed, redo the placement of children in table cells.
184 * This is gated by having a valid table allocation and by the "layout" parameter, which prevents a recursive loop.
185 * We do the placement later, also to prevent a recursive loop. */
186 if ((layout)
187 && (( ! ig->actual_dimension)
188 || (ig->rows != original_rows)
189 || (ig->columns != original_columns)
190 || (ig->container_width != ig->container->allocation.width)
191 || (ig->container_height != ig->container->allocation.height)
192 || (ig->children_changed)))
193 {
194 ig->actual_dimension = TRUE;
195 ig->children_changed = FALSE;
196 g_idle_add((GSourceFunc) icon_grid_placement, ig);
197 }
198}
199
200/* Handler for "size-request" event on the icon grid element. */
201static void icon_grid_element_size_request(GtkWidget * widget, GtkRequisition * requisition, IconGridElement * ige)
202{
203 /* This is our opportunity to request space for the element. */
204 IconGrid * ig = ige->ig;
205 requisition->width = ig->child_width;
206 if ((ig->constrain_width) && (ig->actual_dimension) && (ig->constrained_child_width > 1))
207 requisition->width = ig->constrained_child_width;
208 requisition->height = ig->child_height;
209}
210
211/* Handler for "size-request" event on the icon grid's container. */
212static void icon_grid_size_request(GtkWidget * widget, GtkRequisition * requisition, IconGrid * ig)
213{
214 /* This is our opportunity to request space for the layout container.
215 * Compute the geometry. Do not lay out children at this time to avoid a recursive loop. */
216 icon_grid_geometry(ig, FALSE);
217
218 /* Compute the requisition. */
219 if ((ig->columns == 0) || (ig->rows == 0))
220 {
221 requisition->width = 1;
222 requisition->height = 1;
223 gtk_widget_hide(ig->widget); /* Necessary to get the plugin to disappear */
224 }
225 else
226 {
227 int column_spaces = ig->columns - 1;
228 int row_spaces = ig->rows - 1;
229 if (column_spaces < 0) column_spaces = 0;
230 if (row_spaces < 0) row_spaces = 0;
231 requisition->width = ig->child_width * ig->columns + column_spaces * ig->spacing + 2 * ig->border;
232 requisition->height = ig->child_height * ig->rows + row_spaces * ig->spacing + 2 * ig->border;
233 gtk_widget_show(ig->widget);
234 }
235}
236
237/* Handler for "size-allocate" event on the icon grid's container. */
238static void icon_grid_size_allocate(GtkWidget * widget, GtkAllocation * allocation, IconGrid * ig)
239{
240 /* This is our notification that there is a resize of the entire panel.
241 * Compute the geometry and recompute layout if the geometry changed. */
242 icon_grid_geometry(ig, TRUE);
243}
244
245/* Initiate a resize. */
246static void icon_grid_demand_resize(IconGrid * ig)
247{
248 ig->children_changed = TRUE;
249 GtkRequisition req;
250 icon_grid_size_request(NULL, &req, ig);
251
252 if ((ig->rows != 0) || (ig->columns != 0))
253 icon_grid_placement(ig);
254}
255
256/* Establish an icon grid in a specified container widget.
257 * The icon grid manages the contents of the container.
258 * The orientation, geometry of the elements, and spacing can be varied. All elements are the same size. */
259IconGrid * icon_grid_new(
260 Panel * panel, GtkWidget * container,
261 GtkOrientation orientation, gint child_width, gint child_height, gint spacing, gint border, gint target_dimension)
262{
263 /* Create a structure representing the icon grid and collect the parameters. */
264 IconGrid * ig = g_new0(IconGrid, 1);
265 ig->panel = panel;
266 ig->container = container;
267 ig->orientation = orientation;
268 ig->child_width = child_width;
269 ig->constrained_child_width = child_width;
270 ig->child_height = child_height;
271 ig->spacing = spacing;
272 ig->border = border;
273 ig->target_dimension = target_dimension;
274
275 /* Create a layout container. */
276 ig->widget = gtk_fixed_new();
277 GTK_WIDGET_SET_FLAGS(ig->widget, GTK_NO_WINDOW);
32a67dc7 278 gtk_widget_set_redraw_on_allocate(ig->widget, FALSE);
2ba86315
DB
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. */
290void 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. */
4652f59b
DB
309 if (visible)
310 gtk_widget_show(ige->widget);
2ba86315
DB
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
318extern 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. */
324void 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. */
349extern 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. */
398void 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. */
412void 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. */
433void icon_grid_free(IconGrid * ig)
434{
435 /* Hide the layout container. */
436 if (ig->widget != NULL)
437 gtk_widget_hide(ig->widget);
438
439 /* Free all memory. */
440 IconGridElement * ige = ig->child_list;
441 while (ige != NULL)
442 {
443 IconGridElement * ige_succ = ige->flink;
444 g_free(ige);
445 ige = ige_succ;
446 }
447 g_free(ig);
448}