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