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