[SF#820] Fix "dynamic" panel width allocation.
[lxde/lxpanel.git] / src / icon-grid.c
1 /*
2 * Copyright (C) 2009-2010 Marty Jack <martyj19@comcast.net>
3 * 2009-2010 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
4 * 2014-2016 Andriy Grytsenko <andrej@rep.kiev.ua>
5 *
6 * This file is a part of LXPanel project.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22
23 #include <gtk/gtk.h>
24 #include <string.h>
25
26 #include "icon-grid.h"
27 #include "gtk-compat.h"
28
29 /* Properties */
30 enum {
31 PROP_0,
32 PROP_ORIENTATION,
33 PROP_SPACING,
34 PROP_CONSTRAIN_WIDTH,
35 PROP_ASPECT_WIDTH
36 //PROP_FILL_WIDTH
37 };
38
39 /* Child properties */
40 enum {
41 CHILD_PROP_0,
42 CHILD_PROP_POSITION
43 };
44
45 /* Representative of an icon grid. This is a manager that packs widgets into a rectangular grid whose size adapts to conditions. */
46 struct _PanelIconGrid
47 {
48 GtkContainer container; /* Parent widget */
49 GList * children; /* List of icon grid elements */
50 GtkOrientation orientation; /* Desired orientation */
51 gint child_width; /* Desired child width */
52 gint child_height; /* Desired child height */
53 guint spacing; /* Desired spacing between grid elements */
54 gint target_dimension; /* Desired dimension perpendicular to orientation */
55 gboolean constrain_width : 1; /* True if width should be constrained by allocated space */
56 gboolean aspect_width : 1; /* True if children should maintain aspect */
57 gboolean fill_width : 1; /* True if children should fill unused width */
58 int rows; /* Computed layout rows */
59 int columns; /* Computed layout columns */
60 GdkWindow *event_window; /* Event window if NO_WINDOW is set */
61 GtkWidget *dest_item; /* Drag destination to draw focus */
62 PanelIconGridDropPosition dest_pos; /* Position to draw focus */
63 };
64
65 struct _PanelIconGridClass
66 {
67 GtkContainerClass parent_class;
68 };
69
70 static void icon_grid_element_check_requisition(PanelIconGrid *ig,
71 GtkRequisition *requisition)
72 {
73 if (ig->aspect_width && !ig->constrain_width &&
74 requisition->width > 1 && requisition->height > 1)
75 {
76 /* calculate width from aspect */
77 gdouble ratio = (gdouble)requisition->width / requisition->height;
78 requisition->width = MAX(ig->child_height * ratio, ig->child_width);
79 }
80 else
81 {
82 requisition->width = ig->child_width;
83 }
84 requisition->height = ig->child_height;
85 }
86
87 /* Establish the widget placement of an icon grid. */
88 static void panel_icon_grid_size_allocate(GtkWidget *widget,
89 GtkAllocation *allocation)
90 {
91 PanelIconGrid *ig = PANEL_ICON_GRID(widget);
92 GtkRequisition req;
93 GtkAllocation child_allocation;
94 int child_width;
95 int child_height;
96 GtkTextDirection direction;
97 guint border;
98 guint x_border, y_border;
99 int x_delta;
100 guint next_coord;
101 guint x, y;
102 GList *ige;
103 GtkWidget *child;
104
105 /* Apply given allocation */
106 gtk_widget_set_allocation(widget, allocation);
107 border = gtk_container_get_border_width(GTK_CONTAINER(widget));
108 x_border = y_border = border;
109 if (ig->orientation == GTK_ORIENTATION_HORIZONTAL)
110 x_border = MAX(border, ig->spacing / 2);
111 else
112 y_border = MAX(border, ig->spacing / 2);
113 child_allocation.width = MAX(allocation->width - 2 * border, 0);
114 child_allocation.height = MAX(allocation->height - 2 * border, 0);
115 if (gtk_widget_get_realized(widget))
116 {
117 if (!gtk_widget_get_has_window(widget))
118 {
119 child_allocation.x = allocation->x + border;
120 child_allocation.y = allocation->y + border;
121 }
122 else
123 {
124 child_allocation.x = 0;
125 child_allocation.y = 0;
126 }
127 if (ig->event_window != NULL)
128 gdk_window_move_resize(ig->event_window,
129 child_allocation.x,
130 child_allocation.y,
131 child_allocation.width,
132 child_allocation.height);
133 if (gtk_widget_get_has_window(widget))
134 gdk_window_move_resize(gtk_widget_get_window(widget),
135 allocation->x + border,
136 allocation->y + border,
137 child_allocation.width,
138 child_allocation.height);
139 }
140
141 /* Get and save the desired container geometry. */
142 if (ig->orientation == GTK_ORIENTATION_HORIZONTAL && allocation->height > 1)
143 ig->target_dimension = allocation->height;
144 else if (ig->orientation == GTK_ORIENTATION_VERTICAL && allocation->width > 1)
145 ig->target_dimension = allocation->width;
146 child_width = ig->child_width;
147 child_height = ig->child_height;
148
149 /* FIXME: is there any sense to recheck rows and columns again?
150 GTK+ should have it done right before this call. */
151
152 /* Get the constrained child geometry if the allocated geometry is insufficient.
153 * All children are still the same size and share equally in the deficit. */
154 if ((ig->columns != 0) && (ig->rows != 0) && (child_allocation.width > 0))
155 {
156 if (ig->constrain_width &&
157 (x_delta = (child_allocation.width + ig->spacing) / ig->columns - ig->spacing) < child_width)
158 child_width = MAX(2, x_delta);
159 /* fill vertical space evenly in horisontal orientation */
160 if (ig->orientation == GTK_ORIENTATION_HORIZONTAL &&
161 (x_delta = (child_allocation.height + ig->spacing) / ig->rows - ig->spacing) > child_height)
162 child_height = MAX(2, x_delta);
163 }
164
165 /* Initialize parameters to control repositioning each visible child. */
166 direction = gtk_widget_get_direction(widget);
167 x = (direction == GTK_TEXT_DIR_RTL) ? allocation->width - x_border : x_border;
168 y = y_border;
169 x_delta = 0;
170 next_coord = border;
171
172 /* Reposition each visible child. */
173 for (ige = ig->children; ige != NULL; ige = ige->next)
174 {
175 child = ige->data;
176 if (gtk_widget_get_visible(child))
177 {
178 /* Do necessary operations on the child. */
179 gtk_widget_get_child_requisition(child, &req);
180 icon_grid_element_check_requisition(ig, &req);
181 child_allocation.width = MIN(req.width, child_width);
182 child_allocation.height = MIN(req.height, child_height);
183
184 /* Check this grid position */
185 if (ig->orientation == GTK_ORIENTATION_HORIZONTAL)
186 {
187 y = next_coord;
188 if (y + child_height > allocation->height - y_border && y > y_border)
189 {
190 y = y_border;
191 if (direction == GTK_TEXT_DIR_RTL)
192 x -= (x_delta + ig->spacing);
193 else
194 x += (x_delta + ig->spacing);
195 x_delta = 0;
196 // FIXME: if fill_width and rows = 1 then allocate whole column
197 }
198 next_coord = y + child_height + ig->spacing;
199 x_delta = MAX(x_delta, child_allocation.width);
200 }
201 else
202 {
203 // FIXME: if fill_width then use aspect to check delta
204 if (direction == GTK_TEXT_DIR_RTL)
205 {
206 next_coord = x - child_allocation.width;
207 if (x < allocation->width - x_border)
208 {
209 next_coord -= ig->spacing;
210 if (next_coord < x_border)
211 {
212 next_coord = allocation->width - x_border;
213 y += child_height + ig->spacing;
214 }
215 }
216 x = next_coord;
217 }
218 else
219 {
220 x = next_coord;
221 if (x + child_allocation.width > allocation->width - x_border && x > x_border)
222 {
223 x = x_border;
224 y += child_height + ig->spacing;
225 }
226 next_coord = x + child_allocation.width + ig->spacing;
227 }
228 }
229 child_allocation.x = x;
230 if (req.height < child_height - 1)
231 y += (child_height - req.height) / 2;
232 child_allocation.y = y;
233
234 if (!gtk_widget_get_has_window (widget))
235 {
236 child_allocation.x += allocation->x;
237 child_allocation.y += allocation->y;
238 }
239 // FIXME: if fill_width and rows > 1 then delay allocation
240 gtk_widget_size_allocate(child, &child_allocation);
241 }
242 }
243 }
244
245 /* Establish the geometry of an icon grid. */
246 static void panel_icon_grid_size_request(GtkWidget *widget,
247 GtkRequisition *requisition)
248 {
249 PanelIconGrid *ig = PANEL_ICON_GRID(widget);
250 GList *ige;
251 int target_dimension = MAX(ig->target_dimension, 0);
252 guint border = gtk_container_get_border_width(GTK_CONTAINER(widget));
253 guint target_borders = MAX(2 * border, ig->spacing);
254 gint old_rows = ig->rows;
255 gint old_columns = ig->columns;
256 gint row = 0, w = 0;
257 GtkRequisition child_requisition;
258
259 requisition->width = 0;
260 requisition->height = 0;
261 ig->rows = 0;
262 ig->columns = 0;
263 if (ig->orientation == GTK_ORIENTATION_HORIZONTAL)
264 {
265 /* In horizontal orientation, fit as many rows into the available height as possible.
266 * Then allocate as many columns as necessary. Guard against zerodivides. */
267 if ((ig->child_height + ig->spacing) != 0)
268 ig->rows = (target_dimension + ig->spacing + border * 2) / (ig->child_height + ig->spacing);
269 if (ig->rows == 0)
270 ig->rows = 1;
271 /* Count visible children and columns. */
272 for (ige = ig->children; ige != NULL; ige = ige->next)
273 if (gtk_widget_get_visible(ige->data))
274 {
275 gtk_widget_size_request(ige->data, &child_requisition);
276 icon_grid_element_check_requisition(ig, &child_requisition);
277 if (row == 0)
278 ig->columns++;
279 w = MAX(w, child_requisition.width);
280 row++;
281 if (row == ig->rows)
282 {
283 if (requisition->width > 0)
284 requisition->width += ig->spacing;
285 requisition->width += w;
286 row = w = 0;
287 }
288 }
289 if (w > 0)
290 {
291 if (requisition->width > 0)
292 requisition->width += ig->spacing;
293 requisition->width += w;
294 }
295 if (requisition->width > 0)
296 requisition->width += target_borders;
297 /* if ((ig->columns == 1) && (ig->rows > visible_children))
298 ig->rows = visible_children; */
299 if (ig->columns > 0)
300 requisition->height = (ig->child_height + ig->spacing) * ig->rows - ig->spacing + 2 * border;
301 }
302 else
303 {
304 /* In vertical orientation, fit as many columns into the available width as possible.
305 * Then allocate as many rows as necessary. Guard against zerodivides. */
306 if ((ig->child_width + ig->spacing) != 0)
307 ig->columns = (target_dimension + ig->spacing + border * 2) / (ig->child_width + ig->spacing);
308 if (ig->columns == 0)
309 ig->columns = 1;
310 /* Count visible children and rows. */
311 for (ige = ig->children; ige != NULL; ige = ige->next)
312 if (gtk_widget_get_visible(ige->data))
313 {
314 gtk_widget_size_request(ige->data, &child_requisition);
315 icon_grid_element_check_requisition(ig, &child_requisition);
316 if (w > 0 && w + child_requisition.width > target_dimension)
317 {
318 w = 0;
319 ig->rows++;
320 }
321 if (w > 0)
322 w += ig->spacing;
323 w += child_requisition.width;
324 requisition->width = MAX(requisition->width, w);
325 }
326 if (w > 0)
327 ig->rows++;
328 if (requisition->width > 0)
329 requisition->width += 2 * border;
330 if (ig->rows > 0)
331 requisition->height = (ig->child_height + ig->spacing) * ig->rows - ig->spacing + target_borders;
332 }
333
334 /* Apply the requisition. */
335 if (ig->rows != old_rows || ig->columns != old_columns)
336 gtk_widget_queue_resize(widget);
337 }
338
339 #if GTK_CHECK_VERSION(3, 0, 0)
340 static void panel_icon_grid_get_preferred_width(GtkWidget *widget,
341 gint *minimal_width,
342 gint *natural_width)
343 {
344 PanelIconGrid *ig = PANEL_ICON_GRID(widget);
345 GtkRequisition requisition;
346
347 if (ig->orientation == GTK_ORIENTATION_VERTICAL)
348 {
349 if (minimal_width)
350 *minimal_width = MIN(ig->target_dimension, ig->child_width);
351 if (natural_width)
352 *natural_width = ig->target_dimension;
353 return;
354 }
355 panel_icon_grid_size_request(widget, &requisition);
356 if (minimal_width)
357 *minimal_width = requisition.width;
358 if (natural_width)
359 *natural_width = requisition.width;
360 }
361
362 static void panel_icon_grid_get_preferred_height(GtkWidget *widget,
363 gint *minimal_height,
364 gint *natural_height)
365 {
366 PanelIconGrid *ig = PANEL_ICON_GRID(widget);
367 GtkRequisition requisition;
368
369 if (ig->orientation == GTK_ORIENTATION_HORIZONTAL)
370 requisition.height = ig->target_dimension;
371 else
372 panel_icon_grid_size_request(widget, &requisition);
373 if (minimal_height)
374 *minimal_height = requisition.height;
375 if (natural_height)
376 *natural_height = requisition.height;
377 }
378 #endif
379
380 /* Add an icon grid element and establish its initial visibility. */
381 static void panel_icon_grid_add(GtkContainer *container, GtkWidget *widget)
382 {
383 PanelIconGrid *ig = PANEL_ICON_GRID(container);
384
385 /* Insert at the tail of the child list. This keeps the graphics in the order they were added. */
386 ig->children = g_list_append(ig->children, widget);
387
388 /* Add the widget to the layout container. */
389 gtk_widget_set_parent(widget, GTK_WIDGET(container));
390 // gtk_widget_queue_resize(GTK_WIDGET(container));
391 }
392
393 void panel_icon_grid_set_constrain_width(PanelIconGrid * ig, gboolean constrain_width)
394 {
395 g_return_if_fail(PANEL_IS_ICON_GRID(ig));
396
397 if ((!ig->constrain_width && !constrain_width) ||
398 (ig->constrain_width && constrain_width))
399 return;
400
401 ig->constrain_width = !!constrain_width;
402 gtk_widget_queue_resize(GTK_WIDGET(ig));
403 }
404
405 void panel_icon_grid_set_aspect_width(PanelIconGrid * ig, gboolean aspect_width)
406 {
407 g_return_if_fail(PANEL_IS_ICON_GRID(ig));
408
409 if ((!ig->aspect_width && !aspect_width) || (ig->aspect_width && aspect_width))
410 return;
411
412 ig->aspect_width = !!aspect_width;
413 gtk_widget_queue_resize(GTK_WIDGET(ig));
414 }
415
416 /* void panel_icon_grid_set_fill_width(PanelIconGrid * ig, gboolean fill_width)
417 {
418 g_return_if_fail(PANEL_IS_ICON_GRID(ig));
419
420 if ((!ig->fill_width && !fill_width) || (ig->fill_width && fill_width))
421 return;
422
423 ig->fill_width = !!fill_width;
424 gtk_widget_queue_resize(GTK_WIDGET(ig));
425 } */
426
427 /* Remove an icon grid element. */
428 static void panel_icon_grid_remove(GtkContainer *container, GtkWidget *widget)
429 {
430 PanelIconGrid *ig = PANEL_ICON_GRID(container);
431 GList *children = ig->children;
432 GtkWidget *child;
433
434 while (children)
435 {
436 child = children->data;
437 if (widget == child)
438 {
439 gboolean was_visible = gtk_widget_get_visible(widget);
440
441 /* The child is found. Remove from child list and layout container. */
442 gtk_widget_unparent (widget);
443 ig->children = g_list_remove_link(ig->children, children);
444 g_list_free(children);
445
446 /* Do a relayout if needed. */
447 if (was_visible)
448 gtk_widget_queue_resize(GTK_WIDGET(ig));
449 break;
450 }
451 children = children->next;
452 }
453 }
454
455 /* Get the index of an icon grid element. Actually it's
456 the same as gtk_container_child_get(ig, child, "position", &pos, NULL)
457 but more convenient to use. */
458 gint panel_icon_grid_get_child_position(PanelIconGrid * ig, GtkWidget * child)
459 {
460 g_return_val_if_fail(PANEL_IS_ICON_GRID(ig), -1);
461
462 return g_list_index(ig->children, child);
463 }
464
465 /* Reorder an icon grid element. */
466 void panel_icon_grid_reorder_child(PanelIconGrid * ig, GtkWidget * child, gint position)
467 {
468 GList *old_link;
469 GList *new_link;
470 gint old_position;
471
472 g_return_if_fail(PANEL_IS_ICON_GRID(ig));
473 g_return_if_fail(GTK_IS_WIDGET(child));
474
475 old_link = ig->children;
476 old_position = 0;
477 while (old_link)
478 {
479 if (old_link->data == child)
480 break;
481 old_link = old_link->next;
482 old_position++;
483 }
484
485 g_return_if_fail(old_link != NULL);
486
487 if (position == old_position)
488 return;
489
490 /* Remove the child from its current position. */
491 ig->children = g_list_delete_link(ig->children, old_link);
492 if (position < 0)
493 new_link = NULL;
494 else
495 new_link = g_list_nth(ig->children, position);
496
497 /* If the child was found, insert it at the new position. */
498 ig->children = g_list_insert_before(ig->children, new_link, child);
499
500 /* Do a relayout. */
501 if (gtk_widget_get_visible(child) && gtk_widget_get_visible(GTK_WIDGET(ig)))
502 gtk_widget_queue_resize(child);
503 }
504
505 guint panel_icon_grid_get_n_children(PanelIconGrid * ig)
506 {
507 g_return_val_if_fail(PANEL_IS_ICON_GRID(ig), 0);
508
509 return g_list_length(ig->children);
510 }
511
512 /* Change the geometry of an icon grid. */
513 void panel_icon_grid_set_geometry(PanelIconGrid * ig,
514 GtkOrientation orientation, gint child_width, gint child_height, gint spacing, gint border, gint target_dimension)
515 {
516 g_return_if_fail(PANEL_IS_ICON_GRID(ig));
517
518 gtk_container_set_border_width(GTK_CONTAINER(ig), border);
519
520 if (ig->orientation == orientation && ig->child_width == child_width &&
521 ig->child_height == child_height && (gint)ig->spacing == spacing &&
522 ig->target_dimension == target_dimension)
523 return;
524
525 ig->orientation = orientation;
526 ig->child_width = child_width;
527 ig->child_height = child_height;
528 ig->spacing = MAX(spacing, 1);
529 ig->target_dimension = MAX(target_dimension, 0);
530 gtk_widget_queue_resize(GTK_WIDGET(ig));
531 }
532
533 /* get position for coordinates, return FALSE if it's outside of icon grid */
534 gboolean panel_icon_grid_get_dest_at_pos(PanelIconGrid * ig, gint x, gint y,
535 GtkWidget ** child, PanelIconGridDropPosition * pos)
536 {
537 GtkAllocation allocation;
538 PanelIconGridDropPosition drop_pos;
539 GtkWidget *widget;
540 GList *ige;
541 gboolean rtl, upper = TRUE;
542
543 g_return_val_if_fail(PANEL_IS_ICON_GRID(ig), FALSE);
544
545 widget = GTK_WIDGET(ig);
546 if (!gtk_widget_get_realized(widget))
547 return FALSE;
548 if (!gtk_widget_get_has_window(widget))
549 return FALSE;
550
551 rtl = (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL);
552 if (ig->orientation == GTK_ORIENTATION_HORIZONTAL)
553 {
554 for (ige = ig->children; ige != NULL; ige = ige->next)
555 {
556 gtk_widget_get_allocation(ige->data, &allocation);
557 if (x < allocation.x)
558 {
559 if (!rtl)
560 {
561 /* reached next column */
562 drop_pos = PANEL_ICON_GRID_DROP_LEFT_BEFORE;
563 break;
564 }
565 }
566 else if (x < (allocation.x + allocation.width))
567 {
568 /* within this column */
569 if (y < allocation.y)
570 {
571 /* reached next row */
572 if (upper)
573 drop_pos = rtl ? PANEL_ICON_GRID_DROP_RIGHT_BEFORE : PANEL_ICON_GRID_DROP_LEFT_BEFORE;
574 else
575 drop_pos = PANEL_ICON_GRID_DROP_ABOVE;
576 break;
577 }
578 else if (y < (allocation.y + allocation.height))
579 {
580 /* within this row */
581 drop_pos = PANEL_ICON_GRID_DROP_INTO;
582 break;
583 }
584 upper = FALSE;
585 }
586 else if (rtl)
587 {
588 /* reached next column */
589 drop_pos = PANEL_ICON_GRID_DROP_RIGHT_BEFORE;
590 break;
591 }
592 }
593 }
594 else
595 {
596 for (ige = ig->children; ige != NULL; ige = ige->next)
597 {
598 gtk_widget_get_allocation(ige->data, &allocation);
599 if (y < allocation.y)
600 {
601 /* reached next row */
602 drop_pos = PANEL_ICON_GRID_DROP_ABOVE;
603 break;
604 }
605 else if (y < (allocation.y + allocation.height))
606 {
607 /* within this row */
608 if (x < allocation.x)
609 {
610 if (!rtl)
611 {
612 /* reached next column */
613 if (upper)
614 drop_pos = PANEL_ICON_GRID_DROP_ABOVE;
615 else
616 drop_pos = PANEL_ICON_GRID_DROP_LEFT_BEFORE;
617 break;
618 }
619 }
620 else if (x < (allocation.x + allocation.width))
621 {
622 /* within this column */
623 drop_pos = PANEL_ICON_GRID_DROP_INTO;
624 break;
625 }
626 else if (rtl)
627 {
628 /* reached next column */
629 if (upper)
630 drop_pos = PANEL_ICON_GRID_DROP_ABOVE;
631 else
632 drop_pos = PANEL_ICON_GRID_DROP_RIGHT_BEFORE;
633 break;
634 }
635 upper = FALSE;
636 }
637 }
638 }
639 if (ige == NULL)
640 {
641 /* not within allocated space */
642 ige = g_list_last(ig->children);
643 if (ig->orientation != GTK_ORIENTATION_HORIZONTAL)
644 drop_pos = PANEL_ICON_GRID_DROP_BELOW;
645 else if (rtl)
646 drop_pos = PANEL_ICON_GRID_DROP_LEFT_AFTER;
647 else
648 drop_pos = PANEL_ICON_GRID_DROP_RIGHT_AFTER;
649 }
650 if (child)
651 *child = (ige == NULL) ? NULL : ige->data;
652 if (pos)
653 *pos = drop_pos;
654 return TRUE;
655 }
656
657 static void panel_icon_grid_queue_draw_child(PanelIconGrid * ig, GtkWidget * child)
658 {
659 GtkWidget *widget = GTK_WIDGET(ig);
660 GtkAllocation allocation;
661 GdkRectangle rect;
662
663 if (!gtk_widget_get_realized(widget))
664 return;
665 if (!gtk_widget_get_has_window(widget))
666 return;
667
668 gtk_widget_get_allocation(child, &allocation);
669
670 switch (ig->dest_pos)
671 {
672 case PANEL_ICON_GRID_DROP_LEFT_AFTER:
673 case PANEL_ICON_GRID_DROP_LEFT_BEFORE:
674 rect.x = allocation.x - 2;
675 rect.width = 2;
676 rect.y = allocation.y;
677 rect.height = allocation.height;
678 break;
679 case PANEL_ICON_GRID_DROP_RIGHT_AFTER:
680 case PANEL_ICON_GRID_DROP_RIGHT_BEFORE:
681 rect.x = allocation.x + allocation.width;
682 rect.width = 2;
683 rect.y = allocation.y;
684 rect.height = allocation.height;
685 break;
686 case PANEL_ICON_GRID_DROP_BELOW:
687 rect.x = allocation.x;
688 rect.width = allocation.width;
689 rect.y = allocation.y + allocation.height;
690 rect.height = 2;
691 break;
692 case PANEL_ICON_GRID_DROP_ABOVE:
693 rect.x = allocation.x;
694 rect.width = allocation.width;
695 rect.y = allocation.y - 2;
696 rect.height = 2;
697 break;
698 case PANEL_ICON_GRID_DROP_INTO:
699 default:
700 rect.x = allocation.x - 1;
701 rect.width = allocation.width + 2;
702 rect.y = allocation.y - 1;
703 rect.height = allocation.height + 2;
704 }
705
706 if (rect.width > 0 && rect.height > 0)
707 gdk_window_invalidate_rect(gtk_widget_get_window(widget), &rect, TRUE);
708 }
709
710 /* sets data and renders widget appropriately, need be drawable and realized */
711 void panel_icon_grid_set_drag_dest(PanelIconGrid * ig, GtkWidget * child,
712 PanelIconGridDropPosition pos)
713 {
714 GtkWidget *widget;
715 GtkWidget *current_dest;
716
717 g_return_if_fail(PANEL_IS_ICON_GRID(ig));
718
719 widget = GTK_WIDGET(ig);
720
721 if (!gtk_widget_get_realized(widget))
722 return;
723 if (!gtk_widget_get_has_window(widget))
724 return;
725
726 // reset previous state
727 current_dest = ig->dest_item;
728 if (current_dest)
729 {
730 ig->dest_item = NULL;
731 panel_icon_grid_queue_draw_child(ig, current_dest);
732 }
733
734 // need a special support for empty grid?
735 ig->dest_pos = pos;
736
737 // remember new state
738 if (child && g_list_find(ig->children, child))
739 {
740 ig->dest_item = child;
741 panel_icon_grid_queue_draw_child(ig, child);
742 }
743 }
744
745 PanelIconGridDropPosition panel_icon_grid_get_drag_dest(PanelIconGrid * ig,
746 GtkWidget ** child)
747 {
748 g_return_val_if_fail(PANEL_IS_ICON_GRID(ig), 0);
749
750 if (child)
751 *child = ig->dest_item;
752 return ig->dest_pos;
753 }
754
755
756 G_DEFINE_TYPE_WITH_CODE(PanelIconGrid, panel_icon_grid, GTK_TYPE_CONTAINER,
757 G_IMPLEMENT_INTERFACE(GTK_TYPE_ORIENTABLE, NULL));
758
759 static void panel_icon_grid_set_property(GObject *object, guint prop_id,
760 const GValue *value, GParamSpec *pspec)
761 {
762 PanelIconGrid *ig = PANEL_ICON_GRID(object);
763 guint spacing;
764 GtkOrientation orientation;
765
766 switch (prop_id)
767 {
768 case PROP_ORIENTATION:
769 orientation = g_value_get_enum(value);
770 if (orientation != ig->orientation)
771 {
772 ig->orientation = orientation;
773 gtk_widget_queue_resize(GTK_WIDGET(ig));
774 }
775 break;
776 case PROP_SPACING:
777 spacing = g_value_get_uint(value);
778 if (spacing != ig->spacing)
779 {
780 ig->spacing = spacing;
781 g_object_notify(object, "spacing");
782 gtk_widget_queue_resize(GTK_WIDGET(ig));
783 }
784 break;
785 case PROP_CONSTRAIN_WIDTH:
786 panel_icon_grid_set_constrain_width(ig, g_value_get_boolean(value));
787 break;
788 case PROP_ASPECT_WIDTH:
789 panel_icon_grid_set_aspect_width(ig, g_value_get_boolean(value));
790 break;
791 /* case PROP_FILL_WIDTH:
792 panel_icon_grid_set_fill_width(ig, g_value_get_boolean(value));
793 break; */
794 default:
795 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
796 break;
797 }
798 }
799
800 static void panel_icon_grid_get_property(GObject *object, guint prop_id,
801 GValue *value, GParamSpec *pspec)
802 {
803 PanelIconGrid *ig = PANEL_ICON_GRID(object);
804
805 switch (prop_id)
806 {
807 case PROP_ORIENTATION:
808 g_value_set_enum(value, ig->orientation);
809 break;
810 case PROP_SPACING:
811 g_value_set_uint(value, ig->spacing);
812 break;
813 case PROP_CONSTRAIN_WIDTH:
814 g_value_set_boolean(value, ig->constrain_width);
815 break;
816 case PROP_ASPECT_WIDTH:
817 g_value_set_boolean(value, ig->aspect_width);
818 break;
819 /* case PROP_FILL_WIDTH:
820 g_value_set_boolean(value, ig->fill_width);
821 break; */
822 default:
823 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
824 break;
825 }
826 }
827
828 /* realize()...expose() are taken from GtkEventBox implementation */
829 static void panel_icon_grid_realize(GtkWidget *widget)
830 {
831 PanelIconGrid *ig = PANEL_ICON_GRID(widget);
832 GdkWindow *window;
833 GtkStyle *style;
834 GtkAllocation allocation;
835 GdkWindowAttr attributes;
836 guint border = gtk_container_get_border_width(GTK_CONTAINER(widget));
837 gint attributes_mask;
838 gboolean visible_window;
839
840 #if GTK_CHECK_VERSION(2, 20, 0)
841 gtk_widget_set_realized(widget, TRUE);
842 #else
843 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
844 #endif
845
846 gtk_widget_get_allocation(widget, &allocation);
847 attributes.x = allocation.x + border;
848 attributes.y = allocation.y + border;
849 attributes.width = allocation.width - 2 * border;
850 attributes.height = allocation.height - 2 * border;
851 attributes.window_type = GDK_WINDOW_CHILD;
852 attributes.event_mask = gtk_widget_get_events(widget)
853 | GDK_BUTTON_MOTION_MASK
854 | GDK_BUTTON_PRESS_MASK
855 | GDK_BUTTON_RELEASE_MASK
856 | GDK_EXPOSURE_MASK
857 | GDK_ENTER_NOTIFY_MASK
858 | GDK_LEAVE_NOTIFY_MASK;
859
860 visible_window = gtk_widget_get_has_window(widget);
861 if (visible_window)
862 {
863 attributes.visual = gtk_widget_get_visual(widget);
864 #if !GTK_CHECK_VERSION(3, 0, 0)
865 attributes.colormap = gtk_widget_get_colormap(widget);
866 #endif
867 attributes.wclass = GDK_INPUT_OUTPUT;
868
869 #if GTK_CHECK_VERSION(3, 0, 0)
870 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
871 #else
872 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
873 #endif
874
875 window = gdk_window_new(gtk_widget_get_parent_window(widget),
876 &attributes, attributes_mask);
877 gtk_widget_set_window(widget, window);
878 gdk_window_set_user_data(window, widget);
879 }
880 else
881 {
882 window = gtk_widget_get_parent_window(widget);
883 gtk_widget_set_window(widget, window);
884 g_object_ref(window);
885
886 attributes.wclass = GDK_INPUT_ONLY;
887 attributes_mask = GDK_WA_X | GDK_WA_Y;
888
889 ig->event_window = gdk_window_new(window, &attributes, attributes_mask);
890 gdk_window_set_user_data(ig->event_window, widget);
891 }
892
893 style = gtk_style_attach(gtk_widget_get_style(widget), window);
894 gtk_widget_set_style(widget, style);
895
896 if (visible_window)
897 gtk_style_set_background(style, window, GTK_STATE_NORMAL);
898 }
899
900 static void panel_icon_grid_unrealize(GtkWidget *widget)
901 {
902 PanelIconGrid *ig = PANEL_ICON_GRID(widget);
903
904 if (ig->event_window != NULL)
905 {
906 gdk_window_set_user_data(ig->event_window, NULL);
907 gdk_window_destroy(ig->event_window);
908 ig->event_window = NULL;
909 }
910
911 GTK_WIDGET_CLASS(panel_icon_grid_parent_class)->unrealize(widget);
912 }
913
914 static void panel_icon_grid_map(GtkWidget *widget)
915 {
916 PanelIconGrid *ig = PANEL_ICON_GRID(widget);
917
918 if (ig->event_window != NULL)
919 gdk_window_show(ig->event_window);
920 GTK_WIDGET_CLASS(panel_icon_grid_parent_class)->map(widget);
921 }
922
923 static void panel_icon_grid_unmap(GtkWidget *widget)
924 {
925 PanelIconGrid *ig = PANEL_ICON_GRID(widget);
926
927 if (ig->event_window != NULL)
928 gdk_window_hide(ig->event_window);
929 GTK_WIDGET_CLASS(panel_icon_grid_parent_class)->unmap(widget);
930 }
931
932 #if GTK_CHECK_VERSION(3, 0, 0)
933 static gboolean panel_icon_grid_draw(GtkWidget *widget, cairo_t *cr)
934 #else
935 static gboolean panel_icon_grid_expose(GtkWidget *widget, GdkEventExpose *event)
936 #endif
937 {
938 if (gtk_widget_is_drawable(widget))
939 {
940 PanelIconGrid *ig;
941
942 if (gtk_widget_get_has_window(widget) &&
943 !gtk_widget_get_app_paintable(widget))
944 #if GTK_CHECK_VERSION(3, 0, 0)
945 gtk_render_background(gtk_widget_get_style_context(widget), cr, 0, 0,
946 gtk_widget_get_allocated_width(widget),
947 gtk_widget_get_allocated_height(widget));
948 #else
949 gtk_paint_flat_box(gtk_widget_get_style(widget),
950 gtk_widget_get_window(widget),
951 gtk_widget_get_state(widget), GTK_SHADOW_NONE,
952 &event->area, widget, "panelicongrid",
953 0, 0, -1, -1);
954 #endif
955
956 ig = PANEL_ICON_GRID(widget);
957 if (ig->dest_item && gtk_widget_get_has_window(widget))
958 {
959 GtkAllocation allocation;
960 GdkRectangle rect;
961 #if GTK_CHECK_VERSION(3, 0, 0)
962 GtkStyleContext *context;
963 #endif
964
965 gtk_widget_get_allocation(ig->dest_item, &allocation);
966 #if GTK_CHECK_VERSION(3, 0, 0)
967 cairo_save(cr);
968 //gtk_cairo_transform_to_window(cr, widget, gtk_widget_get_window(widget));
969 #endif
970 switch(ig->dest_pos)
971 {
972 case PANEL_ICON_GRID_DROP_LEFT_AFTER:
973 case PANEL_ICON_GRID_DROP_LEFT_BEFORE:
974 rect.x = allocation.x - 2;
975 rect.width = 2;
976 rect.y = allocation.y;
977 rect.height = allocation.height;
978 break;
979 case PANEL_ICON_GRID_DROP_RIGHT_AFTER:
980 case PANEL_ICON_GRID_DROP_RIGHT_BEFORE:
981 rect.x = allocation.x + allocation.width;
982 rect.width = 2;
983 rect.y = allocation.y;
984 rect.height = allocation.height;
985 break;
986 case PANEL_ICON_GRID_DROP_BELOW:
987 rect.x = allocation.x;
988 rect.width = allocation.width;
989 rect.y = allocation.y + allocation.height;
990 rect.height = 2;
991 break;
992 case PANEL_ICON_GRID_DROP_ABOVE:
993 rect.x = allocation.x;
994 rect.width = allocation.width;
995 rect.y = allocation.y - 2;
996 rect.height = 2;
997 break;
998 case PANEL_ICON_GRID_DROP_INTO:
999 default:
1000 rect.x = allocation.x - 1;
1001 rect.width = allocation.width + 2;
1002 rect.y = allocation.y - 1;
1003 rect.height = allocation.height + 2;
1004 }
1005 #if GTK_CHECK_VERSION(3, 0, 0)
1006 context = gtk_widget_get_style_context(widget);
1007 gtk_style_context_set_state(context, gtk_widget_get_state_flags(widget));
1008 gtk_render_focus(context, cr, rect.x, rect.y, rect.width, rect.height);
1009 cairo_restore(cr);
1010 #else
1011 gtk_paint_focus(gtk_widget_get_style(widget),
1012 gtk_widget_get_window(widget),
1013 gtk_widget_get_state(widget),
1014 NULL, widget,
1015 "panelicongrid-drop-indicator",
1016 rect.x, rect.y, rect.width, rect.height);
1017 #endif
1018 }
1019
1020 #if GTK_CHECK_VERSION(3, 0, 0)
1021 GTK_WIDGET_CLASS(panel_icon_grid_parent_class)->draw(widget, cr);
1022 #else
1023 GTK_WIDGET_CLASS(panel_icon_grid_parent_class)->expose_event(widget, event);
1024 #endif
1025 }
1026 return FALSE;
1027 }
1028
1029 static void panel_icon_grid_forall(GtkContainer *container,
1030 gboolean include_internals,
1031 GtkCallback callback,
1032 gpointer callback_data)
1033 {
1034 PanelIconGrid *ig = PANEL_ICON_GRID(container);
1035 GList *children = ig->children;
1036 GtkWidget *child;
1037
1038 while (children)
1039 {
1040 child = children->data;
1041 children = children->next;
1042 (* callback)(child, callback_data);
1043 }
1044 }
1045
1046 static GType panel_icon_grid_child_type(GtkContainer *container)
1047 {
1048 return GTK_TYPE_WIDGET;
1049 }
1050
1051 static void panel_icon_grid_set_child_property(GtkContainer *container,
1052 GtkWidget *child,
1053 guint prop_id,
1054 const GValue *value,
1055 GParamSpec *pspec)
1056 {
1057 PanelIconGrid *ig = PANEL_ICON_GRID(container);
1058
1059 switch (prop_id)
1060 {
1061 case CHILD_PROP_POSITION:
1062 panel_icon_grid_reorder_child(ig, child, g_value_get_int(value));
1063 break;
1064 default:
1065 G_OBJECT_WARN_INVALID_PROPERTY_ID(container, prop_id, pspec);
1066 break;
1067 }
1068 }
1069
1070 static void panel_icon_grid_get_child_property(GtkContainer *container,
1071 GtkWidget *child,
1072 guint prop_id,
1073 GValue *value,
1074 GParamSpec *pspec)
1075 {
1076 PanelIconGrid *ig = PANEL_ICON_GRID(container);
1077
1078 switch (prop_id)
1079 {
1080 case CHILD_PROP_POSITION:
1081 g_value_set_int(value, panel_icon_grid_get_child_position(ig, child));
1082 break;
1083 default:
1084 G_OBJECT_WARN_INVALID_PROPERTY_ID(container, prop_id, pspec);
1085 break;
1086 }
1087 }
1088
1089 static void panel_icon_grid_class_init(PanelIconGridClass *klass)
1090 {
1091 GObjectClass *object_class = G_OBJECT_CLASS(klass);
1092 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1093 GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
1094
1095 object_class->set_property = panel_icon_grid_set_property;
1096 object_class->get_property = panel_icon_grid_get_property;
1097
1098 #if GTK_CHECK_VERSION(3, 0, 0)
1099 widget_class->get_preferred_width = panel_icon_grid_get_preferred_width;
1100 widget_class->get_preferred_height = panel_icon_grid_get_preferred_height;
1101 #else
1102 widget_class->size_request = panel_icon_grid_size_request;
1103 #endif
1104 widget_class->size_allocate = panel_icon_grid_size_allocate;
1105 widget_class->realize = panel_icon_grid_realize;
1106 widget_class->unrealize = panel_icon_grid_unrealize;
1107 widget_class->map = panel_icon_grid_map;
1108 widget_class->unmap = panel_icon_grid_unmap;
1109 #if GTK_CHECK_VERSION(3, 0, 0)
1110 widget_class->draw = panel_icon_grid_draw;
1111 #else
1112 widget_class->expose_event = panel_icon_grid_expose;
1113 #endif
1114
1115 container_class->add = panel_icon_grid_add;
1116 container_class->remove = panel_icon_grid_remove;
1117 container_class->forall = panel_icon_grid_forall;
1118 container_class->child_type = panel_icon_grid_child_type;
1119 container_class->get_child_property = panel_icon_grid_get_child_property;
1120 container_class->set_child_property = panel_icon_grid_set_child_property;
1121
1122 g_object_class_override_property(object_class,
1123 PROP_ORIENTATION,
1124 "orientation");
1125 //FIXME: override border width to min = 1
1126 g_object_class_install_property(object_class,
1127 PROP_SPACING,
1128 g_param_spec_uint("spacing",
1129 "Spacing",
1130 "The amount of space between children",
1131 1,
1132 G_MAXINT,
1133 1,
1134 G_PARAM_READWRITE));
1135 g_object_class_install_property(object_class,
1136 PROP_CONSTRAIN_WIDTH,
1137 g_param_spec_boolean("constrain-width",
1138 "Constrain width",
1139 "Whether to constrain width by allocated space",
1140 FALSE, G_PARAM_READWRITE));
1141 g_object_class_install_property(object_class,
1142 PROP_ASPECT_WIDTH,
1143 g_param_spec_boolean("aspect-width",
1144 "Maintain children aspect",
1145 "Whether to set children width to maintain their aspect",
1146 FALSE, G_PARAM_READWRITE));
1147
1148 gtk_container_class_install_child_property(container_class,
1149 CHILD_PROP_POSITION,
1150 g_param_spec_int("position",
1151 "Position",
1152 "The index of the child in the parent",
1153 -1, G_MAXINT, 0,
1154 G_PARAM_READWRITE));
1155 }
1156
1157 static void panel_icon_grid_init(PanelIconGrid *ig)
1158 {
1159 gtk_widget_set_redraw_on_allocate(GTK_WIDGET(ig), FALSE);
1160
1161 ig->orientation = GTK_ORIENTATION_HORIZONTAL;
1162 }
1163
1164 /* Establish an icon grid in a specified container widget.
1165 * The icon grid manages the contents of the container.
1166 * The orientation, geometry of the elements, and spacing can be varied. All elements are the same size. */
1167 GtkWidget * panel_icon_grid_new(
1168 GtkOrientation orientation, gint child_width, gint child_height, gint spacing, gint border, gint target_dimension)
1169 {
1170 /* Create a structure representing the icon grid and collect the parameters. */
1171 PanelIconGrid * ig = g_object_new(PANEL_TYPE_ICON_GRID,
1172 "orientation", orientation,
1173 "spacing", MAX(spacing, 1),
1174 "border-width", border,
1175 NULL);
1176
1177 ig->child_width = child_width;
1178 ig->child_height = child_height;
1179 ig->target_dimension = MAX(target_dimension, 0);
1180
1181 return (GtkWidget *)ig;
1182 }