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