1 | /* gtkiconview.c |
2 | * Copyright (C) 2002, 2004 Anders Carlsson <andersca@gnu.org> |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Library General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Library General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Library General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | */ |
17 | |
18 | #include "config.h" |
19 | |
20 | #include "gtkiconviewprivate.h" |
21 | |
22 | #include "gtkadjustmentprivate.h" |
23 | #include "gtkcellareabox.h" |
24 | #include "gtkcellareacontext.h" |
25 | #include "gtkcelllayout.h" |
26 | #include "gtkcellrenderer.h" |
27 | #include "gtkcellrendererpixbuf.h" |
28 | #include "gtkcellrenderertext.h" |
29 | #include "gtkdragsourceprivate.h" |
30 | #include "gtkentry.h" |
31 | #include "gtkintl.h" |
32 | #include "gtkmain.h" |
33 | #include "gtkmarshalers.h" |
34 | #include "gtkorientable.h" |
35 | #include "gtkprivate.h" |
36 | #include "gtkscrollable.h" |
37 | #include "gtksizerequest.h" |
38 | #include "gtksnapshot.h" |
39 | #include "gtkstylecontextprivate.h" |
40 | #include "gtktreednd.h" |
41 | #include "gtktypebuiltins.h" |
42 | #include "gtkwidgetprivate.h" |
43 | #include "gtkwindow.h" |
44 | #include "gtkeventcontrollerkey.h" |
45 | #include "gtkdragsource.h" |
46 | #include "gtkdragicon.h" |
47 | #include "gtknative.h" |
48 | |
49 | #include <string.h> |
50 | |
51 | /** |
52 | * GtkIconView: |
53 | * |
54 | * `GtkIconView` is a widget which displays data in a grid of icons. |
55 | * |
56 | * `GtkIconView` provides an alternative view on a `GtkTreeModel`. |
57 | * It displays the model as a grid of icons with labels. Like |
58 | * [class@Gtk.TreeView], it allows to select one or multiple items |
59 | * (depending on the selection mode, see [method@Gtk.IconView.set_selection_mode]). |
60 | * In addition to selection with the arrow keys, `GtkIconView` supports |
61 | * rubberband selection, which is controlled by dragging the pointer. |
62 | * |
63 | * Note that if the tree model is backed by an actual tree store (as |
64 | * opposed to a flat list where the mapping to icons is obvious), |
65 | * `GtkIconView` will only display the first level of the tree and |
66 | * ignore the tree’s branches. |
67 | * |
68 | * # CSS nodes |
69 | * |
70 | * ``` |
71 | * iconview.view |
72 | * ╰── [rubberband] |
73 | * ``` |
74 | * |
75 | * `GtkIconView` has a single CSS node with name iconview and style class .view. |
76 | * For rubberband selection, a subnode with name rubberband is used. |
77 | */ |
78 | |
79 | #define SCROLL_EDGE_SIZE 15 |
80 | |
81 | typedef struct _GtkIconViewChild GtkIconViewChild; |
82 | struct _GtkIconViewChild |
83 | { |
84 | GtkWidget *widget; |
85 | GdkRectangle area; |
86 | }; |
87 | |
88 | /* Signals */ |
89 | enum |
90 | { |
91 | ITEM_ACTIVATED, |
92 | SELECTION_CHANGED, |
93 | SELECT_ALL, |
94 | UNSELECT_ALL, |
95 | SELECT_CURSOR_ITEM, |
96 | TOGGLE_CURSOR_ITEM, |
97 | MOVE_CURSOR, |
98 | ACTIVATE_CURSOR_ITEM, |
99 | LAST_SIGNAL |
100 | }; |
101 | |
102 | /* Properties */ |
103 | enum |
104 | { |
105 | PROP_0, |
106 | PROP_PIXBUF_COLUMN, |
107 | PROP_TEXT_COLUMN, |
108 | PROP_MARKUP_COLUMN, |
109 | PROP_SELECTION_MODE, |
110 | PROP_ITEM_ORIENTATION, |
111 | PROP_MODEL, |
112 | PROP_COLUMNS, |
113 | PROP_ITEM_WIDTH, |
114 | PROP_SPACING, |
115 | PROP_ROW_SPACING, |
116 | PROP_COLUMN_SPACING, |
117 | PROP_MARGIN, |
118 | PROP_REORDERABLE, |
119 | PROP_TOOLTIP_COLUMN, |
120 | PROP_ITEM_PADDING, |
121 | PROP_CELL_AREA, |
122 | PROP_HADJUSTMENT, |
123 | PROP_VADJUSTMENT, |
124 | PROP_HSCROLL_POLICY, |
125 | PROP_VSCROLL_POLICY, |
126 | PROP_ACTIVATE_ON_SINGLE_CLICK |
127 | }; |
128 | |
129 | /* GObject vfuncs */ |
130 | static void gtk_icon_view_cell_layout_init (GtkCellLayoutIface *iface); |
131 | static void gtk_icon_view_dispose (GObject *object); |
132 | static void gtk_icon_view_constructed (GObject *object); |
133 | static void gtk_icon_view_set_property (GObject *object, |
134 | guint prop_id, |
135 | const GValue *value, |
136 | GParamSpec *pspec); |
137 | static void gtk_icon_view_get_property (GObject *object, |
138 | guint prop_id, |
139 | GValue *value, |
140 | GParamSpec *pspec); |
141 | /* GtkWidget vfuncs */ |
142 | static GtkSizeRequestMode gtk_icon_view_get_request_mode (GtkWidget *widget); |
143 | static void gtk_icon_view_measure (GtkWidget *widget, |
144 | GtkOrientation orientation, |
145 | int for_size, |
146 | int *minimum, |
147 | int *natural, |
148 | int *minimum_baseline, |
149 | int *natural_baseline); |
150 | static void gtk_icon_view_size_allocate (GtkWidget *widget, |
151 | int width, |
152 | int height, |
153 | int baseline); |
154 | static void gtk_icon_view_snapshot (GtkWidget *widget, |
155 | GtkSnapshot *snapshot); |
156 | static void gtk_icon_view_motion (GtkEventController *controller, |
157 | double x, |
158 | double y, |
159 | gpointer user_data); |
160 | static void gtk_icon_view_leave (GtkEventController *controller, |
161 | gpointer user_data); |
162 | static void gtk_icon_view_button_press (GtkGestureClick *gesture, |
163 | int n_press, |
164 | double x, |
165 | double y, |
166 | gpointer user_data); |
167 | static void gtk_icon_view_button_release (GtkGestureClick *gesture, |
168 | int n_press, |
169 | double x, |
170 | double y, |
171 | gpointer user_data); |
172 | static gboolean gtk_icon_view_key_pressed (GtkEventControllerKey *controller, |
173 | guint keyval, |
174 | guint keycode, |
175 | GdkModifierType state, |
176 | GtkWidget *widget); |
177 | |
178 | static void gtk_icon_view_remove (GtkIconView *icon_view, |
179 | GtkWidget *widget); |
180 | |
181 | /* GtkIconView vfuncs */ |
182 | static void gtk_icon_view_real_select_all (GtkIconView *icon_view); |
183 | static void gtk_icon_view_real_unselect_all (GtkIconView *icon_view); |
184 | static void gtk_icon_view_real_select_cursor_item (GtkIconView *icon_view); |
185 | static void gtk_icon_view_real_toggle_cursor_item (GtkIconView *icon_view); |
186 | static gboolean gtk_icon_view_real_activate_cursor_item (GtkIconView *icon_view); |
187 | |
188 | /* Internal functions */ |
189 | static void gtk_icon_view_set_hadjustment_values (GtkIconView *icon_view); |
190 | static void gtk_icon_view_set_vadjustment_values (GtkIconView *icon_view); |
191 | static void gtk_icon_view_set_hadjustment (GtkIconView *icon_view, |
192 | GtkAdjustment *adjustment); |
193 | static void gtk_icon_view_set_vadjustment (GtkIconView *icon_view, |
194 | GtkAdjustment *adjustment); |
195 | static void gtk_icon_view_adjustment_changed (GtkAdjustment *adjustment, |
196 | GtkIconView *icon_view); |
197 | static void gtk_icon_view_layout (GtkIconView *icon_view); |
198 | static void gtk_icon_view_snapshot_item (GtkIconView *icon_view, |
199 | GtkSnapshot *snapshot, |
200 | GtkIconViewItem *item, |
201 | int x, |
202 | int y, |
203 | gboolean draw_focus); |
204 | static void gtk_icon_view_snapshot_rubberband (GtkIconView *icon_view, |
205 | GtkSnapshot *snapshot); |
206 | static void gtk_icon_view_queue_draw_path (GtkIconView *icon_view, |
207 | GtkTreePath *path); |
208 | static void gtk_icon_view_queue_draw_item (GtkIconView *icon_view, |
209 | GtkIconViewItem *item); |
210 | static void gtk_icon_view_start_rubberbanding (GtkIconView *icon_view, |
211 | GdkDevice *device, |
212 | int x, |
213 | int y); |
214 | static void gtk_icon_view_stop_rubberbanding (GtkIconView *icon_view); |
215 | static void gtk_icon_view_update_rubberband_selection (GtkIconView *icon_view); |
216 | static gboolean gtk_icon_view_item_hit_test (GtkIconView *icon_view, |
217 | GtkIconViewItem *item, |
218 | int x, |
219 | int y, |
220 | int width, |
221 | int height); |
222 | static gboolean gtk_icon_view_unselect_all_internal (GtkIconView *icon_view); |
223 | static void gtk_icon_view_update_rubberband (GtkIconView *icon_view); |
224 | static void gtk_icon_view_item_invalidate_size (GtkIconViewItem *item); |
225 | static void gtk_icon_view_invalidate_sizes (GtkIconView *icon_view); |
226 | static void gtk_icon_view_add_move_binding (GtkWidgetClass *widget_class, |
227 | guint keyval, |
228 | guint modmask, |
229 | GtkMovementStep step, |
230 | int count); |
231 | static gboolean gtk_icon_view_real_move_cursor (GtkIconView *icon_view, |
232 | GtkMovementStep step, |
233 | int count, |
234 | gboolean extend, |
235 | gboolean modify); |
236 | static void gtk_icon_view_move_cursor_up_down (GtkIconView *icon_view, |
237 | int count); |
238 | static void gtk_icon_view_move_cursor_page_up_down (GtkIconView *icon_view, |
239 | int count); |
240 | static void gtk_icon_view_move_cursor_left_right (GtkIconView *icon_view, |
241 | int count); |
242 | static void gtk_icon_view_move_cursor_start_end (GtkIconView *icon_view, |
243 | int count); |
244 | static void gtk_icon_view_scroll_to_item (GtkIconView *icon_view, |
245 | GtkIconViewItem *item); |
246 | static gboolean gtk_icon_view_select_all_between (GtkIconView *icon_view, |
247 | GtkIconViewItem *anchor, |
248 | GtkIconViewItem *cursor); |
249 | |
250 | static void gtk_icon_view_ensure_cell_area (GtkIconView *icon_view, |
251 | GtkCellArea *cell_area); |
252 | |
253 | static GtkCellArea *gtk_icon_view_cell_layout_get_area (GtkCellLayout *layout); |
254 | |
255 | static void gtk_icon_view_add_editable (GtkCellArea *area, |
256 | GtkCellRenderer *renderer, |
257 | GtkCellEditable *editable, |
258 | GdkRectangle *cell_area, |
259 | const char *path, |
260 | GtkIconView *icon_view); |
261 | static void gtk_icon_view_remove_editable (GtkCellArea *area, |
262 | GtkCellRenderer *renderer, |
263 | GtkCellEditable *editable, |
264 | GtkIconView *icon_view); |
265 | static void update_text_cell (GtkIconView *icon_view); |
266 | static void update_pixbuf_cell (GtkIconView *icon_view); |
267 | |
268 | /* Source side drag signals */ |
269 | static void gtk_icon_view_dnd_finished_cb (GdkDrag *drag, |
270 | GtkWidget *widget); |
271 | static GdkContentProvider * gtk_icon_view_drag_data_get (GtkIconView *icon_view, |
272 | GtkTreePath *source_row); |
273 | |
274 | /* Target side drag signals */ |
275 | static void gtk_icon_view_drag_leave (GtkDropTargetAsync *dest, |
276 | GdkDrop *drop, |
277 | GtkIconView *icon_view); |
278 | static GdkDragAction gtk_icon_view_drag_motion (GtkDropTargetAsync *dest, |
279 | GdkDrop *drop, |
280 | double x, |
281 | double y, |
282 | GtkIconView *icon_view); |
283 | static gboolean gtk_icon_view_drag_drop (GtkDropTargetAsync *dest, |
284 | GdkDrop *drop, |
285 | double x, |
286 | double y, |
287 | GtkIconView *icon_view); |
288 | static void gtk_icon_view_drag_data_received (GObject *source, |
289 | GAsyncResult *result, |
290 | gpointer data); |
291 | static gboolean gtk_icon_view_maybe_begin_drag (GtkIconView *icon_view, |
292 | double x, |
293 | double y, |
294 | GdkDevice *device); |
295 | |
296 | static void remove_scroll_timeout (GtkIconView *icon_view); |
297 | |
298 | /* GtkBuildable */ |
299 | static GtkBuildableIface *parent_buildable_iface; |
300 | static void gtk_icon_view_buildable_init (GtkBuildableIface *iface); |
301 | static gboolean gtk_icon_view_buildable_custom_tag_start (GtkBuildable *buildable, |
302 | GtkBuilder *builder, |
303 | GObject *child, |
304 | const char *tagname, |
305 | GtkBuildableParser *parser, |
306 | gpointer *data); |
307 | static void gtk_icon_view_buildable_custom_tag_end (GtkBuildable *buildable, |
308 | GtkBuilder *builder, |
309 | GObject *child, |
310 | const char *tagname, |
311 | gpointer data); |
312 | |
313 | |
314 | static guint icon_view_signals[LAST_SIGNAL] = { 0 }; |
315 | |
316 | G_DEFINE_TYPE_WITH_CODE (GtkIconView, gtk_icon_view, GTK_TYPE_WIDGET, |
317 | G_ADD_PRIVATE (GtkIconView) |
318 | G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT, |
319 | gtk_icon_view_cell_layout_init) |
320 | G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, |
321 | gtk_icon_view_buildable_init) |
322 | G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL)) |
323 | |
324 | static void |
325 | gtk_icon_view_class_init (GtkIconViewClass *klass) |
326 | { |
327 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
328 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
329 | |
330 | gobject_class->constructed = gtk_icon_view_constructed; |
331 | gobject_class->dispose = gtk_icon_view_dispose; |
332 | gobject_class->set_property = gtk_icon_view_set_property; |
333 | gobject_class->get_property = gtk_icon_view_get_property; |
334 | |
335 | widget_class->get_request_mode = gtk_icon_view_get_request_mode; |
336 | widget_class->measure = gtk_icon_view_measure; |
337 | widget_class->size_allocate = gtk_icon_view_size_allocate; |
338 | widget_class->snapshot = gtk_icon_view_snapshot; |
339 | widget_class->focus = gtk_widget_focus_self; |
340 | widget_class->grab_focus = gtk_widget_grab_focus_self; |
341 | |
342 | klass->select_all = gtk_icon_view_real_select_all; |
343 | klass->unselect_all = gtk_icon_view_real_unselect_all; |
344 | klass->select_cursor_item = gtk_icon_view_real_select_cursor_item; |
345 | klass->toggle_cursor_item = gtk_icon_view_real_toggle_cursor_item; |
346 | klass->activate_cursor_item = gtk_icon_view_real_activate_cursor_item; |
347 | klass->move_cursor = gtk_icon_view_real_move_cursor; |
348 | |
349 | /* Properties */ |
350 | /** |
351 | * GtkIconView:selection-mode: |
352 | * |
353 | * The ::selection-mode property specifies the selection mode of |
354 | * icon view. If the mode is %GTK_SELECTION_MULTIPLE, rubberband selection |
355 | * is enabled, for the other modes, only keyboard selection is possible. |
356 | */ |
357 | g_object_class_install_property (oclass: gobject_class, |
358 | property_id: PROP_SELECTION_MODE, |
359 | pspec: g_param_spec_enum (name: "selection-mode" , |
360 | P_("Selection mode" ), |
361 | P_("The selection mode" ), |
362 | enum_type: GTK_TYPE_SELECTION_MODE, |
363 | default_value: GTK_SELECTION_SINGLE, |
364 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
365 | |
366 | /** |
367 | * GtkIconView:pixbuf-column: |
368 | * |
369 | * The ::pixbuf-column property contains the number of the model column |
370 | * containing the pixbufs which are displayed. The pixbuf column must be |
371 | * of type `GDK_TYPE_PIXBUF`. Setting this property to -1 turns off the |
372 | * display of pixbufs. |
373 | */ |
374 | g_object_class_install_property (oclass: gobject_class, |
375 | property_id: PROP_PIXBUF_COLUMN, |
376 | pspec: g_param_spec_int (name: "pixbuf-column" , |
377 | P_("Pixbuf column" ), |
378 | P_("Model column used to retrieve the icon pixbuf from" ), |
379 | minimum: -1, G_MAXINT, default_value: -1, |
380 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
381 | |
382 | /** |
383 | * GtkIconView:text-column: |
384 | * |
385 | * The ::text-column property contains the number of the model column |
386 | * containing the texts which are displayed. The text column must be |
387 | * of type `G_TYPE_STRING`. If this property and the :markup-column |
388 | * property are both set to -1, no texts are displayed. |
389 | */ |
390 | g_object_class_install_property (oclass: gobject_class, |
391 | property_id: PROP_TEXT_COLUMN, |
392 | pspec: g_param_spec_int (name: "text-column" , |
393 | P_("Text column" ), |
394 | P_("Model column used to retrieve the text from" ), |
395 | minimum: -1, G_MAXINT, default_value: -1, |
396 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
397 | |
398 | |
399 | /** |
400 | * GtkIconView:markup-column: |
401 | * |
402 | * The ::markup-column property contains the number of the model column |
403 | * containing markup information to be displayed. The markup column must be |
404 | * of type `G_TYPE_STRING`. If this property and the :text-column property |
405 | * are both set to column numbers, it overrides the text column. |
406 | * If both are set to -1, no texts are displayed. |
407 | */ |
408 | g_object_class_install_property (oclass: gobject_class, |
409 | property_id: PROP_MARKUP_COLUMN, |
410 | pspec: g_param_spec_int (name: "markup-column" , |
411 | P_("Markup column" ), |
412 | P_("Model column used to retrieve the text if using Pango markup" ), |
413 | minimum: -1, G_MAXINT, default_value: -1, |
414 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
415 | |
416 | g_object_class_install_property (oclass: gobject_class, |
417 | property_id: PROP_MODEL, |
418 | pspec: g_param_spec_object (name: "model" , |
419 | P_("Icon View Model" ), |
420 | P_("The model for the icon view" ), |
421 | GTK_TYPE_TREE_MODEL, |
422 | GTK_PARAM_READWRITE)); |
423 | |
424 | /** |
425 | * GtkIconView:columns: |
426 | * |
427 | * The columns property contains the number of the columns in which the |
428 | * items should be displayed. If it is -1, the number of columns will |
429 | * be chosen automatically to fill the available area. |
430 | */ |
431 | g_object_class_install_property (oclass: gobject_class, |
432 | property_id: PROP_COLUMNS, |
433 | pspec: g_param_spec_int (name: "columns" , |
434 | P_("Number of columns" ), |
435 | P_("Number of columns to display" ), |
436 | minimum: -1, G_MAXINT, default_value: -1, |
437 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
438 | |
439 | |
440 | /** |
441 | * GtkIconView:item-width: |
442 | * |
443 | * The item-width property specifies the width to use for each item. |
444 | * If it is set to -1, the icon view will automatically determine a |
445 | * suitable item size. |
446 | */ |
447 | g_object_class_install_property (oclass: gobject_class, |
448 | property_id: PROP_ITEM_WIDTH, |
449 | pspec: g_param_spec_int (name: "item-width" , |
450 | P_("Width for each item" ), |
451 | P_("The width used for each item" ), |
452 | minimum: -1, G_MAXINT, default_value: -1, |
453 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
454 | |
455 | /** |
456 | * GtkIconView:spacing: |
457 | * |
458 | * The spacing property specifies the space which is inserted between |
459 | * the cells (i.e. the icon and the text) of an item. |
460 | */ |
461 | g_object_class_install_property (oclass: gobject_class, |
462 | property_id: PROP_SPACING, |
463 | pspec: g_param_spec_int (name: "spacing" , |
464 | P_("Spacing" ), |
465 | P_("Space which is inserted between cells of an item" ), |
466 | minimum: 0, G_MAXINT, default_value: 0, |
467 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
468 | |
469 | /** |
470 | * GtkIconView:row-spacing: |
471 | * |
472 | * The row-spacing property specifies the space which is inserted between |
473 | * the rows of the icon view. |
474 | */ |
475 | g_object_class_install_property (oclass: gobject_class, |
476 | property_id: PROP_ROW_SPACING, |
477 | pspec: g_param_spec_int (name: "row-spacing" , |
478 | P_("Row Spacing" ), |
479 | P_("Space which is inserted between grid rows" ), |
480 | minimum: 0, G_MAXINT, default_value: 6, |
481 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
482 | |
483 | /** |
484 | * GtkIconView:column-spacing: |
485 | * |
486 | * The column-spacing property specifies the space which is inserted between |
487 | * the columns of the icon view. |
488 | */ |
489 | g_object_class_install_property (oclass: gobject_class, |
490 | property_id: PROP_COLUMN_SPACING, |
491 | pspec: g_param_spec_int (name: "column-spacing" , |
492 | P_("Column Spacing" ), |
493 | P_("Space which is inserted between grid columns" ), |
494 | minimum: 0, G_MAXINT, default_value: 6, |
495 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
496 | |
497 | /** |
498 | * GtkIconView:margin: |
499 | * |
500 | * The margin property specifies the space which is inserted |
501 | * at the edges of the icon view. |
502 | */ |
503 | g_object_class_install_property (oclass: gobject_class, |
504 | property_id: PROP_MARGIN, |
505 | pspec: g_param_spec_int (name: "margin" , |
506 | P_("Margin" ), |
507 | P_("Space which is inserted at the edges of the icon view" ), |
508 | minimum: 0, G_MAXINT, default_value: 6, |
509 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
510 | |
511 | /** |
512 | * GtkIconView:item-orientation: |
513 | * |
514 | * The item-orientation property specifies how the cells (i.e. the icon and |
515 | * the text) of the item are positioned relative to each other. |
516 | */ |
517 | g_object_class_install_property (oclass: gobject_class, |
518 | property_id: PROP_ITEM_ORIENTATION, |
519 | pspec: g_param_spec_enum (name: "item-orientation" , |
520 | P_("Item Orientation" ), |
521 | P_("How the text and icon of each item are positioned relative to each other" ), |
522 | enum_type: GTK_TYPE_ORIENTATION, |
523 | default_value: GTK_ORIENTATION_VERTICAL, |
524 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
525 | |
526 | /** |
527 | * GtkIconView:reorderable: |
528 | * |
529 | * The reorderable property specifies if the items can be reordered |
530 | * by DND. |
531 | */ |
532 | g_object_class_install_property (oclass: gobject_class, |
533 | property_id: PROP_REORDERABLE, |
534 | pspec: g_param_spec_boolean (name: "reorderable" , |
535 | P_("Reorderable" ), |
536 | P_("View is reorderable" ), |
537 | FALSE, |
538 | flags: G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
539 | |
540 | g_object_class_install_property (oclass: gobject_class, |
541 | property_id: PROP_TOOLTIP_COLUMN, |
542 | pspec: g_param_spec_int (name: "tooltip-column" , |
543 | P_("Tooltip Column" ), |
544 | P_("The column in the model containing the tooltip texts for the items" ), |
545 | minimum: -1, |
546 | G_MAXINT, |
547 | default_value: -1, |
548 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
549 | |
550 | /** |
551 | * GtkIconView:item-padding: |
552 | * |
553 | * The item-padding property specifies the padding around each |
554 | * of the icon view's item. |
555 | */ |
556 | g_object_class_install_property (oclass: gobject_class, |
557 | property_id: PROP_ITEM_PADDING, |
558 | pspec: g_param_spec_int (name: "item-padding" , |
559 | P_("Item Padding" ), |
560 | P_("Padding around icon view items" ), |
561 | minimum: 0, G_MAXINT, default_value: 6, |
562 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
563 | |
564 | /** |
565 | * GtkIconView:cell-area: |
566 | * |
567 | * The `GtkCellArea` used to layout cell renderers for this view. |
568 | * |
569 | * If no area is specified when creating the icon view with gtk_icon_view_new_with_area() |
570 | * a `GtkCellAreaBox` will be used. |
571 | */ |
572 | g_object_class_install_property (oclass: gobject_class, |
573 | property_id: PROP_CELL_AREA, |
574 | pspec: g_param_spec_object (name: "cell-area" , |
575 | P_("Cell Area" ), |
576 | P_("The GtkCellArea used to layout cells" ), |
577 | GTK_TYPE_CELL_AREA, |
578 | GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); |
579 | |
580 | /** |
581 | * GtkIconView:activate-on-single-click: |
582 | * |
583 | * The activate-on-single-click property specifies whether the "item-activated" signal |
584 | * will be emitted after a single click. |
585 | */ |
586 | g_object_class_install_property (oclass: gobject_class, |
587 | property_id: PROP_ACTIVATE_ON_SINGLE_CLICK, |
588 | pspec: g_param_spec_boolean (name: "activate-on-single-click" , |
589 | P_("Activate on Single Click" ), |
590 | P_("Activate row on a single click" ), |
591 | FALSE, |
592 | GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
593 | |
594 | /* Scrollable interface properties */ |
595 | g_object_class_override_property (oclass: gobject_class, property_id: PROP_HADJUSTMENT, name: "hadjustment" ); |
596 | g_object_class_override_property (oclass: gobject_class, property_id: PROP_VADJUSTMENT, name: "vadjustment" ); |
597 | g_object_class_override_property (oclass: gobject_class, property_id: PROP_HSCROLL_POLICY, name: "hscroll-policy" ); |
598 | g_object_class_override_property (oclass: gobject_class, property_id: PROP_VSCROLL_POLICY, name: "vscroll-policy" ); |
599 | |
600 | /* Signals */ |
601 | /** |
602 | * GtkIconView::item-activated: |
603 | * @iconview: the object on which the signal is emitted |
604 | * @path: the `GtkTreePath` for the activated item |
605 | * |
606 | * The ::item-activated signal is emitted when the method |
607 | * gtk_icon_view_item_activated() is called, when the user double |
608 | * clicks an item with the "activate-on-single-click" property set |
609 | * to %FALSE, or when the user single clicks an item when the |
610 | * "activate-on-single-click" property set to %TRUE. It is also |
611 | * emitted when a non-editable item is selected and one of the keys: |
612 | * Space, Return or Enter is pressed. |
613 | */ |
614 | icon_view_signals[ITEM_ACTIVATED] = |
615 | g_signal_new (I_("item-activated" ), |
616 | G_TYPE_FROM_CLASS (gobject_class), |
617 | signal_flags: G_SIGNAL_RUN_LAST, |
618 | G_STRUCT_OFFSET (GtkIconViewClass, item_activated), |
619 | NULL, NULL, |
620 | NULL, |
621 | G_TYPE_NONE, n_params: 1, |
622 | GTK_TYPE_TREE_PATH); |
623 | |
624 | /** |
625 | * GtkIconView::selection-changed: |
626 | * @iconview: the object on which the signal is emitted |
627 | * |
628 | * The ::selection-changed signal is emitted when the selection |
629 | * (i.e. the set of selected items) changes. |
630 | */ |
631 | icon_view_signals[SELECTION_CHANGED] = |
632 | g_signal_new (I_("selection-changed" ), |
633 | G_TYPE_FROM_CLASS (gobject_class), |
634 | signal_flags: G_SIGNAL_RUN_FIRST, |
635 | G_STRUCT_OFFSET (GtkIconViewClass, selection_changed), |
636 | NULL, NULL, |
637 | NULL, |
638 | G_TYPE_NONE, n_params: 0); |
639 | |
640 | /** |
641 | * GtkIconView::select-all: |
642 | * @iconview: the object on which the signal is emitted |
643 | * |
644 | * A [keybinding signal][class@Gtk.SignalAction] |
645 | * which gets emitted when the user selects all items. |
646 | * |
647 | * Applications should not connect to it, but may emit it with |
648 | * g_signal_emit_by_name() if they need to control selection |
649 | * programmatically. |
650 | * |
651 | * The default binding for this signal is Ctrl-a. |
652 | */ |
653 | icon_view_signals[SELECT_ALL] = |
654 | g_signal_new (I_("select-all" ), |
655 | G_TYPE_FROM_CLASS (gobject_class), |
656 | signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, |
657 | G_STRUCT_OFFSET (GtkIconViewClass, select_all), |
658 | NULL, NULL, |
659 | NULL, |
660 | G_TYPE_NONE, n_params: 0); |
661 | |
662 | /** |
663 | * GtkIconView::unselect-all: |
664 | * @iconview: the object on which the signal is emitted |
665 | * |
666 | * A [keybinding signal][class@Gtk.SignalAction] |
667 | * which gets emitted when the user unselects all items. |
668 | * |
669 | * Applications should not connect to it, but may emit it with |
670 | * g_signal_emit_by_name() if they need to control selection |
671 | * programmatically. |
672 | * |
673 | * The default binding for this signal is Ctrl-Shift-a. |
674 | */ |
675 | icon_view_signals[UNSELECT_ALL] = |
676 | g_signal_new (I_("unselect-all" ), |
677 | G_TYPE_FROM_CLASS (gobject_class), |
678 | signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, |
679 | G_STRUCT_OFFSET (GtkIconViewClass, unselect_all), |
680 | NULL, NULL, |
681 | NULL, |
682 | G_TYPE_NONE, n_params: 0); |
683 | |
684 | /** |
685 | * GtkIconView::select-cursor-item: |
686 | * @iconview: the object on which the signal is emitted |
687 | * |
688 | * A [keybinding signal][class@Gtk.SignalAction] |
689 | * which gets emitted when the user selects the item that is currently |
690 | * focused. |
691 | * |
692 | * Applications should not connect to it, but may emit it with |
693 | * g_signal_emit_by_name() if they need to control selection |
694 | * programmatically. |
695 | * |
696 | * There is no default binding for this signal. |
697 | */ |
698 | icon_view_signals[SELECT_CURSOR_ITEM] = |
699 | g_signal_new (I_("select-cursor-item" ), |
700 | G_TYPE_FROM_CLASS (gobject_class), |
701 | signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, |
702 | G_STRUCT_OFFSET (GtkIconViewClass, select_cursor_item), |
703 | NULL, NULL, |
704 | NULL, |
705 | G_TYPE_NONE, n_params: 0); |
706 | |
707 | /** |
708 | * GtkIconView::toggle-cursor-item: |
709 | * @iconview: the object on which the signal is emitted |
710 | * |
711 | * A [keybinding signal][class@Gtk.SignalAction] |
712 | * which gets emitted when the user toggles whether the currently |
713 | * focused item is selected or not. The exact effect of this |
714 | * depend on the selection mode. |
715 | * |
716 | * Applications should not connect to it, but may emit it with |
717 | * g_signal_emit_by_name() if they need to control selection |
718 | * programmatically. |
719 | * |
720 | * There is no default binding for this signal is Ctrl-Space. |
721 | */ |
722 | icon_view_signals[TOGGLE_CURSOR_ITEM] = |
723 | g_signal_new (I_("toggle-cursor-item" ), |
724 | G_TYPE_FROM_CLASS (gobject_class), |
725 | signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, |
726 | G_STRUCT_OFFSET (GtkIconViewClass, toggle_cursor_item), |
727 | NULL, NULL, |
728 | NULL, |
729 | G_TYPE_NONE, n_params: 0); |
730 | |
731 | /** |
732 | * GtkIconView::activate-cursor-item: |
733 | * @iconview: the object on which the signal is emitted |
734 | * |
735 | * A [keybinding signal][class@Gtk.SignalAction] |
736 | * which gets emitted when the user activates the currently |
737 | * focused item. |
738 | * |
739 | * Applications should not connect to it, but may emit it with |
740 | * g_signal_emit_by_name() if they need to control activation |
741 | * programmatically. |
742 | * |
743 | * The default bindings for this signal are Space, Return and Enter. |
744 | */ |
745 | icon_view_signals[ACTIVATE_CURSOR_ITEM] = |
746 | g_signal_new (I_("activate-cursor-item" ), |
747 | G_TYPE_FROM_CLASS (gobject_class), |
748 | signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, |
749 | G_STRUCT_OFFSET (GtkIconViewClass, activate_cursor_item), |
750 | NULL, NULL, |
751 | c_marshaller: _gtk_marshal_BOOLEAN__VOID, |
752 | G_TYPE_BOOLEAN, n_params: 0); |
753 | g_signal_set_va_marshaller (signal_id: icon_view_signals[ACTIVATE_CURSOR_ITEM], |
754 | G_TYPE_FROM_CLASS (klass), |
755 | va_marshaller: _gtk_marshal_BOOLEAN__VOIDv); |
756 | |
757 | /** |
758 | * GtkIconView::move-cursor: |
759 | * @iconview: the object which received the signal |
760 | * @step: the granularity of the move, as a `GtkMovementStep` |
761 | * @count: the number of @step units to move |
762 | * @extend: whether to extend the selection |
763 | * @modify: whether to modify the selection |
764 | * |
765 | * The ::move-cursor signal is a |
766 | * [keybinding signal][class@Gtk.SignalAction] |
767 | * which gets emitted when the user initiates a cursor movement. |
768 | * |
769 | * Applications should not connect to it, but may emit it with |
770 | * g_signal_emit_by_name() if they need to control the cursor |
771 | * programmatically. |
772 | * |
773 | * The default bindings for this signal include |
774 | * - Arrow keys which move by individual steps |
775 | * - Home/End keys which move to the first/last item |
776 | * - PageUp/PageDown which move by "pages" |
777 | * All of these will extend the selection when combined with |
778 | * the Shift modifier. |
779 | */ |
780 | icon_view_signals[MOVE_CURSOR] = |
781 | g_signal_new (I_("move-cursor" ), |
782 | G_TYPE_FROM_CLASS (gobject_class), |
783 | signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, |
784 | G_STRUCT_OFFSET (GtkIconViewClass, move_cursor), |
785 | NULL, NULL, |
786 | c_marshaller: _gtk_marshal_BOOLEAN__ENUM_INT_BOOLEAN_BOOLEAN, |
787 | G_TYPE_BOOLEAN, n_params: 4, |
788 | GTK_TYPE_MOVEMENT_STEP, |
789 | G_TYPE_INT, |
790 | G_TYPE_BOOLEAN, |
791 | G_TYPE_BOOLEAN); |
792 | g_signal_set_va_marshaller (signal_id: icon_view_signals[MOVE_CURSOR], |
793 | G_TYPE_FROM_CLASS (klass), |
794 | va_marshaller: _gtk_marshal_BOOLEAN__ENUM_INT_BOOLEAN_BOOLEANv); |
795 | |
796 | /* Key bindings */ |
797 | gtk_widget_class_add_binding_signal (widget_class, |
798 | GDK_KEY_a, mods: GDK_CONTROL_MASK, |
799 | signal: "select-all" , |
800 | NULL); |
801 | gtk_widget_class_add_binding_signal (widget_class, |
802 | GDK_KEY_a, mods: GDK_CONTROL_MASK | GDK_SHIFT_MASK, |
803 | signal: "unselect-all" , |
804 | NULL); |
805 | gtk_widget_class_add_binding_signal (widget_class, |
806 | GDK_KEY_space, mods: GDK_CONTROL_MASK, |
807 | signal: "toggle-cursor-item" , |
808 | NULL); |
809 | gtk_widget_class_add_binding_signal (widget_class, |
810 | GDK_KEY_KP_Space, mods: GDK_CONTROL_MASK, |
811 | signal: "toggle-cursor-item" , |
812 | NULL); |
813 | |
814 | gtk_widget_class_add_binding_signal (widget_class, |
815 | GDK_KEY_space, mods: 0, |
816 | signal: "activate-cursor-item" , |
817 | NULL); |
818 | gtk_widget_class_add_binding_signal (widget_class, |
819 | GDK_KEY_KP_Space, mods: 0, |
820 | signal: "activate-cursor-item" , |
821 | NULL); |
822 | gtk_widget_class_add_binding_signal (widget_class, |
823 | GDK_KEY_Return, mods: 0, |
824 | signal: "activate-cursor-item" , |
825 | NULL); |
826 | gtk_widget_class_add_binding_signal (widget_class, |
827 | GDK_KEY_ISO_Enter, mods: 0, |
828 | signal: "activate-cursor-item" , |
829 | NULL); |
830 | gtk_widget_class_add_binding_signal (widget_class, |
831 | GDK_KEY_KP_Enter, mods: 0, |
832 | signal: "activate-cursor-item" , |
833 | NULL); |
834 | |
835 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_Up, modmask: 0, |
836 | step: GTK_MOVEMENT_DISPLAY_LINES, count: -1); |
837 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_KP_Up, modmask: 0, |
838 | step: GTK_MOVEMENT_DISPLAY_LINES, count: -1); |
839 | |
840 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_Down, modmask: 0, |
841 | step: GTK_MOVEMENT_DISPLAY_LINES, count: 1); |
842 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_KP_Down, modmask: 0, |
843 | step: GTK_MOVEMENT_DISPLAY_LINES, count: 1); |
844 | |
845 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_p, modmask: GDK_CONTROL_MASK, |
846 | step: GTK_MOVEMENT_DISPLAY_LINES, count: -1); |
847 | |
848 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_n, modmask: GDK_CONTROL_MASK, |
849 | step: GTK_MOVEMENT_DISPLAY_LINES, count: 1); |
850 | |
851 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_Home, modmask: 0, |
852 | step: GTK_MOVEMENT_BUFFER_ENDS, count: -1); |
853 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_KP_Home, modmask: 0, |
854 | step: GTK_MOVEMENT_BUFFER_ENDS, count: -1); |
855 | |
856 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_End, modmask: 0, |
857 | step: GTK_MOVEMENT_BUFFER_ENDS, count: 1); |
858 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_KP_End, modmask: 0, |
859 | step: GTK_MOVEMENT_BUFFER_ENDS, count: 1); |
860 | |
861 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_Page_Up, modmask: 0, |
862 | step: GTK_MOVEMENT_PAGES, count: -1); |
863 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_KP_Page_Up, modmask: 0, |
864 | step: GTK_MOVEMENT_PAGES, count: -1); |
865 | |
866 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_Page_Down, modmask: 0, |
867 | step: GTK_MOVEMENT_PAGES, count: 1); |
868 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_KP_Page_Down, modmask: 0, |
869 | step: GTK_MOVEMENT_PAGES, count: 1); |
870 | |
871 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_Right, modmask: 0, |
872 | step: GTK_MOVEMENT_VISUAL_POSITIONS, count: 1); |
873 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_Left, modmask: 0, |
874 | step: GTK_MOVEMENT_VISUAL_POSITIONS, count: -1); |
875 | |
876 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_KP_Right, modmask: 0, |
877 | step: GTK_MOVEMENT_VISUAL_POSITIONS, count: 1); |
878 | gtk_icon_view_add_move_binding (widget_class, GDK_KEY_KP_Left, modmask: 0, |
879 | step: GTK_MOVEMENT_VISUAL_POSITIONS, count: -1); |
880 | |
881 | gtk_widget_class_set_css_name (widget_class, I_("iconview" )); |
882 | } |
883 | |
884 | static void |
885 | gtk_icon_view_buildable_add_child (GtkBuildable *buildable, |
886 | GtkBuilder *builder, |
887 | GObject *child, |
888 | const char *type) |
889 | { |
890 | if (GTK_IS_CELL_RENDERER (child)) |
891 | _gtk_cell_layout_buildable_add_child (buildable, builder, child, type); |
892 | else |
893 | parent_buildable_iface->add_child (buildable, builder, child, type); |
894 | } |
895 | |
896 | static void |
897 | gtk_icon_view_buildable_init (GtkBuildableIface *iface) |
898 | { |
899 | parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface); |
900 | iface->add_child = gtk_icon_view_buildable_add_child; |
901 | iface->custom_tag_start = gtk_icon_view_buildable_custom_tag_start; |
902 | iface->custom_tag_end = gtk_icon_view_buildable_custom_tag_end; |
903 | } |
904 | |
905 | static void |
906 | gtk_icon_view_cell_layout_init (GtkCellLayoutIface *iface) |
907 | { |
908 | iface->get_area = gtk_icon_view_cell_layout_get_area; |
909 | } |
910 | |
911 | static void |
912 | gtk_icon_view_init (GtkIconView *icon_view) |
913 | { |
914 | GtkEventController *controller; |
915 | GtkGesture *gesture; |
916 | |
917 | icon_view->priv = gtk_icon_view_get_instance_private (self: icon_view); |
918 | |
919 | icon_view->priv->width = 0; |
920 | icon_view->priv->height = 0; |
921 | icon_view->priv->selection_mode = GTK_SELECTION_SINGLE; |
922 | icon_view->priv->pressed_button = -1; |
923 | icon_view->priv->press_start_x = -1; |
924 | icon_view->priv->press_start_y = -1; |
925 | icon_view->priv->text_column = -1; |
926 | icon_view->priv->markup_column = -1; |
927 | icon_view->priv->pixbuf_column = -1; |
928 | icon_view->priv->text_cell = NULL; |
929 | icon_view->priv->pixbuf_cell = NULL; |
930 | icon_view->priv->tooltip_column = -1; |
931 | icon_view->priv->mouse_x = -1; |
932 | icon_view->priv->mouse_y = -1; |
933 | |
934 | gtk_widget_set_overflow (GTK_WIDGET (icon_view), overflow: GTK_OVERFLOW_HIDDEN); |
935 | gtk_widget_set_focusable (GTK_WIDGET (icon_view), TRUE); |
936 | |
937 | icon_view->priv->item_orientation = GTK_ORIENTATION_VERTICAL; |
938 | |
939 | icon_view->priv->columns = -1; |
940 | icon_view->priv->item_width = -1; |
941 | icon_view->priv->spacing = 0; |
942 | icon_view->priv->row_spacing = 6; |
943 | icon_view->priv->column_spacing = 6; |
944 | icon_view->priv->margin = 6; |
945 | icon_view->priv->item_padding = 6; |
946 | icon_view->priv->activate_on_single_click = FALSE; |
947 | |
948 | icon_view->priv->draw_focus = TRUE; |
949 | |
950 | icon_view->priv->row_contexts = |
951 | g_ptr_array_new_with_free_func (element_free_func: (GDestroyNotify)g_object_unref); |
952 | |
953 | gtk_widget_add_css_class (GTK_WIDGET (icon_view), css_class: "view" ); |
954 | |
955 | gesture = gtk_gesture_click_new (); |
956 | g_signal_connect (gesture, "pressed" , G_CALLBACK (gtk_icon_view_button_press), |
957 | icon_view); |
958 | g_signal_connect (gesture, "released" , G_CALLBACK (gtk_icon_view_button_release), |
959 | icon_view); |
960 | gtk_widget_add_controller (GTK_WIDGET (icon_view), GTK_EVENT_CONTROLLER (gesture)); |
961 | |
962 | controller = gtk_event_controller_motion_new (); |
963 | g_signal_connect (controller, "leave" , G_CALLBACK (gtk_icon_view_leave), icon_view); |
964 | g_signal_connect (controller, "motion" , G_CALLBACK (gtk_icon_view_motion), icon_view); |
965 | gtk_widget_add_controller (GTK_WIDGET (icon_view), controller); |
966 | |
967 | controller = gtk_event_controller_key_new (); |
968 | g_signal_connect (controller, "key-pressed" , G_CALLBACK (gtk_icon_view_key_pressed), |
969 | icon_view); |
970 | gtk_widget_add_controller (GTK_WIDGET (icon_view), controller); |
971 | } |
972 | |
973 | /* GObject methods */ |
974 | |
975 | static void |
976 | gtk_icon_view_constructed (GObject *object) |
977 | { |
978 | GtkIconView *icon_view = GTK_ICON_VIEW (object); |
979 | |
980 | G_OBJECT_CLASS (gtk_icon_view_parent_class)->constructed (object); |
981 | |
982 | gtk_icon_view_ensure_cell_area (icon_view, NULL); |
983 | } |
984 | |
985 | static void |
986 | gtk_icon_view_dispose (GObject *object) |
987 | { |
988 | GtkIconView *icon_view; |
989 | GtkIconViewPrivate *priv; |
990 | |
991 | icon_view = GTK_ICON_VIEW (object); |
992 | priv = icon_view->priv; |
993 | |
994 | gtk_icon_view_set_model (icon_view, NULL); |
995 | |
996 | if (icon_view->priv->scroll_to_path != NULL) |
997 | { |
998 | gtk_tree_row_reference_free (reference: icon_view->priv->scroll_to_path); |
999 | icon_view->priv->scroll_to_path = NULL; |
1000 | } |
1001 | |
1002 | remove_scroll_timeout (icon_view); |
1003 | |
1004 | if (icon_view->priv->hadjustment != NULL) |
1005 | { |
1006 | g_object_unref (object: icon_view->priv->hadjustment); |
1007 | icon_view->priv->hadjustment = NULL; |
1008 | } |
1009 | |
1010 | if (icon_view->priv->vadjustment != NULL) |
1011 | { |
1012 | g_object_unref (object: icon_view->priv->vadjustment); |
1013 | icon_view->priv->vadjustment = NULL; |
1014 | } |
1015 | |
1016 | if (priv->cell_area_context) |
1017 | { |
1018 | g_object_unref (object: priv->cell_area_context); |
1019 | priv->cell_area_context = NULL; |
1020 | } |
1021 | |
1022 | if (priv->row_contexts) |
1023 | { |
1024 | g_ptr_array_free (array: priv->row_contexts, TRUE); |
1025 | priv->row_contexts = NULL; |
1026 | } |
1027 | |
1028 | if (priv->cell_area) |
1029 | { |
1030 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
1031 | |
1032 | g_signal_handler_disconnect (instance: priv->cell_area, handler_id: priv->add_editable_id); |
1033 | g_signal_handler_disconnect (instance: priv->cell_area, handler_id: priv->remove_editable_id); |
1034 | priv->add_editable_id = 0; |
1035 | priv->remove_editable_id = 0; |
1036 | |
1037 | g_object_unref (object: priv->cell_area); |
1038 | priv->cell_area = NULL; |
1039 | } |
1040 | |
1041 | g_clear_object (&priv->key_controller); |
1042 | |
1043 | g_clear_pointer (&priv->source_formats, gdk_content_formats_unref); |
1044 | |
1045 | G_OBJECT_CLASS (gtk_icon_view_parent_class)->dispose (object); |
1046 | } |
1047 | |
1048 | static void |
1049 | gtk_icon_view_set_property (GObject *object, |
1050 | guint prop_id, |
1051 | const GValue *value, |
1052 | GParamSpec *pspec) |
1053 | { |
1054 | GtkIconView *icon_view; |
1055 | GtkCellArea *area; |
1056 | |
1057 | icon_view = GTK_ICON_VIEW (object); |
1058 | |
1059 | switch (prop_id) |
1060 | { |
1061 | case PROP_SELECTION_MODE: |
1062 | gtk_icon_view_set_selection_mode (icon_view, mode: g_value_get_enum (value)); |
1063 | break; |
1064 | case PROP_PIXBUF_COLUMN: |
1065 | gtk_icon_view_set_pixbuf_column (icon_view, column: g_value_get_int (value)); |
1066 | break; |
1067 | case PROP_TEXT_COLUMN: |
1068 | gtk_icon_view_set_text_column (icon_view, column: g_value_get_int (value)); |
1069 | break; |
1070 | case PROP_MARKUP_COLUMN: |
1071 | gtk_icon_view_set_markup_column (icon_view, column: g_value_get_int (value)); |
1072 | break; |
1073 | case PROP_MODEL: |
1074 | gtk_icon_view_set_model (icon_view, model: g_value_get_object (value)); |
1075 | break; |
1076 | case PROP_ITEM_ORIENTATION: |
1077 | gtk_icon_view_set_item_orientation (icon_view, orientation: g_value_get_enum (value)); |
1078 | break; |
1079 | case PROP_COLUMNS: |
1080 | gtk_icon_view_set_columns (icon_view, columns: g_value_get_int (value)); |
1081 | break; |
1082 | case PROP_ITEM_WIDTH: |
1083 | gtk_icon_view_set_item_width (icon_view, item_width: g_value_get_int (value)); |
1084 | break; |
1085 | case PROP_SPACING: |
1086 | gtk_icon_view_set_spacing (icon_view, spacing: g_value_get_int (value)); |
1087 | break; |
1088 | case PROP_ROW_SPACING: |
1089 | gtk_icon_view_set_row_spacing (icon_view, row_spacing: g_value_get_int (value)); |
1090 | break; |
1091 | case PROP_COLUMN_SPACING: |
1092 | gtk_icon_view_set_column_spacing (icon_view, column_spacing: g_value_get_int (value)); |
1093 | break; |
1094 | case PROP_MARGIN: |
1095 | gtk_icon_view_set_margin (icon_view, margin: g_value_get_int (value)); |
1096 | break; |
1097 | case PROP_REORDERABLE: |
1098 | gtk_icon_view_set_reorderable (icon_view, reorderable: g_value_get_boolean (value)); |
1099 | break; |
1100 | |
1101 | case PROP_TOOLTIP_COLUMN: |
1102 | gtk_icon_view_set_tooltip_column (icon_view, column: g_value_get_int (value)); |
1103 | break; |
1104 | |
1105 | case PROP_ITEM_PADDING: |
1106 | gtk_icon_view_set_item_padding (icon_view, item_padding: g_value_get_int (value)); |
1107 | break; |
1108 | |
1109 | case PROP_ACTIVATE_ON_SINGLE_CLICK: |
1110 | gtk_icon_view_set_activate_on_single_click (icon_view, single: g_value_get_boolean (value)); |
1111 | break; |
1112 | |
1113 | case PROP_CELL_AREA: |
1114 | /* Construct-only, can only be assigned once */ |
1115 | area = g_value_get_object (value); |
1116 | if (area) |
1117 | { |
1118 | if (icon_view->priv->cell_area != NULL) |
1119 | { |
1120 | g_warning ("cell-area has already been set, ignoring construct property" ); |
1121 | g_object_ref_sink (area); |
1122 | g_object_unref (object: area); |
1123 | } |
1124 | else |
1125 | gtk_icon_view_ensure_cell_area (icon_view, cell_area: area); |
1126 | } |
1127 | break; |
1128 | |
1129 | case PROP_HADJUSTMENT: |
1130 | gtk_icon_view_set_hadjustment (icon_view, adjustment: g_value_get_object (value)); |
1131 | break; |
1132 | case PROP_VADJUSTMENT: |
1133 | gtk_icon_view_set_vadjustment (icon_view, adjustment: g_value_get_object (value)); |
1134 | break; |
1135 | case PROP_HSCROLL_POLICY: |
1136 | if (icon_view->priv->hscroll_policy != g_value_get_enum (value)) |
1137 | { |
1138 | icon_view->priv->hscroll_policy = g_value_get_enum (value); |
1139 | gtk_widget_queue_resize (GTK_WIDGET (icon_view)); |
1140 | g_object_notify_by_pspec (object, pspec); |
1141 | } |
1142 | break; |
1143 | case PROP_VSCROLL_POLICY: |
1144 | if (icon_view->priv->vscroll_policy != g_value_get_enum (value)) |
1145 | { |
1146 | icon_view->priv->vscroll_policy = g_value_get_enum (value); |
1147 | gtk_widget_queue_resize (GTK_WIDGET (icon_view)); |
1148 | g_object_notify_by_pspec (object, pspec); |
1149 | } |
1150 | break; |
1151 | |
1152 | default: |
1153 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
1154 | break; |
1155 | } |
1156 | } |
1157 | |
1158 | static void |
1159 | gtk_icon_view_get_property (GObject *object, |
1160 | guint prop_id, |
1161 | GValue *value, |
1162 | GParamSpec *pspec) |
1163 | { |
1164 | GtkIconView *icon_view; |
1165 | |
1166 | icon_view = GTK_ICON_VIEW (object); |
1167 | |
1168 | switch (prop_id) |
1169 | { |
1170 | case PROP_SELECTION_MODE: |
1171 | g_value_set_enum (value, v_enum: icon_view->priv->selection_mode); |
1172 | break; |
1173 | case PROP_PIXBUF_COLUMN: |
1174 | g_value_set_int (value, v_int: icon_view->priv->pixbuf_column); |
1175 | break; |
1176 | case PROP_TEXT_COLUMN: |
1177 | g_value_set_int (value, v_int: icon_view->priv->text_column); |
1178 | break; |
1179 | case PROP_MARKUP_COLUMN: |
1180 | g_value_set_int (value, v_int: icon_view->priv->markup_column); |
1181 | break; |
1182 | case PROP_MODEL: |
1183 | g_value_set_object (value, v_object: icon_view->priv->model); |
1184 | break; |
1185 | case PROP_ITEM_ORIENTATION: |
1186 | g_value_set_enum (value, v_enum: icon_view->priv->item_orientation); |
1187 | break; |
1188 | case PROP_COLUMNS: |
1189 | g_value_set_int (value, v_int: icon_view->priv->columns); |
1190 | break; |
1191 | case PROP_ITEM_WIDTH: |
1192 | g_value_set_int (value, v_int: icon_view->priv->item_width); |
1193 | break; |
1194 | case PROP_SPACING: |
1195 | g_value_set_int (value, v_int: icon_view->priv->spacing); |
1196 | break; |
1197 | case PROP_ROW_SPACING: |
1198 | g_value_set_int (value, v_int: icon_view->priv->row_spacing); |
1199 | break; |
1200 | case PROP_COLUMN_SPACING: |
1201 | g_value_set_int (value, v_int: icon_view->priv->column_spacing); |
1202 | break; |
1203 | case PROP_MARGIN: |
1204 | g_value_set_int (value, v_int: icon_view->priv->margin); |
1205 | break; |
1206 | case PROP_REORDERABLE: |
1207 | g_value_set_boolean (value, v_boolean: icon_view->priv->reorderable); |
1208 | break; |
1209 | case PROP_TOOLTIP_COLUMN: |
1210 | g_value_set_int (value, v_int: icon_view->priv->tooltip_column); |
1211 | break; |
1212 | |
1213 | case PROP_ITEM_PADDING: |
1214 | g_value_set_int (value, v_int: icon_view->priv->item_padding); |
1215 | break; |
1216 | |
1217 | case PROP_ACTIVATE_ON_SINGLE_CLICK: |
1218 | g_value_set_boolean (value, v_boolean: icon_view->priv->activate_on_single_click); |
1219 | break; |
1220 | |
1221 | case PROP_CELL_AREA: |
1222 | g_value_set_object (value, v_object: icon_view->priv->cell_area); |
1223 | break; |
1224 | |
1225 | case PROP_HADJUSTMENT: |
1226 | g_value_set_object (value, v_object: icon_view->priv->hadjustment); |
1227 | break; |
1228 | case PROP_VADJUSTMENT: |
1229 | g_value_set_object (value, v_object: icon_view->priv->vadjustment); |
1230 | break; |
1231 | case PROP_HSCROLL_POLICY: |
1232 | g_value_set_enum (value, v_enum: icon_view->priv->hscroll_policy); |
1233 | break; |
1234 | case PROP_VSCROLL_POLICY: |
1235 | g_value_set_enum (value, v_enum: icon_view->priv->vscroll_policy); |
1236 | break; |
1237 | |
1238 | default: |
1239 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
1240 | break; |
1241 | } |
1242 | } |
1243 | |
1244 | /* GtkWidget methods */ |
1245 | |
1246 | static int |
1247 | gtk_icon_view_get_n_items (GtkIconView *icon_view) |
1248 | { |
1249 | GtkIconViewPrivate *priv = icon_view->priv; |
1250 | |
1251 | if (priv->model == NULL) |
1252 | return 0; |
1253 | |
1254 | return gtk_tree_model_iter_n_children (tree_model: priv->model, NULL); |
1255 | } |
1256 | |
1257 | static void |
1258 | adjust_wrap_width (GtkIconView *icon_view) |
1259 | { |
1260 | if (icon_view->priv->text_cell) |
1261 | { |
1262 | int pixbuf_width, wrap_width; |
1263 | |
1264 | if (icon_view->priv->items && icon_view->priv->pixbuf_cell) |
1265 | { |
1266 | gtk_cell_renderer_get_preferred_width (cell: icon_view->priv->pixbuf_cell, |
1267 | GTK_WIDGET (icon_view), |
1268 | minimum_size: &pixbuf_width, NULL); |
1269 | } |
1270 | else |
1271 | { |
1272 | pixbuf_width = 0; |
1273 | } |
1274 | |
1275 | if (icon_view->priv->item_width >= 0) |
1276 | { |
1277 | if (icon_view->priv->item_orientation == GTK_ORIENTATION_VERTICAL) |
1278 | { |
1279 | wrap_width = icon_view->priv->item_width; |
1280 | } |
1281 | else |
1282 | { |
1283 | wrap_width = icon_view->priv->item_width - pixbuf_width; |
1284 | } |
1285 | |
1286 | wrap_width -= 2 * icon_view->priv->item_padding * 2; |
1287 | } |
1288 | else |
1289 | { |
1290 | wrap_width = MAX (pixbuf_width * 2, 50); |
1291 | } |
1292 | |
1293 | if (icon_view->priv->items && icon_view->priv->pixbuf_cell) |
1294 | { |
1295 | /* Here we go with the same old guess, try the icon size and set double |
1296 | * the size of the first icon found in the list, naive but works much |
1297 | * of the time */ |
1298 | |
1299 | wrap_width = MAX (wrap_width * 2, 50); |
1300 | } |
1301 | |
1302 | g_object_set (object: icon_view->priv->text_cell, first_property_name: "wrap-width" , wrap_width, NULL); |
1303 | g_object_set (object: icon_view->priv->text_cell, first_property_name: "width" , wrap_width, NULL); |
1304 | } |
1305 | } |
1306 | |
1307 | /* General notes about layout |
1308 | * |
1309 | * The icon view is layouted like this: |
1310 | * |
1311 | * +----------+ s +----------+ |
1312 | * | padding | p | padding | |
1313 | * | +------+ | a | +------+ | |
1314 | * | | cell | | c | | cell | | |
1315 | * | +------+ | i | +------+ | |
1316 | * | | n | | |
1317 | * +----------+ g +----------+ |
1318 | * |
1319 | * In size request and allocation code, there are 3 sizes that are used: |
1320 | * * cell size |
1321 | * This is the size returned by gtk_cell_area_get_preferred_foo(). In places |
1322 | * where code is interacting with the cell area and renderers this is useful. |
1323 | * * padded size |
1324 | * This is the cell size plus the item padding on each side. |
1325 | * * spaced size |
1326 | * This is the padded size plus the spacing. This is what’s used for most |
1327 | * calculations because it can (ab)use the following formula: |
1328 | * iconview_size = 2 * margin + n_items * spaced_size - spacing |
1329 | * So when reading this code and fixing my bugs where I confuse these two, be |
1330 | * aware of this distinction. |
1331 | */ |
1332 | static void |
1333 | cell_area_get_preferred_size (GtkIconView *icon_view, |
1334 | GtkCellAreaContext *context, |
1335 | GtkOrientation orientation, |
1336 | int for_size, |
1337 | int *minimum, |
1338 | int *natural) |
1339 | { |
1340 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
1341 | { |
1342 | if (for_size > 0) |
1343 | gtk_cell_area_get_preferred_width_for_height (area: icon_view->priv->cell_area, |
1344 | context, |
1345 | GTK_WIDGET (icon_view), |
1346 | height: for_size, |
1347 | minimum_width: minimum, natural_width: natural); |
1348 | else |
1349 | gtk_cell_area_get_preferred_width (area: icon_view->priv->cell_area, |
1350 | context, |
1351 | GTK_WIDGET (icon_view), |
1352 | minimum_width: minimum, natural_width: natural); |
1353 | } |
1354 | else |
1355 | { |
1356 | if (for_size > 0) |
1357 | gtk_cell_area_get_preferred_height_for_width (area: icon_view->priv->cell_area, |
1358 | context, |
1359 | GTK_WIDGET (icon_view), |
1360 | width: for_size, |
1361 | minimum_height: minimum, natural_height: natural); |
1362 | else |
1363 | gtk_cell_area_get_preferred_height (area: icon_view->priv->cell_area, |
1364 | context, |
1365 | GTK_WIDGET (icon_view), |
1366 | minimum_height: minimum, natural_height: natural); |
1367 | } |
1368 | } |
1369 | |
1370 | static gboolean |
1371 | gtk_icon_view_is_empty (GtkIconView *icon_view) |
1372 | { |
1373 | return icon_view->priv->items == NULL; |
1374 | } |
1375 | |
1376 | static void |
1377 | gtk_icon_view_get_preferred_item_size (GtkIconView *icon_view, |
1378 | GtkOrientation orientation, |
1379 | int for_size, |
1380 | int *minimum, |
1381 | int *natural) |
1382 | { |
1383 | GtkIconViewPrivate *priv = icon_view->priv; |
1384 | GtkCellAreaContext *context; |
1385 | GList *items; |
1386 | |
1387 | g_assert (!gtk_icon_view_is_empty (icon_view)); |
1388 | |
1389 | context = gtk_cell_area_create_context (area: priv->cell_area); |
1390 | |
1391 | for_size -= 2 * priv->item_padding; |
1392 | |
1393 | if (for_size > 0) |
1394 | { |
1395 | /* This is necessary for the context to work properly */ |
1396 | for (items = priv->items; items; items = items->next) |
1397 | { |
1398 | GtkIconViewItem *item = items->data; |
1399 | |
1400 | _gtk_icon_view_set_cell_data (icon_view, item); |
1401 | cell_area_get_preferred_size (icon_view, context, orientation: 1 - orientation, for_size: -1, NULL, NULL); |
1402 | } |
1403 | } |
1404 | |
1405 | for (items = priv->items; items; items = items->next) |
1406 | { |
1407 | GtkIconViewItem *item = items->data; |
1408 | |
1409 | _gtk_icon_view_set_cell_data (icon_view, item); |
1410 | if (items == priv->items) |
1411 | adjust_wrap_width (icon_view); |
1412 | cell_area_get_preferred_size (icon_view, context, orientation, for_size, NULL, NULL); |
1413 | } |
1414 | |
1415 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
1416 | { |
1417 | if (for_size > 0) |
1418 | gtk_cell_area_context_get_preferred_width_for_height (context, |
1419 | height: for_size, |
1420 | minimum_width: minimum, natural_width: natural); |
1421 | else |
1422 | gtk_cell_area_context_get_preferred_width (context, |
1423 | minimum_width: minimum, natural_width: natural); |
1424 | } |
1425 | else |
1426 | { |
1427 | if (for_size > 0) |
1428 | gtk_cell_area_context_get_preferred_height_for_width (context, |
1429 | width: for_size, |
1430 | minimum_height: minimum, natural_height: natural); |
1431 | else |
1432 | gtk_cell_area_context_get_preferred_height (context, |
1433 | minimum_height: minimum, natural_height: natural); |
1434 | } |
1435 | |
1436 | if (orientation == GTK_ORIENTATION_HORIZONTAL && priv->item_width >= 0) |
1437 | { |
1438 | if (minimum) |
1439 | *minimum = MAX (*minimum, priv->item_width); |
1440 | if (natural) |
1441 | *natural = *minimum; |
1442 | } |
1443 | |
1444 | if (minimum) |
1445 | *minimum = MAX (1, *minimum + 2 * priv->item_padding); |
1446 | if (natural) |
1447 | *natural = MAX (1, *natural + 2 * priv->item_padding); |
1448 | |
1449 | g_object_unref (object: context); |
1450 | } |
1451 | |
1452 | static void |
1453 | gtk_icon_view_compute_n_items_for_size (GtkIconView *icon_view, |
1454 | GtkOrientation orientation, |
1455 | int size, |
1456 | int *min_items, |
1457 | int *min_item_size, |
1458 | int *max_items, |
1459 | int *max_item_size) |
1460 | { |
1461 | GtkIconViewPrivate *priv = icon_view->priv; |
1462 | int minimum, natural, spacing; |
1463 | |
1464 | g_return_if_fail (min_item_size == NULL || min_items != NULL); |
1465 | g_return_if_fail (max_item_size == NULL || max_items != NULL); |
1466 | g_return_if_fail (!gtk_icon_view_is_empty (icon_view)); |
1467 | |
1468 | gtk_icon_view_get_preferred_item_size (icon_view, orientation, for_size: -1, minimum: &minimum, natural: &natural); |
1469 | |
1470 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
1471 | spacing = priv->column_spacing; |
1472 | else |
1473 | spacing = priv->row_spacing; |
1474 | |
1475 | size -= 2 * priv->margin; |
1476 | size += spacing; |
1477 | minimum += spacing; |
1478 | natural += spacing; |
1479 | |
1480 | if (priv->columns > 0) |
1481 | { |
1482 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
1483 | { |
1484 | if (min_items) |
1485 | *min_items = priv->columns; |
1486 | if (max_items) |
1487 | *max_items = priv->columns; |
1488 | } |
1489 | else |
1490 | { |
1491 | int n_items = gtk_icon_view_get_n_items (icon_view); |
1492 | |
1493 | if (min_items) |
1494 | *min_items = (n_items + priv->columns - 1) / priv->columns; |
1495 | if (max_items) |
1496 | *max_items = (n_items + priv->columns - 1) / priv->columns; |
1497 | } |
1498 | } |
1499 | else |
1500 | { |
1501 | if (max_items) |
1502 | { |
1503 | if (size <= minimum) |
1504 | *max_items = 1; |
1505 | else |
1506 | *max_items = size / minimum; |
1507 | } |
1508 | |
1509 | if (min_items) |
1510 | { |
1511 | if (size <= natural) |
1512 | *min_items = 1; |
1513 | else |
1514 | *min_items = size / natural; |
1515 | } |
1516 | } |
1517 | |
1518 | if (min_item_size) |
1519 | { |
1520 | *min_item_size = size / *min_items; |
1521 | *min_item_size = CLAMP (*min_item_size, minimum, natural); |
1522 | *min_item_size -= spacing; |
1523 | *min_item_size -= 2 * priv->item_padding; |
1524 | } |
1525 | |
1526 | if (max_item_size) |
1527 | { |
1528 | *max_item_size = size / *max_items; |
1529 | *max_item_size = CLAMP (*max_item_size, minimum, natural); |
1530 | *max_item_size -= spacing; |
1531 | *max_item_size -= 2 * priv->item_padding; |
1532 | } |
1533 | } |
1534 | |
1535 | static GtkSizeRequestMode |
1536 | gtk_icon_view_get_request_mode (GtkWidget *widget) |
1537 | { |
1538 | return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; |
1539 | } |
1540 | |
1541 | static void |
1542 | gtk_icon_view_measure (GtkWidget *widget, |
1543 | GtkOrientation orientation, |
1544 | int for_size, |
1545 | int *minimum, |
1546 | int *natural, |
1547 | int *minimum_baseline, |
1548 | int *natural_baseline) |
1549 | { |
1550 | GtkIconView *icon_view = GTK_ICON_VIEW (widget); |
1551 | GtkIconViewPrivate *priv = icon_view->priv; |
1552 | GtkOrientation other_orientation = orientation == GTK_ORIENTATION_HORIZONTAL ? |
1553 | GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL; |
1554 | int item_min, item_nat, items = 0, item_size = 0, n_items; |
1555 | |
1556 | if (gtk_icon_view_is_empty (icon_view)) |
1557 | { |
1558 | *minimum = *natural = 2 * priv->margin; |
1559 | return; |
1560 | } |
1561 | |
1562 | n_items = gtk_icon_view_get_n_items (icon_view); |
1563 | |
1564 | if (for_size < 0) |
1565 | { |
1566 | gtk_icon_view_get_preferred_item_size (icon_view, orientation, for_size: -1, minimum: &item_min, natural: &item_nat); |
1567 | |
1568 | if (priv->columns > 0) |
1569 | { |
1570 | int n_rows = (n_items + priv->columns - 1) / priv->columns; |
1571 | |
1572 | *minimum = item_min * n_rows + priv->row_spacing * (n_rows - 1); |
1573 | *natural = item_nat * n_rows + priv->row_spacing * (n_rows - 1); |
1574 | } |
1575 | else |
1576 | { |
1577 | *minimum = item_min; |
1578 | *natural = item_nat * n_items + priv->row_spacing * (n_items - 1); |
1579 | } |
1580 | } |
1581 | else |
1582 | { |
1583 | gtk_icon_view_compute_n_items_for_size (icon_view, orientation, size: for_size, NULL, NULL, max_items: &items, max_item_size: &item_size); |
1584 | gtk_icon_view_get_preferred_item_size (icon_view, orientation: other_orientation, for_size: item_size, minimum: &item_min, natural: &item_nat); |
1585 | *minimum = (item_min + priv->row_spacing) * ((n_items + items - 1) / items) - priv->row_spacing; |
1586 | *natural = (item_nat + priv->row_spacing) * ((n_items + items - 1) / items) - priv->row_spacing; |
1587 | } |
1588 | |
1589 | *minimum += 2 * priv->margin; |
1590 | *natural += 2 * priv->margin; |
1591 | } |
1592 | |
1593 | |
1594 | static void |
1595 | gtk_icon_view_allocate_children (GtkIconView *icon_view) |
1596 | { |
1597 | GList *list; |
1598 | |
1599 | for (list = icon_view->priv->children; list; list = list->next) |
1600 | { |
1601 | GtkIconViewChild *child = list->data; |
1602 | |
1603 | /* totally ignore our child's requisition */ |
1604 | gtk_widget_size_allocate (widget: child->widget, allocation: &child->area, baseline: -1); |
1605 | } |
1606 | } |
1607 | |
1608 | static void |
1609 | gtk_icon_view_size_allocate (GtkWidget *widget, |
1610 | int width, |
1611 | int height, |
1612 | int baseline) |
1613 | { |
1614 | GtkIconView *icon_view = GTK_ICON_VIEW (widget); |
1615 | |
1616 | gtk_icon_view_layout (icon_view); |
1617 | |
1618 | gtk_icon_view_allocate_children (icon_view); |
1619 | |
1620 | /* Delay signal emission */ |
1621 | g_object_freeze_notify (G_OBJECT (icon_view->priv->hadjustment)); |
1622 | g_object_freeze_notify (G_OBJECT (icon_view->priv->vadjustment)); |
1623 | |
1624 | gtk_icon_view_set_hadjustment_values (icon_view); |
1625 | gtk_icon_view_set_vadjustment_values (icon_view); |
1626 | |
1627 | if (gtk_widget_get_realized (widget) && |
1628 | icon_view->priv->scroll_to_path) |
1629 | { |
1630 | GtkTreePath *path; |
1631 | path = gtk_tree_row_reference_get_path (reference: icon_view->priv->scroll_to_path); |
1632 | gtk_tree_row_reference_free (reference: icon_view->priv->scroll_to_path); |
1633 | icon_view->priv->scroll_to_path = NULL; |
1634 | |
1635 | gtk_icon_view_scroll_to_path (icon_view, path, |
1636 | use_align: icon_view->priv->scroll_to_use_align, |
1637 | row_align: icon_view->priv->scroll_to_row_align, |
1638 | col_align: icon_view->priv->scroll_to_col_align); |
1639 | gtk_tree_path_free (path); |
1640 | } |
1641 | |
1642 | /* Emit any pending signals now */ |
1643 | g_object_thaw_notify (G_OBJECT (icon_view->priv->hadjustment)); |
1644 | g_object_thaw_notify (G_OBJECT (icon_view->priv->vadjustment)); |
1645 | } |
1646 | |
1647 | static void |
1648 | gtk_icon_view_snapshot (GtkWidget *widget, |
1649 | GtkSnapshot *snapshot) |
1650 | { |
1651 | GtkIconView *icon_view; |
1652 | GList *icons; |
1653 | GtkTreePath *path; |
1654 | int dest_index; |
1655 | GtkIconViewDropPosition dest_pos; |
1656 | GtkIconViewItem *dest_item = NULL; |
1657 | GtkStyleContext *context; |
1658 | int width, height; |
1659 | double offset_x, offset_y; |
1660 | |
1661 | icon_view = GTK_ICON_VIEW (widget); |
1662 | |
1663 | context = gtk_widget_get_style_context (widget); |
1664 | width = gtk_widget_get_width (widget); |
1665 | height = gtk_widget_get_height (widget); |
1666 | |
1667 | offset_x = gtk_adjustment_get_value (adjustment: icon_view->priv->hadjustment); |
1668 | offset_y = gtk_adjustment_get_value (adjustment: icon_view->priv->vadjustment); |
1669 | |
1670 | gtk_snapshot_save (snapshot); |
1671 | gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (- offset_x, - offset_y)); |
1672 | |
1673 | gtk_icon_view_get_drag_dest_item (icon_view, path: &path, pos: &dest_pos); |
1674 | |
1675 | if (path) |
1676 | { |
1677 | dest_index = gtk_tree_path_get_indices (path)[0]; |
1678 | gtk_tree_path_free (path); |
1679 | } |
1680 | else |
1681 | dest_index = -1; |
1682 | |
1683 | for (icons = icon_view->priv->items; icons; icons = icons->next) |
1684 | { |
1685 | GtkIconViewItem *item = icons->data; |
1686 | graphene_rect_t area; |
1687 | |
1688 | graphene_rect_init (r: &area, |
1689 | x: item->cell_area.x - icon_view->priv->item_padding, |
1690 | y: item->cell_area.y - icon_view->priv->item_padding, |
1691 | width: item->cell_area.width + icon_view->priv->item_padding * 2, |
1692 | height: item->cell_area.height + icon_view->priv->item_padding * 2); |
1693 | |
1694 | if (gdk_rectangle_intersect (src1: &item->cell_area, |
1695 | src2: &(GdkRectangle) { offset_x, offset_y, width, height }, NULL)) |
1696 | { |
1697 | gtk_icon_view_snapshot_item (icon_view, snapshot, item, |
1698 | x: item->cell_area.x, y: item->cell_area.y, |
1699 | draw_focus: icon_view->priv->draw_focus); |
1700 | |
1701 | if (dest_index == item->index) |
1702 | dest_item = item; |
1703 | } |
1704 | } |
1705 | |
1706 | if (dest_item && |
1707 | dest_pos != GTK_ICON_VIEW_NO_DROP) |
1708 | { |
1709 | GdkRectangle rect = { 0 }; |
1710 | |
1711 | switch (dest_pos) |
1712 | { |
1713 | case GTK_ICON_VIEW_DROP_INTO: |
1714 | rect = dest_item->cell_area; |
1715 | break; |
1716 | case GTK_ICON_VIEW_DROP_ABOVE: |
1717 | rect.x = dest_item->cell_area.x; |
1718 | rect.y = dest_item->cell_area.y - 1; |
1719 | rect.width = dest_item->cell_area.width; |
1720 | rect.height = 2; |
1721 | break; |
1722 | case GTK_ICON_VIEW_DROP_LEFT: |
1723 | rect.x = dest_item->cell_area.x - 1; |
1724 | rect.y = dest_item->cell_area.y; |
1725 | rect.width = 2; |
1726 | rect.height = dest_item->cell_area.height; |
1727 | break; |
1728 | case GTK_ICON_VIEW_DROP_BELOW: |
1729 | rect.x = dest_item->cell_area.x; |
1730 | rect.y = dest_item->cell_area.y + dest_item->cell_area.height - 1; |
1731 | rect.width = dest_item->cell_area.width; |
1732 | rect.height = 2; |
1733 | break; |
1734 | case GTK_ICON_VIEW_DROP_RIGHT: |
1735 | rect.x = dest_item->cell_area.x + dest_item->cell_area.width - 1; |
1736 | rect.y = dest_item->cell_area.y; |
1737 | rect.width = 2; |
1738 | rect.height = dest_item->cell_area.height; |
1739 | break; |
1740 | case GTK_ICON_VIEW_NO_DROP: |
1741 | default: |
1742 | break; |
1743 | } |
1744 | |
1745 | gtk_style_context_save_to_node (context, node: icon_view->priv->dndnode); |
1746 | gtk_style_context_set_state (context, flags: gtk_style_context_get_state (context) | GTK_STATE_FLAG_DROP_ACTIVE); |
1747 | |
1748 | gtk_snapshot_render_frame (snapshot, context, |
1749 | x: rect.x, y: rect.y, |
1750 | width: rect.width, height: rect.height); |
1751 | |
1752 | gtk_style_context_restore (context); |
1753 | } |
1754 | |
1755 | if (icon_view->priv->doing_rubberband) |
1756 | gtk_icon_view_snapshot_rubberband (icon_view, snapshot); |
1757 | |
1758 | gtk_snapshot_restore (snapshot); |
1759 | |
1760 | GTK_WIDGET_CLASS (gtk_icon_view_parent_class)->snapshot (widget, snapshot); |
1761 | } |
1762 | |
1763 | static gboolean |
1764 | rubberband_scroll_timeout (gpointer data) |
1765 | { |
1766 | GtkIconView *icon_view = data; |
1767 | |
1768 | gtk_adjustment_set_value (adjustment: icon_view->priv->vadjustment, |
1769 | value: gtk_adjustment_get_value (adjustment: icon_view->priv->vadjustment) + |
1770 | icon_view->priv->scroll_value_diff); |
1771 | |
1772 | gtk_icon_view_update_rubberband (icon_view); |
1773 | |
1774 | return TRUE; |
1775 | } |
1776 | |
1777 | static GtkIconViewItem * |
1778 | _gtk_icon_view_get_item_at_widget_coords (GtkIconView *icon_view, |
1779 | int x, |
1780 | int y, |
1781 | gboolean only_in_cell, |
1782 | GtkCellRenderer **cell_at_pos) |
1783 | { |
1784 | x += gtk_adjustment_get_value (adjustment: icon_view->priv->hadjustment); |
1785 | y += gtk_adjustment_get_value (adjustment: icon_view->priv->vadjustment); |
1786 | |
1787 | return _gtk_icon_view_get_item_at_coords (icon_view, x, y, |
1788 | only_in_cell, cell_at_pos); |
1789 | } |
1790 | |
1791 | static void |
1792 | gtk_icon_view_motion (GtkEventController *controller, |
1793 | double x, |
1794 | double y, |
1795 | gpointer user_data) |
1796 | { |
1797 | GtkIconView *icon_view; |
1798 | int abs_y; |
1799 | GdkDevice *device; |
1800 | |
1801 | icon_view = GTK_ICON_VIEW (user_data); |
1802 | |
1803 | icon_view->priv->mouse_x = x; |
1804 | icon_view->priv->mouse_y = y; |
1805 | |
1806 | device = gtk_event_controller_get_current_event_device (controller); |
1807 | gtk_icon_view_maybe_begin_drag (icon_view, x, y, device); |
1808 | |
1809 | if (icon_view->priv->doing_rubberband) |
1810 | { |
1811 | int height; |
1812 | gtk_icon_view_update_rubberband (icon_view); |
1813 | |
1814 | abs_y = icon_view->priv->mouse_y - icon_view->priv->height * |
1815 | (gtk_adjustment_get_value (adjustment: icon_view->priv->vadjustment) / |
1816 | (gtk_adjustment_get_upper (adjustment: icon_view->priv->vadjustment) - |
1817 | gtk_adjustment_get_lower (adjustment: icon_view->priv->vadjustment))); |
1818 | |
1819 | height = gtk_widget_get_height (GTK_WIDGET (icon_view)); |
1820 | |
1821 | |
1822 | if (abs_y < 0 || abs_y > height) |
1823 | { |
1824 | if (abs_y < 0) |
1825 | icon_view->priv->scroll_value_diff = abs_y; |
1826 | else |
1827 | icon_view->priv->scroll_value_diff = abs_y - height; |
1828 | |
1829 | icon_view->priv->event_last_x = icon_view->priv->mouse_x; |
1830 | icon_view->priv->event_last_y = icon_view->priv->mouse_x; |
1831 | |
1832 | if (icon_view->priv->scroll_timeout_id == 0) { |
1833 | icon_view->priv->scroll_timeout_id = g_timeout_add (interval: 30, function: rubberband_scroll_timeout, data: icon_view); |
1834 | gdk_source_set_static_name_by_id (tag: icon_view->priv->scroll_timeout_id, name: "[gtk] rubberband_scroll_timeout" ); |
1835 | } |
1836 | } |
1837 | else |
1838 | remove_scroll_timeout (icon_view); |
1839 | } |
1840 | else |
1841 | { |
1842 | GtkIconViewItem *item, *last_prelight_item; |
1843 | GtkCellRenderer *cell = NULL; |
1844 | |
1845 | last_prelight_item = icon_view->priv->last_prelight; |
1846 | item = _gtk_icon_view_get_item_at_widget_coords (icon_view, |
1847 | x: icon_view->priv->mouse_x, |
1848 | y: icon_view->priv->mouse_y, |
1849 | FALSE, |
1850 | cell_at_pos: &cell); |
1851 | |
1852 | if (item != last_prelight_item) |
1853 | { |
1854 | if (item != NULL) |
1855 | { |
1856 | gtk_icon_view_queue_draw_item (icon_view, item); |
1857 | } |
1858 | |
1859 | if (last_prelight_item != NULL) |
1860 | { |
1861 | gtk_icon_view_queue_draw_item (icon_view, |
1862 | item: icon_view->priv->last_prelight); |
1863 | } |
1864 | |
1865 | icon_view->priv->last_prelight = item; |
1866 | } |
1867 | } |
1868 | } |
1869 | |
1870 | static void |
1871 | gtk_icon_view_leave (GtkEventController *controller, |
1872 | gpointer user_data) |
1873 | { |
1874 | GtkIconView *icon_view; |
1875 | GtkIconViewPrivate *priv; |
1876 | |
1877 | icon_view = GTK_ICON_VIEW (user_data); |
1878 | priv = icon_view->priv; |
1879 | |
1880 | if (priv->last_prelight) |
1881 | { |
1882 | gtk_icon_view_queue_draw_item (icon_view, item: priv->last_prelight); |
1883 | priv->last_prelight = NULL; |
1884 | } |
1885 | } |
1886 | |
1887 | static void |
1888 | gtk_icon_view_remove (GtkIconView *icon_view, |
1889 | GtkWidget *widget) |
1890 | { |
1891 | GtkIconViewChild *child = NULL; |
1892 | GList *tmp_list; |
1893 | |
1894 | tmp_list = icon_view->priv->children; |
1895 | while (tmp_list) |
1896 | { |
1897 | child = tmp_list->data; |
1898 | if (child->widget == widget) |
1899 | { |
1900 | gtk_widget_unparent (widget); |
1901 | |
1902 | icon_view->priv->children = g_list_remove_link (list: icon_view->priv->children, llink: tmp_list); |
1903 | g_list_free_1 (list: tmp_list); |
1904 | g_free (mem: child); |
1905 | return; |
1906 | } |
1907 | |
1908 | tmp_list = tmp_list->next; |
1909 | } |
1910 | } |
1911 | |
1912 | static void |
1913 | gtk_icon_view_add_editable (GtkCellArea *area, |
1914 | GtkCellRenderer *renderer, |
1915 | GtkCellEditable *editable, |
1916 | GdkRectangle *cell_area, |
1917 | const char *path, |
1918 | GtkIconView *icon_view) |
1919 | { |
1920 | GtkIconViewChild *child; |
1921 | GtkWidget *widget = GTK_WIDGET (editable); |
1922 | |
1923 | child = g_new (GtkIconViewChild, 1); |
1924 | |
1925 | child->widget = widget; |
1926 | child->area.x = cell_area->x; |
1927 | child->area.y = cell_area->y; |
1928 | child->area.width = cell_area->width; |
1929 | child->area.height = cell_area->height; |
1930 | |
1931 | icon_view->priv->children = g_list_append (list: icon_view->priv->children, data: child); |
1932 | |
1933 | gtk_widget_set_parent (widget, GTK_WIDGET (icon_view)); |
1934 | } |
1935 | |
1936 | static void |
1937 | gtk_icon_view_remove_editable (GtkCellArea *area, |
1938 | GtkCellRenderer *renderer, |
1939 | GtkCellEditable *editable, |
1940 | GtkIconView *icon_view) |
1941 | { |
1942 | GtkTreePath *path; |
1943 | |
1944 | if (gtk_widget_has_focus (GTK_WIDGET (editable))) |
1945 | gtk_widget_grab_focus (GTK_WIDGET (icon_view)); |
1946 | |
1947 | gtk_icon_view_remove (icon_view, GTK_WIDGET (editable)); |
1948 | |
1949 | path = gtk_tree_path_new_from_string (path: gtk_cell_area_get_current_path_string (area)); |
1950 | gtk_icon_view_queue_draw_path (icon_view, path); |
1951 | gtk_tree_path_free (path); |
1952 | } |
1953 | |
1954 | /** |
1955 | * gtk_icon_view_set_cursor: |
1956 | * @icon_view: A `GtkIconView` |
1957 | * @path: A `GtkTreePath` |
1958 | * @cell: (nullable): One of the cell renderers of @icon_view |
1959 | * @start_editing: %TRUE if the specified cell should start being edited. |
1960 | * |
1961 | * Sets the current keyboard focus to be at @path, and selects it. This is |
1962 | * useful when you want to focus the user’s attention on a particular item. |
1963 | * If @cell is not %NULL, then focus is given to the cell specified by |
1964 | * it. Additionally, if @start_editing is %TRUE, then editing should be |
1965 | * started in the specified cell. |
1966 | * |
1967 | * This function is often followed by `gtk_widget_grab_focus |
1968 | * (icon_view)` in order to give keyboard focus to the widget. |
1969 | * Please note that editing can only happen when the widget is realized. |
1970 | **/ |
1971 | void |
1972 | gtk_icon_view_set_cursor (GtkIconView *icon_view, |
1973 | GtkTreePath *path, |
1974 | GtkCellRenderer *cell, |
1975 | gboolean start_editing) |
1976 | { |
1977 | GtkIconViewItem *item = NULL; |
1978 | |
1979 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
1980 | g_return_if_fail (path != NULL); |
1981 | g_return_if_fail (cell == NULL || GTK_IS_CELL_RENDERER (cell)); |
1982 | |
1983 | if (icon_view->priv->cell_area) |
1984 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
1985 | |
1986 | if (gtk_tree_path_get_depth (path) == 1) |
1987 | item = g_list_nth_data (list: icon_view->priv->items, |
1988 | n: gtk_tree_path_get_indices(path)[0]); |
1989 | |
1990 | if (!item) |
1991 | return; |
1992 | |
1993 | _gtk_icon_view_set_cursor_item (icon_view, item, cursor_cell: cell); |
1994 | gtk_icon_view_scroll_to_path (icon_view, path, FALSE, row_align: 0.0, col_align: 0.0); |
1995 | |
1996 | if (start_editing && |
1997 | icon_view->priv->cell_area) |
1998 | { |
1999 | GtkCellAreaContext *context; |
2000 | |
2001 | context = g_ptr_array_index (icon_view->priv->row_contexts, item->row); |
2002 | _gtk_icon_view_set_cell_data (icon_view, item); |
2003 | gtk_cell_area_activate (area: icon_view->priv->cell_area, context, |
2004 | GTK_WIDGET (icon_view), cell_area: &item->cell_area, |
2005 | flags: 0, TRUE); |
2006 | } |
2007 | } |
2008 | |
2009 | /** |
2010 | * gtk_icon_view_get_cursor: |
2011 | * @icon_view: A `GtkIconView` |
2012 | * @path: (out) (optional) (transfer full): Return location for the current |
2013 | * cursor path |
2014 | * @cell: (out) (optional) (transfer none): Return location the current |
2015 | * focus cell |
2016 | * |
2017 | * Fills in @path and @cell with the current cursor path and cell. |
2018 | * If the cursor isn’t currently set, then *@path will be %NULL. |
2019 | * If no cell currently has focus, then *@cell will be %NULL. |
2020 | * |
2021 | * The returned `GtkTreePath` must be freed with gtk_tree_path_free(). |
2022 | * |
2023 | * Returns: %TRUE if the cursor is set. |
2024 | **/ |
2025 | gboolean |
2026 | gtk_icon_view_get_cursor (GtkIconView *icon_view, |
2027 | GtkTreePath **path, |
2028 | GtkCellRenderer **cell) |
2029 | { |
2030 | GtkIconViewItem *item; |
2031 | |
2032 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), FALSE); |
2033 | |
2034 | item = icon_view->priv->cursor_item; |
2035 | |
2036 | if (path != NULL) |
2037 | { |
2038 | if (item != NULL) |
2039 | *path = gtk_tree_path_new_from_indices (first_index: item->index, -1); |
2040 | else |
2041 | *path = NULL; |
2042 | } |
2043 | |
2044 | if (cell != NULL && item != NULL && icon_view->priv->cell_area != NULL) |
2045 | *cell = gtk_cell_area_get_focus_cell (area: icon_view->priv->cell_area); |
2046 | |
2047 | return (item != NULL); |
2048 | } |
2049 | |
2050 | static void |
2051 | gtk_icon_view_button_press (GtkGestureClick *gesture, |
2052 | int n_press, |
2053 | double x, |
2054 | double y, |
2055 | gpointer user_data) |
2056 | { |
2057 | GtkIconView *icon_view = user_data; |
2058 | GtkWidget *widget = GTK_WIDGET (icon_view); |
2059 | GtkIconViewItem *item; |
2060 | gboolean dirty = FALSE; |
2061 | GtkCellRenderer *cell = NULL, *cursor_cell = NULL; |
2062 | int button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); |
2063 | GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); |
2064 | GdkEvent *event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence); |
2065 | GdkModifierType state; |
2066 | |
2067 | if (!gtk_widget_has_focus (widget)) |
2068 | gtk_widget_grab_focus (widget); |
2069 | |
2070 | if (button == GDK_BUTTON_PRIMARY) |
2071 | { |
2072 | GdkModifierType extend_mod_mask = GDK_SHIFT_MASK; |
2073 | GdkModifierType modify_mod_mask = GDK_CONTROL_MASK; |
2074 | |
2075 | state = gdk_event_get_modifier_state (event); |
2076 | |
2077 | item = _gtk_icon_view_get_item_at_widget_coords (icon_view, |
2078 | x, y, |
2079 | FALSE, |
2080 | cell_at_pos: &cell); |
2081 | |
2082 | /* |
2083 | * We consider only the cells' area as the item area if the |
2084 | * item is not selected, but if it *is* selected, the complete |
2085 | * selection rectangle is considered to be part of the item. |
2086 | */ |
2087 | if (item != NULL && (cell != NULL || item->selected)) |
2088 | { |
2089 | if (cell != NULL) |
2090 | { |
2091 | if (gtk_cell_renderer_is_activatable (cell)) |
2092 | cursor_cell = cell; |
2093 | } |
2094 | |
2095 | gtk_icon_view_scroll_to_item (icon_view, item); |
2096 | |
2097 | if (icon_view->priv->selection_mode == GTK_SELECTION_NONE) |
2098 | { |
2099 | _gtk_icon_view_set_cursor_item (icon_view, item, cursor_cell); |
2100 | } |
2101 | else if (icon_view->priv->selection_mode == GTK_SELECTION_MULTIPLE && |
2102 | (state & extend_mod_mask)) |
2103 | { |
2104 | gtk_icon_view_unselect_all_internal (icon_view); |
2105 | |
2106 | _gtk_icon_view_set_cursor_item (icon_view, item, cursor_cell); |
2107 | if (!icon_view->priv->anchor_item) |
2108 | icon_view->priv->anchor_item = item; |
2109 | else |
2110 | gtk_icon_view_select_all_between (icon_view, |
2111 | anchor: icon_view->priv->anchor_item, |
2112 | cursor: item); |
2113 | dirty = TRUE; |
2114 | } |
2115 | else |
2116 | { |
2117 | if ((icon_view->priv->selection_mode == GTK_SELECTION_MULTIPLE || |
2118 | ((icon_view->priv->selection_mode == GTK_SELECTION_SINGLE) && item->selected)) && |
2119 | (state & modify_mod_mask)) |
2120 | { |
2121 | item->selected = !item->selected; |
2122 | gtk_icon_view_queue_draw_item (icon_view, item); |
2123 | dirty = TRUE; |
2124 | } |
2125 | else |
2126 | { |
2127 | gtk_icon_view_unselect_all_internal (icon_view); |
2128 | |
2129 | item->selected = TRUE; |
2130 | gtk_icon_view_queue_draw_item (icon_view, item); |
2131 | dirty = TRUE; |
2132 | } |
2133 | _gtk_icon_view_set_cursor_item (icon_view, item, cursor_cell); |
2134 | icon_view->priv->anchor_item = item; |
2135 | } |
2136 | |
2137 | /* Save press to possibly begin a drag */ |
2138 | if (icon_view->priv->pressed_button < 0) |
2139 | { |
2140 | icon_view->priv->pressed_button = button; |
2141 | icon_view->priv->press_start_x = x; |
2142 | icon_view->priv->press_start_y = y; |
2143 | } |
2144 | |
2145 | icon_view->priv->last_single_clicked = item; |
2146 | |
2147 | /* cancel the current editing, if it exists */ |
2148 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
2149 | |
2150 | if (cell != NULL && gtk_cell_renderer_is_activatable (cell)) |
2151 | { |
2152 | GtkCellAreaContext *context; |
2153 | |
2154 | context = g_ptr_array_index (icon_view->priv->row_contexts, item->row); |
2155 | |
2156 | _gtk_icon_view_set_cell_data (icon_view, item); |
2157 | gtk_cell_area_activate (area: icon_view->priv->cell_area, context, |
2158 | GTK_WIDGET (icon_view), |
2159 | cell_area: &item->cell_area, flags: 0, FALSE); |
2160 | } |
2161 | } |
2162 | else |
2163 | { |
2164 | if (icon_view->priv->selection_mode != GTK_SELECTION_BROWSE && |
2165 | !(state & modify_mod_mask)) |
2166 | { |
2167 | dirty = gtk_icon_view_unselect_all_internal (icon_view); |
2168 | } |
2169 | |
2170 | if (icon_view->priv->selection_mode == GTK_SELECTION_MULTIPLE) |
2171 | gtk_icon_view_start_rubberbanding (icon_view, |
2172 | device: gtk_gesture_get_device (GTK_GESTURE (gesture)), |
2173 | x, y); |
2174 | } |
2175 | |
2176 | /* don't draw keyboard focus around a clicked-on item */ |
2177 | icon_view->priv->draw_focus = FALSE; |
2178 | } |
2179 | |
2180 | if (!icon_view->priv->activate_on_single_click |
2181 | && button == GDK_BUTTON_PRIMARY |
2182 | && n_press == 2) |
2183 | { |
2184 | item = _gtk_icon_view_get_item_at_widget_coords (icon_view, |
2185 | x, y, |
2186 | FALSE, |
2187 | NULL); |
2188 | |
2189 | if (item && item == icon_view->priv->last_single_clicked) |
2190 | { |
2191 | GtkTreePath *path; |
2192 | |
2193 | path = gtk_tree_path_new_from_indices (first_index: item->index, -1); |
2194 | gtk_icon_view_item_activated (icon_view, path); |
2195 | gtk_tree_path_free (path); |
2196 | } |
2197 | |
2198 | icon_view->priv->last_single_clicked = NULL; |
2199 | icon_view->priv->pressed_button = -1; |
2200 | } |
2201 | |
2202 | if (dirty) |
2203 | g_signal_emit (instance: icon_view, signal_id: icon_view_signals[SELECTION_CHANGED], detail: 0); |
2204 | } |
2205 | |
2206 | static gboolean |
2207 | button_event_modifies_selection (GdkEvent *event) |
2208 | { |
2209 | guint state = gdk_event_get_modifier_state (event); |
2210 | |
2211 | return (state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0; |
2212 | } |
2213 | |
2214 | static void |
2215 | gtk_icon_view_button_release (GtkGestureClick *gesture, |
2216 | int n_press, |
2217 | double x, |
2218 | double y, |
2219 | gpointer user_data) |
2220 | { |
2221 | GtkIconView *icon_view = user_data; |
2222 | int button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); |
2223 | GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); |
2224 | GdkEvent *event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence); |
2225 | |
2226 | if (icon_view->priv->pressed_button == button) |
2227 | icon_view->priv->pressed_button = -1; |
2228 | |
2229 | gtk_icon_view_stop_rubberbanding (icon_view); |
2230 | |
2231 | remove_scroll_timeout (icon_view); |
2232 | |
2233 | if (button == GDK_BUTTON_PRIMARY |
2234 | && icon_view->priv->activate_on_single_click |
2235 | && !button_event_modifies_selection (event) |
2236 | && icon_view->priv->last_single_clicked != NULL) |
2237 | { |
2238 | GtkIconViewItem *item; |
2239 | |
2240 | item = _gtk_icon_view_get_item_at_widget_coords (icon_view, |
2241 | x, y, |
2242 | FALSE, |
2243 | NULL); |
2244 | if (item == icon_view->priv->last_single_clicked) |
2245 | { |
2246 | GtkTreePath *path; |
2247 | path = gtk_tree_path_new_from_indices (first_index: item->index, -1); |
2248 | gtk_icon_view_item_activated (icon_view, path); |
2249 | gtk_tree_path_free (path); |
2250 | } |
2251 | |
2252 | icon_view->priv->last_single_clicked = NULL; |
2253 | } |
2254 | } |
2255 | |
2256 | static gboolean |
2257 | gtk_icon_view_key_pressed (GtkEventControllerKey *controller, |
2258 | guint keyval, |
2259 | guint keycode, |
2260 | GdkModifierType state, |
2261 | GtkWidget *widget) |
2262 | { |
2263 | GtkIconView *icon_view = GTK_ICON_VIEW (widget); |
2264 | |
2265 | if (icon_view->priv->doing_rubberband) |
2266 | { |
2267 | if (keyval == GDK_KEY_Escape) |
2268 | gtk_icon_view_stop_rubberbanding (icon_view); |
2269 | |
2270 | return TRUE; |
2271 | } |
2272 | |
2273 | return FALSE; |
2274 | } |
2275 | |
2276 | static void |
2277 | gtk_icon_view_update_rubberband (GtkIconView *icon_view) |
2278 | { |
2279 | int x, y; |
2280 | |
2281 | x = MAX (icon_view->priv->mouse_x, 0); |
2282 | y = MAX (icon_view->priv->mouse_y, 0); |
2283 | |
2284 | icon_view->priv->rubberband_x2 = x + gtk_adjustment_get_value (adjustment: icon_view->priv->hadjustment); |
2285 | icon_view->priv->rubberband_y2 = y + gtk_adjustment_get_value (adjustment: icon_view->priv->vadjustment); |
2286 | |
2287 | gtk_icon_view_update_rubberband_selection (icon_view); |
2288 | gtk_widget_queue_draw (GTK_WIDGET (icon_view)); |
2289 | } |
2290 | |
2291 | static void |
2292 | gtk_icon_view_start_rubberbanding (GtkIconView *icon_view, |
2293 | GdkDevice *device, |
2294 | int x, |
2295 | int y) |
2296 | { |
2297 | GtkIconViewPrivate *priv = icon_view->priv; |
2298 | GList *items; |
2299 | GtkCssNode *widget_node; |
2300 | |
2301 | if (priv->rubberband_device) |
2302 | return; |
2303 | |
2304 | for (items = priv->items; items; items = items->next) |
2305 | { |
2306 | GtkIconViewItem *item = items->data; |
2307 | |
2308 | item->selected_before_rubberbanding = item->selected; |
2309 | } |
2310 | |
2311 | priv->rubberband_x1 = x + gtk_adjustment_get_value (adjustment: priv->hadjustment); |
2312 | priv->rubberband_y1 = y + gtk_adjustment_get_value (adjustment: priv->vadjustment); |
2313 | priv->rubberband_x2 = priv->rubberband_x1; |
2314 | priv->rubberband_y2 = priv->rubberband_y1; |
2315 | |
2316 | priv->doing_rubberband = TRUE; |
2317 | priv->rubberband_device = device; |
2318 | |
2319 | widget_node = gtk_widget_get_css_node (GTK_WIDGET (icon_view)); |
2320 | priv->rubberband_node = gtk_css_node_new (); |
2321 | gtk_css_node_set_name (cssnode: priv->rubberband_node, name: g_quark_from_static_string (string: "rubberband" )); |
2322 | gtk_css_node_set_parent (cssnode: priv->rubberband_node, parent: widget_node); |
2323 | gtk_css_node_set_state (cssnode: priv->rubberband_node, state_flags: gtk_css_node_get_state (cssnode: widget_node)); |
2324 | g_object_unref (object: priv->rubberband_node); |
2325 | } |
2326 | |
2327 | static void |
2328 | gtk_icon_view_stop_rubberbanding (GtkIconView *icon_view) |
2329 | { |
2330 | GtkIconViewPrivate *priv = icon_view->priv; |
2331 | |
2332 | if (!priv->doing_rubberband) |
2333 | return; |
2334 | |
2335 | priv->doing_rubberband = FALSE; |
2336 | priv->rubberband_device = NULL; |
2337 | gtk_css_node_set_parent (cssnode: priv->rubberband_node, NULL); |
2338 | priv->rubberband_node = NULL; |
2339 | |
2340 | gtk_widget_queue_draw (GTK_WIDGET (icon_view)); |
2341 | } |
2342 | |
2343 | static void |
2344 | gtk_icon_view_update_rubberband_selection (GtkIconView *icon_view) |
2345 | { |
2346 | GList *items; |
2347 | int x, y, width, height; |
2348 | gboolean dirty = FALSE; |
2349 | |
2350 | x = MIN (icon_view->priv->rubberband_x1, |
2351 | icon_view->priv->rubberband_x2); |
2352 | y = MIN (icon_view->priv->rubberband_y1, |
2353 | icon_view->priv->rubberband_y2); |
2354 | width = ABS (icon_view->priv->rubberband_x1 - |
2355 | icon_view->priv->rubberband_x2); |
2356 | height = ABS (icon_view->priv->rubberband_y1 - |
2357 | icon_view->priv->rubberband_y2); |
2358 | |
2359 | for (items = icon_view->priv->items; items; items = items->next) |
2360 | { |
2361 | GtkIconViewItem *item = items->data; |
2362 | gboolean is_in; |
2363 | gboolean selected; |
2364 | |
2365 | is_in = gtk_icon_view_item_hit_test (icon_view, item, |
2366 | x, y, width, height); |
2367 | |
2368 | selected = is_in ^ item->selected_before_rubberbanding; |
2369 | |
2370 | if (item->selected != selected) |
2371 | { |
2372 | item->selected = selected; |
2373 | dirty = TRUE; |
2374 | gtk_icon_view_queue_draw_item (icon_view, item); |
2375 | } |
2376 | } |
2377 | |
2378 | if (dirty) |
2379 | g_signal_emit (instance: icon_view, signal_id: icon_view_signals[SELECTION_CHANGED], detail: 0); |
2380 | } |
2381 | |
2382 | |
2383 | typedef struct { |
2384 | GdkRectangle hit_rect; |
2385 | gboolean hit; |
2386 | } HitTestData; |
2387 | |
2388 | static gboolean |
2389 | hit_test (GtkCellRenderer *renderer, |
2390 | const GdkRectangle *cell_area, |
2391 | const GdkRectangle *cell_background, |
2392 | HitTestData *data) |
2393 | { |
2394 | if (MIN (data->hit_rect.x + data->hit_rect.width, cell_area->x + cell_area->width) - |
2395 | MAX (data->hit_rect.x, cell_area->x) > 0 && |
2396 | MIN (data->hit_rect.y + data->hit_rect.height, cell_area->y + cell_area->height) - |
2397 | MAX (data->hit_rect.y, cell_area->y) > 0) |
2398 | data->hit = TRUE; |
2399 | |
2400 | return (data->hit != FALSE); |
2401 | } |
2402 | |
2403 | static gboolean |
2404 | gtk_icon_view_item_hit_test (GtkIconView *icon_view, |
2405 | GtkIconViewItem *item, |
2406 | int x, |
2407 | int y, |
2408 | int width, |
2409 | int height) |
2410 | { |
2411 | HitTestData data = { { x, y, width, height }, FALSE }; |
2412 | GtkCellAreaContext *context; |
2413 | GdkRectangle *item_area = &item->cell_area; |
2414 | |
2415 | if (MIN (x + width, item_area->x + item_area->width) - MAX (x, item_area->x) <= 0 || |
2416 | MIN (y + height, item_area->y + item_area->height) - MAX (y, item_area->y) <= 0) |
2417 | return FALSE; |
2418 | |
2419 | context = g_ptr_array_index (icon_view->priv->row_contexts, item->row); |
2420 | |
2421 | _gtk_icon_view_set_cell_data (icon_view, item); |
2422 | gtk_cell_area_foreach_alloc (area: icon_view->priv->cell_area, context, |
2423 | GTK_WIDGET (icon_view), |
2424 | cell_area: item_area, background_area: item_area, |
2425 | callback: (GtkCellAllocCallback)hit_test, callback_data: &data); |
2426 | |
2427 | return data.hit; |
2428 | } |
2429 | |
2430 | static gboolean |
2431 | gtk_icon_view_unselect_all_internal (GtkIconView *icon_view) |
2432 | { |
2433 | gboolean dirty = FALSE; |
2434 | GList *items; |
2435 | |
2436 | if (icon_view->priv->selection_mode == GTK_SELECTION_NONE) |
2437 | return FALSE; |
2438 | |
2439 | for (items = icon_view->priv->items; items; items = items->next) |
2440 | { |
2441 | GtkIconViewItem *item = items->data; |
2442 | |
2443 | if (item->selected) |
2444 | { |
2445 | item->selected = FALSE; |
2446 | dirty = TRUE; |
2447 | gtk_icon_view_queue_draw_item (icon_view, item); |
2448 | } |
2449 | } |
2450 | |
2451 | return dirty; |
2452 | } |
2453 | |
2454 | |
2455 | /* GtkIconView signals */ |
2456 | static void |
2457 | gtk_icon_view_real_select_all (GtkIconView *icon_view) |
2458 | { |
2459 | gtk_icon_view_select_all (icon_view); |
2460 | } |
2461 | |
2462 | static void |
2463 | gtk_icon_view_real_unselect_all (GtkIconView *icon_view) |
2464 | { |
2465 | gtk_icon_view_unselect_all (icon_view); |
2466 | } |
2467 | |
2468 | static void |
2469 | gtk_icon_view_real_select_cursor_item (GtkIconView *icon_view) |
2470 | { |
2471 | gtk_icon_view_unselect_all (icon_view); |
2472 | |
2473 | if (icon_view->priv->cursor_item != NULL) |
2474 | _gtk_icon_view_select_item (icon_view, item: icon_view->priv->cursor_item); |
2475 | } |
2476 | |
2477 | static gboolean |
2478 | gtk_icon_view_real_activate_cursor_item (GtkIconView *icon_view) |
2479 | { |
2480 | GtkTreePath *path; |
2481 | GtkCellAreaContext *context; |
2482 | |
2483 | if (!icon_view->priv->cursor_item) |
2484 | return FALSE; |
2485 | |
2486 | context = g_ptr_array_index (icon_view->priv->row_contexts, icon_view->priv->cursor_item->row); |
2487 | |
2488 | _gtk_icon_view_set_cell_data (icon_view, item: icon_view->priv->cursor_item); |
2489 | gtk_cell_area_activate (area: icon_view->priv->cell_area, context, |
2490 | GTK_WIDGET (icon_view), |
2491 | cell_area: &icon_view->priv->cursor_item->cell_area, |
2492 | flags: 0, |
2493 | FALSE); |
2494 | |
2495 | path = gtk_tree_path_new_from_indices (first_index: icon_view->priv->cursor_item->index, -1); |
2496 | gtk_icon_view_item_activated (icon_view, path); |
2497 | gtk_tree_path_free (path); |
2498 | |
2499 | return TRUE; |
2500 | } |
2501 | |
2502 | static void |
2503 | gtk_icon_view_real_toggle_cursor_item (GtkIconView *icon_view) |
2504 | { |
2505 | if (!icon_view->priv->cursor_item) |
2506 | return; |
2507 | |
2508 | switch (icon_view->priv->selection_mode) |
2509 | { |
2510 | case GTK_SELECTION_NONE: |
2511 | default: |
2512 | break; |
2513 | case GTK_SELECTION_BROWSE: |
2514 | _gtk_icon_view_select_item (icon_view, item: icon_view->priv->cursor_item); |
2515 | break; |
2516 | case GTK_SELECTION_SINGLE: |
2517 | if (icon_view->priv->cursor_item->selected) |
2518 | _gtk_icon_view_unselect_item (icon_view, item: icon_view->priv->cursor_item); |
2519 | else |
2520 | _gtk_icon_view_select_item (icon_view, item: icon_view->priv->cursor_item); |
2521 | break; |
2522 | case GTK_SELECTION_MULTIPLE: |
2523 | icon_view->priv->cursor_item->selected = !icon_view->priv->cursor_item->selected; |
2524 | g_signal_emit (instance: icon_view, signal_id: icon_view_signals[SELECTION_CHANGED], detail: 0); |
2525 | |
2526 | gtk_icon_view_queue_draw_item (icon_view, item: icon_view->priv->cursor_item); |
2527 | break; |
2528 | } |
2529 | } |
2530 | |
2531 | static void |
2532 | gtk_icon_view_set_hadjustment_values (GtkIconView *icon_view) |
2533 | { |
2534 | int width; |
2535 | GtkAdjustment *adj = icon_view->priv->hadjustment; |
2536 | double old_page_size; |
2537 | double old_upper; |
2538 | double old_value; |
2539 | double new_value; |
2540 | double new_upper; |
2541 | |
2542 | width = gtk_widget_get_width (GTK_WIDGET (icon_view)); |
2543 | |
2544 | old_value = gtk_adjustment_get_value (adjustment: adj); |
2545 | old_upper = gtk_adjustment_get_upper (adjustment: adj); |
2546 | old_page_size = gtk_adjustment_get_page_size (adjustment: adj); |
2547 | new_upper = MAX (width, icon_view->priv->width); |
2548 | |
2549 | if (gtk_widget_get_direction (GTK_WIDGET (icon_view)) == GTK_TEXT_DIR_RTL) |
2550 | { |
2551 | /* Make sure no scrolling occurs for RTL locales also (if possible) */ |
2552 | /* Quick explanation: |
2553 | * In LTR locales, leftmost portion of visible rectangle should stay |
2554 | * fixed, which means left edge of scrollbar thumb should remain fixed |
2555 | * and thus adjustment's value should stay the same. |
2556 | * |
2557 | * In RTL locales, we want to keep rightmost portion of visible |
2558 | * rectangle fixed. This means right edge of thumb should remain fixed. |
2559 | * In this case, upper - value - page_size should remain constant. |
2560 | */ |
2561 | new_value = (new_upper - width) - |
2562 | (old_upper - old_value - old_page_size); |
2563 | new_value = CLAMP (new_value, 0, new_upper - width); |
2564 | } |
2565 | else |
2566 | new_value = CLAMP (old_value, 0, new_upper - width); |
2567 | |
2568 | gtk_adjustment_configure (adjustment: adj, |
2569 | value: new_value, |
2570 | lower: 0.0, |
2571 | upper: new_upper, |
2572 | step_increment: width * 0.1, |
2573 | page_increment: width * 0.9, |
2574 | page_size: width); |
2575 | } |
2576 | |
2577 | static void |
2578 | gtk_icon_view_set_vadjustment_values (GtkIconView *icon_view) |
2579 | { |
2580 | int height; |
2581 | GtkAdjustment *adj = icon_view->priv->vadjustment; |
2582 | |
2583 | height = gtk_widget_get_height (GTK_WIDGET (icon_view)); |
2584 | |
2585 | gtk_adjustment_configure (adjustment: adj, |
2586 | value: gtk_adjustment_get_value (adjustment: adj), |
2587 | lower: 0.0, |
2588 | MAX (height, icon_view->priv->height), |
2589 | step_increment: height * 0.1, |
2590 | page_increment: height * 0.9, |
2591 | page_size: height); |
2592 | } |
2593 | |
2594 | static void |
2595 | gtk_icon_view_set_hadjustment (GtkIconView *icon_view, |
2596 | GtkAdjustment *adjustment) |
2597 | { |
2598 | GtkIconViewPrivate *priv = icon_view->priv; |
2599 | |
2600 | if (adjustment && priv->hadjustment == adjustment) |
2601 | return; |
2602 | |
2603 | if (priv->hadjustment != NULL) |
2604 | { |
2605 | g_signal_handlers_disconnect_matched (instance: priv->hadjustment, |
2606 | mask: G_SIGNAL_MATCH_DATA, |
2607 | signal_id: 0, detail: 0, NULL, NULL, data: icon_view); |
2608 | g_object_unref (object: priv->hadjustment); |
2609 | } |
2610 | |
2611 | if (!adjustment) |
2612 | adjustment = gtk_adjustment_new (value: 0.0, lower: 0.0, upper: 0.0, |
2613 | step_increment: 0.0, page_increment: 0.0, page_size: 0.0); |
2614 | |
2615 | g_signal_connect (adjustment, "value-changed" , |
2616 | G_CALLBACK (gtk_icon_view_adjustment_changed), icon_view); |
2617 | priv->hadjustment = g_object_ref_sink (adjustment); |
2618 | gtk_icon_view_set_hadjustment_values (icon_view); |
2619 | |
2620 | g_object_notify (G_OBJECT (icon_view), property_name: "hadjustment" ); |
2621 | } |
2622 | |
2623 | static void |
2624 | gtk_icon_view_set_vadjustment (GtkIconView *icon_view, |
2625 | GtkAdjustment *adjustment) |
2626 | { |
2627 | GtkIconViewPrivate *priv = icon_view->priv; |
2628 | |
2629 | if (adjustment && priv->vadjustment == adjustment) |
2630 | return; |
2631 | |
2632 | if (priv->vadjustment != NULL) |
2633 | { |
2634 | g_signal_handlers_disconnect_matched (instance: priv->vadjustment, |
2635 | mask: G_SIGNAL_MATCH_DATA, |
2636 | signal_id: 0, detail: 0, NULL, NULL, data: icon_view); |
2637 | g_object_unref (object: priv->vadjustment); |
2638 | } |
2639 | |
2640 | if (!adjustment) |
2641 | adjustment = gtk_adjustment_new (value: 0.0, lower: 0.0, upper: 0.0, |
2642 | step_increment: 0.0, page_increment: 0.0, page_size: 0.0); |
2643 | |
2644 | g_signal_connect (adjustment, "value-changed" , |
2645 | G_CALLBACK (gtk_icon_view_adjustment_changed), icon_view); |
2646 | priv->vadjustment = g_object_ref_sink (adjustment); |
2647 | gtk_icon_view_set_vadjustment_values (icon_view); |
2648 | |
2649 | g_object_notify (G_OBJECT (icon_view), property_name: "vadjustment" ); |
2650 | } |
2651 | |
2652 | static void |
2653 | gtk_icon_view_adjustment_changed (GtkAdjustment *adjustment, |
2654 | GtkIconView *icon_view) |
2655 | { |
2656 | GtkWidget *widget = GTK_WIDGET (icon_view); |
2657 | |
2658 | if (gtk_widget_get_realized (widget)) |
2659 | { |
2660 | if (icon_view->priv->doing_rubberband) |
2661 | gtk_icon_view_update_rubberband (icon_view); |
2662 | } |
2663 | |
2664 | gtk_widget_queue_draw (GTK_WIDGET (icon_view)); |
2665 | } |
2666 | |
2667 | static int |
2668 | compare_sizes (gconstpointer p1, |
2669 | gconstpointer p2, |
2670 | gpointer unused) |
2671 | { |
2672 | return GPOINTER_TO_INT (((const GtkRequestedSize *) p1)->data) |
2673 | - GPOINTER_TO_INT (((const GtkRequestedSize *) p2)->data); |
2674 | } |
2675 | |
2676 | static void |
2677 | gtk_icon_view_layout (GtkIconView *icon_view) |
2678 | { |
2679 | GtkIconViewPrivate *priv = icon_view->priv; |
2680 | GtkWidget *widget = GTK_WIDGET (icon_view); |
2681 | GList *items; |
2682 | int item_width = 0; /* this doesn't include item_padding */ |
2683 | int n_columns, n_rows, n_items; |
2684 | int col, row; |
2685 | GtkRequestedSize *sizes; |
2686 | gboolean rtl; |
2687 | int width, height; |
2688 | |
2689 | if (gtk_icon_view_is_empty (icon_view)) |
2690 | return; |
2691 | |
2692 | rtl = gtk_widget_get_direction (GTK_WIDGET (icon_view)) == GTK_TEXT_DIR_RTL; |
2693 | n_items = gtk_icon_view_get_n_items (icon_view); |
2694 | |
2695 | width = gtk_widget_get_width (widget); |
2696 | height = gtk_widget_get_height (widget); |
2697 | |
2698 | gtk_icon_view_compute_n_items_for_size (icon_view, |
2699 | orientation: GTK_ORIENTATION_HORIZONTAL, |
2700 | size: width, |
2701 | NULL, NULL, |
2702 | max_items: &n_columns, max_item_size: &item_width); |
2703 | n_rows = (n_items + n_columns - 1) / n_columns; |
2704 | |
2705 | priv->width = n_columns * (item_width + 2 * priv->item_padding + priv->column_spacing) - priv->column_spacing; |
2706 | priv->width += 2 * priv->margin; |
2707 | priv->width = MAX (priv->width, width); |
2708 | |
2709 | /* Clear the per row contexts */ |
2710 | g_ptr_array_set_size (array: icon_view->priv->row_contexts, length: 0); |
2711 | |
2712 | gtk_cell_area_context_reset (context: priv->cell_area_context); |
2713 | /* because layouting is complicated. We designed an API |
2714 | * that is O(N²) and nonsensical. |
2715 | * And we're proud of it. */ |
2716 | for (items = priv->items; items; items = items->next) |
2717 | { |
2718 | _gtk_icon_view_set_cell_data (icon_view, item: items->data); |
2719 | gtk_cell_area_get_preferred_width (area: priv->cell_area, |
2720 | context: priv->cell_area_context, |
2721 | widget, |
2722 | NULL, NULL); |
2723 | } |
2724 | |
2725 | sizes = g_newa (GtkRequestedSize, n_rows); |
2726 | items = priv->items; |
2727 | priv->height = priv->margin; |
2728 | |
2729 | /* Collect the heights for all rows */ |
2730 | for (row = 0; row < n_rows; row++) |
2731 | { |
2732 | GtkCellAreaContext *context = gtk_cell_area_copy_context (area: priv->cell_area, context: priv->cell_area_context); |
2733 | g_ptr_array_add (array: priv->row_contexts, data: context); |
2734 | |
2735 | for (col = 0; col < n_columns && items; col++, items = items->next) |
2736 | { |
2737 | GtkIconViewItem *item = items->data; |
2738 | |
2739 | _gtk_icon_view_set_cell_data (icon_view, item); |
2740 | gtk_cell_area_get_preferred_height_for_width (area: priv->cell_area, |
2741 | context, |
2742 | widget, |
2743 | width: item_width, |
2744 | NULL, NULL); |
2745 | } |
2746 | |
2747 | sizes[row].data = GINT_TO_POINTER (row); |
2748 | gtk_cell_area_context_get_preferred_height_for_width (context, |
2749 | width: item_width, |
2750 | minimum_height: &sizes[row].minimum_size, |
2751 | natural_height: &sizes[row].natural_size); |
2752 | priv->height += sizes[row].minimum_size + 2 * priv->item_padding + priv->row_spacing; |
2753 | } |
2754 | |
2755 | priv->height -= priv->row_spacing; |
2756 | priv->height += priv->margin; |
2757 | priv->height = MIN (priv->height, height); |
2758 | |
2759 | gtk_distribute_natural_allocation (extra_space: height - priv->height, |
2760 | n_requested_sizes: n_rows, |
2761 | sizes); |
2762 | |
2763 | /* Actually allocate the rows */ |
2764 | g_qsort_with_data (pbase: sizes, total_elems: n_rows, size: sizeof (GtkRequestedSize), compare_func: compare_sizes, NULL); |
2765 | |
2766 | items = priv->items; |
2767 | priv->height = priv->margin; |
2768 | |
2769 | for (row = 0; row < n_rows; row++) |
2770 | { |
2771 | GtkCellAreaContext *context = g_ptr_array_index (priv->row_contexts, row); |
2772 | gtk_cell_area_context_allocate (context, width: item_width, height: sizes[row].minimum_size); |
2773 | |
2774 | priv->height += priv->item_padding; |
2775 | |
2776 | for (col = 0; col < n_columns && items; col++, items = items->next) |
2777 | { |
2778 | GtkIconViewItem *item = items->data; |
2779 | |
2780 | item->cell_area.x = priv->margin + (col * 2 + 1) * priv->item_padding + col * (priv->column_spacing + item_width); |
2781 | item->cell_area.width = item_width; |
2782 | item->cell_area.y = priv->height; |
2783 | item->cell_area.height = sizes[row].minimum_size; |
2784 | item->row = row; |
2785 | item->col = col; |
2786 | if (rtl) |
2787 | { |
2788 | item->cell_area.x = priv->width - item_width - item->cell_area.x; |
2789 | item->col = n_columns - 1 - col; |
2790 | } |
2791 | } |
2792 | |
2793 | priv->height += sizes[row].minimum_size + priv->item_padding + priv->row_spacing; |
2794 | } |
2795 | |
2796 | priv->height -= priv->row_spacing; |
2797 | priv->height += priv->margin; |
2798 | priv->height = MAX (priv->height, height); |
2799 | } |
2800 | |
2801 | static void |
2802 | gtk_icon_view_invalidate_sizes (GtkIconView *icon_view) |
2803 | { |
2804 | /* Clear all item sizes */ |
2805 | g_list_foreach (list: icon_view->priv->items, |
2806 | func: (GFunc)gtk_icon_view_item_invalidate_size, NULL); |
2807 | |
2808 | /* Re-layout the items */ |
2809 | gtk_widget_queue_resize (GTK_WIDGET (icon_view)); |
2810 | } |
2811 | |
2812 | static void |
2813 | gtk_icon_view_item_invalidate_size (GtkIconViewItem *item) |
2814 | { |
2815 | item->cell_area.width = -1; |
2816 | item->cell_area.height = -1; |
2817 | } |
2818 | |
2819 | static void |
2820 | gtk_icon_view_snapshot_item (GtkIconView *icon_view, |
2821 | GtkSnapshot *snapshot, |
2822 | GtkIconViewItem *item, |
2823 | int x, |
2824 | int y, |
2825 | gboolean draw_focus) |
2826 | { |
2827 | GdkRectangle cell_area; |
2828 | GtkStateFlags state = 0; |
2829 | GtkCellRendererState flags = 0; |
2830 | GtkStyleContext *style_context; |
2831 | GtkWidget *widget = GTK_WIDGET (icon_view); |
2832 | GtkIconViewPrivate *priv = icon_view->priv; |
2833 | GtkCellAreaContext *context; |
2834 | |
2835 | if (priv->model == NULL || item->cell_area.width <= 0 || item->cell_area.height <= 0) |
2836 | return; |
2837 | |
2838 | _gtk_icon_view_set_cell_data (icon_view, item); |
2839 | |
2840 | style_context = gtk_widget_get_style_context (widget); |
2841 | state = gtk_widget_get_state_flags (widget); |
2842 | |
2843 | gtk_style_context_save (context: style_context); |
2844 | gtk_style_context_add_class (context: style_context, class_name: "cell" ); |
2845 | |
2846 | state &= ~(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_PRELIGHT); |
2847 | |
2848 | if ((state & GTK_STATE_FLAG_FOCUSED) && |
2849 | item == icon_view->priv->cursor_item) |
2850 | flags |= GTK_CELL_RENDERER_FOCUSED; |
2851 | |
2852 | if (item->selected) |
2853 | { |
2854 | state |= GTK_STATE_FLAG_SELECTED; |
2855 | flags |= GTK_CELL_RENDERER_SELECTED; |
2856 | } |
2857 | |
2858 | if (item == priv->last_prelight) |
2859 | { |
2860 | state |= GTK_STATE_FLAG_PRELIGHT; |
2861 | flags |= GTK_CELL_RENDERER_PRELIT; |
2862 | } |
2863 | |
2864 | gtk_style_context_set_state (context: style_context, flags: state); |
2865 | |
2866 | gtk_snapshot_render_background (snapshot, context: style_context, |
2867 | x: x - priv->item_padding, |
2868 | y: y - priv->item_padding, |
2869 | width: item->cell_area.width + priv->item_padding * 2, |
2870 | height: item->cell_area.height + priv->item_padding * 2); |
2871 | gtk_snapshot_render_frame (snapshot, context: style_context, |
2872 | x: x - priv->item_padding, |
2873 | y: y - priv->item_padding, |
2874 | width: item->cell_area.width + priv->item_padding * 2, |
2875 | height: item->cell_area.height + priv->item_padding * 2); |
2876 | |
2877 | cell_area.x = x; |
2878 | cell_area.y = y; |
2879 | cell_area.width = item->cell_area.width; |
2880 | cell_area.height = item->cell_area.height; |
2881 | |
2882 | context = g_ptr_array_index (priv->row_contexts, item->row); |
2883 | gtk_cell_area_snapshot (area: priv->cell_area, context, |
2884 | widget, snapshot, background_area: &cell_area, cell_area: &cell_area, flags, |
2885 | paint_focus: draw_focus); |
2886 | |
2887 | gtk_style_context_restore (context: style_context); |
2888 | } |
2889 | |
2890 | static void |
2891 | gtk_icon_view_snapshot_rubberband (GtkIconView *icon_view, |
2892 | GtkSnapshot *snapshot) |
2893 | { |
2894 | GtkIconViewPrivate *priv = icon_view->priv; |
2895 | GtkStyleContext *context; |
2896 | GdkRectangle rect; |
2897 | |
2898 | rect.x = MIN (priv->rubberband_x1, priv->rubberband_x2); |
2899 | rect.y = MIN (priv->rubberband_y1, priv->rubberband_y2); |
2900 | rect.width = ABS (priv->rubberband_x1 - priv->rubberband_x2) + 1; |
2901 | rect.height = ABS (priv->rubberband_y1 - priv->rubberband_y2) + 1; |
2902 | |
2903 | context = gtk_widget_get_style_context (GTK_WIDGET (icon_view)); |
2904 | |
2905 | gtk_style_context_save_to_node (context, node: priv->rubberband_node); |
2906 | |
2907 | gtk_snapshot_render_background (snapshot, context, |
2908 | x: rect.x, y: rect.y, |
2909 | width: rect.width, height: rect.height); |
2910 | gtk_snapshot_render_frame (snapshot, context, |
2911 | x: rect.x, y: rect.y, |
2912 | width: rect.width, height: rect.height); |
2913 | |
2914 | gtk_style_context_restore (context); |
2915 | } |
2916 | |
2917 | static void |
2918 | gtk_icon_view_queue_draw_path (GtkIconView *icon_view, |
2919 | GtkTreePath *path) |
2920 | { |
2921 | GList *l; |
2922 | int index; |
2923 | |
2924 | index = gtk_tree_path_get_indices (path)[0]; |
2925 | |
2926 | for (l = icon_view->priv->items; l; l = l->next) |
2927 | { |
2928 | GtkIconViewItem *item = l->data; |
2929 | |
2930 | if (item->index == index) |
2931 | { |
2932 | gtk_icon_view_queue_draw_item (icon_view, item); |
2933 | break; |
2934 | } |
2935 | } |
2936 | } |
2937 | |
2938 | static void |
2939 | gtk_icon_view_queue_draw_item (GtkIconView *icon_view, |
2940 | GtkIconViewItem *item) |
2941 | { |
2942 | gtk_widget_queue_draw (GTK_WIDGET (icon_view)); |
2943 | } |
2944 | |
2945 | void |
2946 | _gtk_icon_view_set_cursor_item (GtkIconView *icon_view, |
2947 | GtkIconViewItem *item, |
2948 | GtkCellRenderer *cursor_cell) |
2949 | { |
2950 | /* When hitting this path from keynav, the focus cell is already set, |
2951 | * but we still need to queue the draw here (in the case that the focus |
2952 | * cell changes but not the cursor item). |
2953 | */ |
2954 | gtk_icon_view_queue_draw_item (icon_view, item); |
2955 | |
2956 | if (icon_view->priv->cursor_item == item && |
2957 | (cursor_cell == NULL || cursor_cell == gtk_cell_area_get_focus_cell (area: icon_view->priv->cell_area))) |
2958 | return; |
2959 | |
2960 | if (icon_view->priv->cursor_item != NULL) |
2961 | gtk_icon_view_queue_draw_item (icon_view, item: icon_view->priv->cursor_item); |
2962 | |
2963 | icon_view->priv->cursor_item = item; |
2964 | |
2965 | if (cursor_cell) |
2966 | gtk_cell_area_set_focus_cell (area: icon_view->priv->cell_area, renderer: cursor_cell); |
2967 | else |
2968 | { |
2969 | /* Make sure there is a cell in focus initially */ |
2970 | if (!gtk_cell_area_get_focus_cell (area: icon_view->priv->cell_area)) |
2971 | gtk_cell_area_focus (area: icon_view->priv->cell_area, direction: GTK_DIR_TAB_FORWARD); |
2972 | } |
2973 | } |
2974 | |
2975 | |
2976 | static GtkIconViewItem * |
2977 | gtk_icon_view_item_new (void) |
2978 | { |
2979 | GtkIconViewItem *item; |
2980 | |
2981 | item = g_slice_new0 (GtkIconViewItem); |
2982 | |
2983 | item->cell_area.width = -1; |
2984 | item->cell_area.height = -1; |
2985 | |
2986 | return item; |
2987 | } |
2988 | |
2989 | static void |
2990 | gtk_icon_view_item_free (GtkIconViewItem *item) |
2991 | { |
2992 | g_return_if_fail (item != NULL); |
2993 | |
2994 | g_slice_free (GtkIconViewItem, item); |
2995 | } |
2996 | |
2997 | GtkIconViewItem * |
2998 | _gtk_icon_view_get_item_at_coords (GtkIconView *icon_view, |
2999 | int x, |
3000 | int y, |
3001 | gboolean only_in_cell, |
3002 | GtkCellRenderer **cell_at_pos) |
3003 | { |
3004 | GList *items; |
3005 | |
3006 | if (cell_at_pos) |
3007 | *cell_at_pos = NULL; |
3008 | |
3009 | for (items = icon_view->priv->items; items; items = items->next) |
3010 | { |
3011 | GtkIconViewItem *item = items->data; |
3012 | GdkRectangle *item_area = &item->cell_area; |
3013 | |
3014 | if (x >= item_area->x - icon_view->priv->column_spacing/2 && |
3015 | x <= item_area->x + item_area->width + icon_view->priv->column_spacing/2 && |
3016 | y >= item_area->y - icon_view->priv->row_spacing/2 && |
3017 | y <= item_area->y + item_area->height + icon_view->priv->row_spacing/2) |
3018 | { |
3019 | if (only_in_cell || cell_at_pos) |
3020 | { |
3021 | GtkCellRenderer *cell = NULL; |
3022 | GtkCellAreaContext *context; |
3023 | |
3024 | context = g_ptr_array_index (icon_view->priv->row_contexts, item->row); |
3025 | _gtk_icon_view_set_cell_data (icon_view, item); |
3026 | |
3027 | if (x >= item_area->x && x <= item_area->x + item_area->width && |
3028 | y >= item_area->y && y <= item_area->y + item_area->height) |
3029 | cell = gtk_cell_area_get_cell_at_position (area: icon_view->priv->cell_area, context, |
3030 | GTK_WIDGET (icon_view), |
3031 | cell_area: item_area, |
3032 | x, y, NULL); |
3033 | |
3034 | if (cell_at_pos) |
3035 | *cell_at_pos = cell; |
3036 | |
3037 | if (only_in_cell) |
3038 | return cell != NULL ? item : NULL; |
3039 | else |
3040 | return item; |
3041 | } |
3042 | return item; |
3043 | } |
3044 | } |
3045 | return NULL; |
3046 | } |
3047 | |
3048 | void |
3049 | _gtk_icon_view_select_item (GtkIconView *icon_view, |
3050 | GtkIconViewItem *item) |
3051 | { |
3052 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
3053 | g_return_if_fail (item != NULL); |
3054 | |
3055 | if (item->selected) |
3056 | return; |
3057 | |
3058 | if (icon_view->priv->selection_mode == GTK_SELECTION_NONE) |
3059 | return; |
3060 | else if (icon_view->priv->selection_mode != GTK_SELECTION_MULTIPLE) |
3061 | gtk_icon_view_unselect_all_internal (icon_view); |
3062 | |
3063 | item->selected = TRUE; |
3064 | |
3065 | g_signal_emit (instance: icon_view, signal_id: icon_view_signals[SELECTION_CHANGED], detail: 0); |
3066 | |
3067 | gtk_icon_view_queue_draw_item (icon_view, item); |
3068 | } |
3069 | |
3070 | |
3071 | void |
3072 | _gtk_icon_view_unselect_item (GtkIconView *icon_view, |
3073 | GtkIconViewItem *item) |
3074 | { |
3075 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
3076 | g_return_if_fail (item != NULL); |
3077 | |
3078 | if (!item->selected) |
3079 | return; |
3080 | |
3081 | if (icon_view->priv->selection_mode == GTK_SELECTION_NONE || |
3082 | icon_view->priv->selection_mode == GTK_SELECTION_BROWSE) |
3083 | return; |
3084 | |
3085 | item->selected = FALSE; |
3086 | |
3087 | g_signal_emit (instance: icon_view, signal_id: icon_view_signals[SELECTION_CHANGED], detail: 0); |
3088 | |
3089 | gtk_icon_view_queue_draw_item (icon_view, item); |
3090 | } |
3091 | |
3092 | static void |
3093 | verify_items (GtkIconView *icon_view) |
3094 | { |
3095 | GList *items; |
3096 | int i = 0; |
3097 | |
3098 | for (items = icon_view->priv->items; items; items = items->next) |
3099 | { |
3100 | GtkIconViewItem *item = items->data; |
3101 | |
3102 | if (item->index != i) |
3103 | g_error ("List item does not match its index: " |
3104 | "item index %d and list index %d\n" , item->index, i); |
3105 | |
3106 | i++; |
3107 | } |
3108 | } |
3109 | |
3110 | static void |
3111 | gtk_icon_view_row_changed (GtkTreeModel *model, |
3112 | GtkTreePath *path, |
3113 | GtkTreeIter *iter, |
3114 | gpointer data) |
3115 | { |
3116 | GtkIconView *icon_view = GTK_ICON_VIEW (data); |
3117 | |
3118 | /* ignore changes in branches */ |
3119 | if (gtk_tree_path_get_depth (path) > 1) |
3120 | return; |
3121 | |
3122 | /* An icon view subclass might add it's own model and populate |
3123 | * things at init() time instead of waiting for the constructor() |
3124 | * to be called |
3125 | */ |
3126 | if (icon_view->priv->cell_area) |
3127 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
3128 | |
3129 | /* Here we can use a "grow-only" strategy for optimization |
3130 | * and only invalidate a single item and queue a relayout |
3131 | * instead of invalidating the whole thing. |
3132 | * |
3133 | * For now GtkIconView still can't deal with huge models |
3134 | * so just invalidate the whole thing when the model |
3135 | * changes. |
3136 | */ |
3137 | gtk_icon_view_invalidate_sizes (icon_view); |
3138 | |
3139 | verify_items (icon_view); |
3140 | } |
3141 | |
3142 | static void |
3143 | gtk_icon_view_row_inserted (GtkTreeModel *model, |
3144 | GtkTreePath *path, |
3145 | GtkTreeIter *iter, |
3146 | gpointer data) |
3147 | { |
3148 | GtkIconView *icon_view = GTK_ICON_VIEW (data); |
3149 | int index; |
3150 | GtkIconViewItem *item; |
3151 | GList *list; |
3152 | |
3153 | /* ignore changes in branches */ |
3154 | if (gtk_tree_path_get_depth (path) > 1) |
3155 | return; |
3156 | |
3157 | gtk_tree_model_ref_node (tree_model: model, iter); |
3158 | |
3159 | index = gtk_tree_path_get_indices(path)[0]; |
3160 | |
3161 | item = gtk_icon_view_item_new (); |
3162 | |
3163 | item->index = index; |
3164 | |
3165 | /* FIXME: We can be more efficient here, |
3166 | we can store a tail pointer and use that when |
3167 | appending (which is a rather common operation) |
3168 | */ |
3169 | icon_view->priv->items = g_list_insert (list: icon_view->priv->items, |
3170 | data: item, position: index); |
3171 | |
3172 | list = g_list_nth (list: icon_view->priv->items, n: index + 1); |
3173 | for (; list; list = list->next) |
3174 | { |
3175 | item = list->data; |
3176 | |
3177 | item->index++; |
3178 | } |
3179 | |
3180 | verify_items (icon_view); |
3181 | |
3182 | gtk_widget_queue_resize (GTK_WIDGET (icon_view)); |
3183 | } |
3184 | |
3185 | static void |
3186 | gtk_icon_view_row_deleted (GtkTreeModel *model, |
3187 | GtkTreePath *path, |
3188 | gpointer data) |
3189 | { |
3190 | GtkIconView *icon_view = GTK_ICON_VIEW (data); |
3191 | int index; |
3192 | GtkIconViewItem *item; |
3193 | GList *list, *next; |
3194 | gboolean emit = FALSE; |
3195 | GtkTreeIter iter; |
3196 | |
3197 | /* ignore changes in branches */ |
3198 | if (gtk_tree_path_get_depth (path) > 1) |
3199 | return; |
3200 | |
3201 | if (gtk_tree_model_get_iter (tree_model: model, iter: &iter, path)) |
3202 | gtk_tree_model_unref_node (tree_model: model, iter: &iter); |
3203 | |
3204 | index = gtk_tree_path_get_indices(path)[0]; |
3205 | |
3206 | list = g_list_nth (list: icon_view->priv->items, n: index); |
3207 | item = list->data; |
3208 | |
3209 | if (icon_view->priv->cell_area) |
3210 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
3211 | |
3212 | if (item == icon_view->priv->anchor_item) |
3213 | icon_view->priv->anchor_item = NULL; |
3214 | |
3215 | if (item == icon_view->priv->cursor_item) |
3216 | icon_view->priv->cursor_item = NULL; |
3217 | |
3218 | if (item == icon_view->priv->last_prelight) |
3219 | icon_view->priv->last_prelight = NULL; |
3220 | |
3221 | if (item->selected) |
3222 | emit = TRUE; |
3223 | |
3224 | gtk_icon_view_item_free (item); |
3225 | |
3226 | for (next = list->next; next; next = next->next) |
3227 | { |
3228 | item = next->data; |
3229 | |
3230 | item->index--; |
3231 | } |
3232 | |
3233 | icon_view->priv->items = g_list_delete_link (list: icon_view->priv->items, link_: list); |
3234 | |
3235 | verify_items (icon_view); |
3236 | |
3237 | gtk_widget_queue_resize (GTK_WIDGET (icon_view)); |
3238 | |
3239 | if (emit) |
3240 | g_signal_emit (instance: icon_view, signal_id: icon_view_signals[SELECTION_CHANGED], detail: 0); |
3241 | } |
3242 | |
3243 | static void |
3244 | gtk_icon_view_rows_reordered (GtkTreeModel *model, |
3245 | GtkTreePath *parent, |
3246 | GtkTreeIter *iter, |
3247 | int *new_order, |
3248 | gpointer data) |
3249 | { |
3250 | GtkIconView *icon_view = GTK_ICON_VIEW (data); |
3251 | int i; |
3252 | int length; |
3253 | GList *items = NULL, *list; |
3254 | GtkIconViewItem **item_array; |
3255 | int *order; |
3256 | |
3257 | /* ignore changes in branches */ |
3258 | if (iter != NULL) |
3259 | return; |
3260 | |
3261 | if (icon_view->priv->cell_area) |
3262 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
3263 | |
3264 | length = gtk_tree_model_iter_n_children (tree_model: model, NULL); |
3265 | |
3266 | order = g_new (int, length); |
3267 | for (i = 0; i < length; i++) |
3268 | order [new_order[i]] = i; |
3269 | |
3270 | item_array = g_new (GtkIconViewItem *, length); |
3271 | for (i = 0, list = icon_view->priv->items; list != NULL; list = list->next, i++) |
3272 | item_array[order[i]] = list->data; |
3273 | g_free (mem: order); |
3274 | |
3275 | for (i = length - 1; i >= 0; i--) |
3276 | { |
3277 | item_array[i]->index = i; |
3278 | items = g_list_prepend (list: items, data: item_array[i]); |
3279 | } |
3280 | |
3281 | g_free (mem: item_array); |
3282 | g_list_free (list: icon_view->priv->items); |
3283 | icon_view->priv->items = items; |
3284 | |
3285 | gtk_widget_queue_resize (GTK_WIDGET (icon_view)); |
3286 | |
3287 | verify_items (icon_view); |
3288 | } |
3289 | |
3290 | static void |
3291 | gtk_icon_view_build_items (GtkIconView *icon_view) |
3292 | { |
3293 | GtkTreeIter iter; |
3294 | int i; |
3295 | GList *items = NULL; |
3296 | |
3297 | if (!gtk_tree_model_get_iter_first (tree_model: icon_view->priv->model, |
3298 | iter: &iter)) |
3299 | return; |
3300 | |
3301 | i = 0; |
3302 | |
3303 | do |
3304 | { |
3305 | GtkIconViewItem *item = gtk_icon_view_item_new (); |
3306 | |
3307 | item->index = i; |
3308 | |
3309 | i++; |
3310 | |
3311 | items = g_list_prepend (list: items, data: item); |
3312 | |
3313 | } while (gtk_tree_model_iter_next (tree_model: icon_view->priv->model, iter: &iter)); |
3314 | |
3315 | icon_view->priv->items = g_list_reverse (list: items); |
3316 | } |
3317 | |
3318 | static void |
3319 | gtk_icon_view_add_move_binding (GtkWidgetClass *widget_class, |
3320 | guint keyval, |
3321 | guint modmask, |
3322 | GtkMovementStep step, |
3323 | int count) |
3324 | { |
3325 | |
3326 | gtk_widget_class_add_binding_signal (widget_class, |
3327 | keyval, mods: modmask, |
3328 | I_("move-cursor" ), |
3329 | format_string: "(iibb)" , step, count, FALSE, FALSE); |
3330 | |
3331 | gtk_widget_class_add_binding_signal (widget_class, |
3332 | keyval, mods: GDK_SHIFT_MASK, |
3333 | signal: "move-cursor" , |
3334 | format_string: "(iibb)" , step, count, TRUE, FALSE); |
3335 | |
3336 | if ((modmask & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) |
3337 | return; |
3338 | |
3339 | gtk_widget_class_add_binding_signal (widget_class, |
3340 | keyval, mods: GDK_CONTROL_MASK | GDK_SHIFT_MASK, |
3341 | signal: "move-cursor" , |
3342 | format_string: "(iibb)" , step, count, TRUE, TRUE); |
3343 | |
3344 | gtk_widget_class_add_binding_signal (widget_class, |
3345 | keyval, mods: GDK_CONTROL_MASK, |
3346 | signal: "move-cursor" , |
3347 | format_string: "(iibb)" , step, count, FALSE, TRUE); |
3348 | } |
3349 | |
3350 | static gboolean |
3351 | gtk_icon_view_real_move_cursor (GtkIconView *icon_view, |
3352 | GtkMovementStep step, |
3353 | int count, |
3354 | gboolean extend, |
3355 | gboolean modify) |
3356 | { |
3357 | g_return_val_if_fail (GTK_ICON_VIEW (icon_view), FALSE); |
3358 | g_return_val_if_fail (step == GTK_MOVEMENT_LOGICAL_POSITIONS || |
3359 | step == GTK_MOVEMENT_VISUAL_POSITIONS || |
3360 | step == GTK_MOVEMENT_DISPLAY_LINES || |
3361 | step == GTK_MOVEMENT_PAGES || |
3362 | step == GTK_MOVEMENT_BUFFER_ENDS, FALSE); |
3363 | |
3364 | if (!gtk_widget_has_focus (GTK_WIDGET (icon_view))) |
3365 | return FALSE; |
3366 | |
3367 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, FALSE); |
3368 | gtk_widget_grab_focus (GTK_WIDGET (icon_view)); |
3369 | |
3370 | icon_view->priv->extend_selection_pressed = extend; |
3371 | icon_view->priv->modify_selection_pressed = modify; |
3372 | |
3373 | switch (step) |
3374 | { |
3375 | case GTK_MOVEMENT_LOGICAL_POSITIONS: |
3376 | case GTK_MOVEMENT_VISUAL_POSITIONS: |
3377 | gtk_icon_view_move_cursor_left_right (icon_view, count); |
3378 | break; |
3379 | case GTK_MOVEMENT_DISPLAY_LINES: |
3380 | gtk_icon_view_move_cursor_up_down (icon_view, count); |
3381 | break; |
3382 | case GTK_MOVEMENT_PAGES: |
3383 | gtk_icon_view_move_cursor_page_up_down (icon_view, count); |
3384 | break; |
3385 | case GTK_MOVEMENT_BUFFER_ENDS: |
3386 | gtk_icon_view_move_cursor_start_end (icon_view, count); |
3387 | break; |
3388 | case GTK_MOVEMENT_WORDS: |
3389 | case GTK_MOVEMENT_DISPLAY_LINE_ENDS: |
3390 | case GTK_MOVEMENT_PARAGRAPHS: |
3391 | case GTK_MOVEMENT_PARAGRAPH_ENDS: |
3392 | case GTK_MOVEMENT_HORIZONTAL_PAGES: |
3393 | default: |
3394 | g_assert_not_reached (); |
3395 | break; |
3396 | } |
3397 | |
3398 | icon_view->priv->modify_selection_pressed = FALSE; |
3399 | icon_view->priv->extend_selection_pressed = FALSE; |
3400 | |
3401 | icon_view->priv->draw_focus = TRUE; |
3402 | |
3403 | return TRUE; |
3404 | } |
3405 | |
3406 | static GtkIconViewItem * |
3407 | find_item (GtkIconView *icon_view, |
3408 | GtkIconViewItem *current, |
3409 | int row_ofs, |
3410 | int col_ofs) |
3411 | { |
3412 | int row, col; |
3413 | GList *items; |
3414 | GtkIconViewItem *item; |
3415 | |
3416 | /* FIXME: this could be more efficient |
3417 | */ |
3418 | row = current->row + row_ofs; |
3419 | col = current->col + col_ofs; |
3420 | |
3421 | for (items = icon_view->priv->items; items; items = items->next) |
3422 | { |
3423 | item = items->data; |
3424 | if (item->row == row && item->col == col) |
3425 | return item; |
3426 | } |
3427 | |
3428 | return NULL; |
3429 | } |
3430 | |
3431 | static GtkIconViewItem * |
3432 | find_item_page_up_down (GtkIconView *icon_view, |
3433 | GtkIconViewItem *current, |
3434 | int count) |
3435 | { |
3436 | GList *item, *next; |
3437 | int y, col; |
3438 | |
3439 | col = current->col; |
3440 | y = current->cell_area.y + count * gtk_adjustment_get_page_size (adjustment: icon_view->priv->vadjustment); |
3441 | |
3442 | item = g_list_find (list: icon_view->priv->items, data: current); |
3443 | if (count > 0) |
3444 | { |
3445 | while (item) |
3446 | { |
3447 | for (next = item->next; next; next = next->next) |
3448 | { |
3449 | if (((GtkIconViewItem *)next->data)->col == col) |
3450 | break; |
3451 | } |
3452 | if (!next || ((GtkIconViewItem *)next->data)->cell_area.y > y) |
3453 | break; |
3454 | |
3455 | item = next; |
3456 | } |
3457 | } |
3458 | else |
3459 | { |
3460 | while (item) |
3461 | { |
3462 | for (next = item->prev; next; next = next->prev) |
3463 | { |
3464 | if (((GtkIconViewItem *)next->data)->col == col) |
3465 | break; |
3466 | } |
3467 | if (!next || ((GtkIconViewItem *)next->data)->cell_area.y < y) |
3468 | break; |
3469 | |
3470 | item = next; |
3471 | } |
3472 | } |
3473 | |
3474 | if (item) |
3475 | return item->data; |
3476 | |
3477 | return NULL; |
3478 | } |
3479 | |
3480 | static gboolean |
3481 | gtk_icon_view_select_all_between (GtkIconView *icon_view, |
3482 | GtkIconViewItem *anchor, |
3483 | GtkIconViewItem *cursor) |
3484 | { |
3485 | GList *items; |
3486 | GtkIconViewItem *item; |
3487 | int row1, row2, col1, col2; |
3488 | gboolean dirty = FALSE; |
3489 | |
3490 | if (anchor->row < cursor->row) |
3491 | { |
3492 | row1 = anchor->row; |
3493 | row2 = cursor->row; |
3494 | } |
3495 | else |
3496 | { |
3497 | row1 = cursor->row; |
3498 | row2 = anchor->row; |
3499 | } |
3500 | |
3501 | if (anchor->col < cursor->col) |
3502 | { |
3503 | col1 = anchor->col; |
3504 | col2 = cursor->col; |
3505 | } |
3506 | else |
3507 | { |
3508 | col1 = cursor->col; |
3509 | col2 = anchor->col; |
3510 | } |
3511 | |
3512 | for (items = icon_view->priv->items; items; items = items->next) |
3513 | { |
3514 | item = items->data; |
3515 | |
3516 | if (row1 <= item->row && item->row <= row2 && |
3517 | col1 <= item->col && item->col <= col2) |
3518 | { |
3519 | if (!item->selected) |
3520 | { |
3521 | dirty = TRUE; |
3522 | item->selected = TRUE; |
3523 | } |
3524 | gtk_icon_view_queue_draw_item (icon_view, item); |
3525 | } |
3526 | } |
3527 | |
3528 | return dirty; |
3529 | } |
3530 | |
3531 | static void |
3532 | gtk_icon_view_move_cursor_up_down (GtkIconView *icon_view, |
3533 | int count) |
3534 | { |
3535 | GtkIconViewItem *item; |
3536 | GtkCellRenderer *cell = NULL; |
3537 | gboolean dirty = FALSE; |
3538 | int step; |
3539 | GtkDirectionType direction; |
3540 | |
3541 | if (!gtk_widget_has_focus (GTK_WIDGET (icon_view))) |
3542 | return; |
3543 | |
3544 | direction = count < 0 ? GTK_DIR_UP : GTK_DIR_DOWN; |
3545 | |
3546 | if (!icon_view->priv->cursor_item) |
3547 | { |
3548 | GList *list; |
3549 | |
3550 | if (count > 0) |
3551 | list = icon_view->priv->items; |
3552 | else |
3553 | list = g_list_last (list: icon_view->priv->items); |
3554 | |
3555 | if (list) |
3556 | { |
3557 | item = list->data; |
3558 | |
3559 | /* Give focus to the first cell initially */ |
3560 | _gtk_icon_view_set_cell_data (icon_view, item); |
3561 | gtk_cell_area_focus (area: icon_view->priv->cell_area, direction); |
3562 | } |
3563 | else |
3564 | { |
3565 | item = NULL; |
3566 | } |
3567 | } |
3568 | else |
3569 | { |
3570 | item = icon_view->priv->cursor_item; |
3571 | step = count > 0 ? 1 : -1; |
3572 | |
3573 | /* Save the current focus cell in case we hit the edge */ |
3574 | cell = gtk_cell_area_get_focus_cell (area: icon_view->priv->cell_area); |
3575 | |
3576 | while (item) |
3577 | { |
3578 | _gtk_icon_view_set_cell_data (icon_view, item); |
3579 | |
3580 | if (gtk_cell_area_focus (area: icon_view->priv->cell_area, direction)) |
3581 | break; |
3582 | |
3583 | item = find_item (icon_view, current: item, row_ofs: step, col_ofs: 0); |
3584 | } |
3585 | } |
3586 | |
3587 | if (!item) |
3588 | { |
3589 | if (!gtk_widget_keynav_failed (GTK_WIDGET (icon_view), direction)) |
3590 | { |
3591 | GtkWidget *toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (icon_view))); |
3592 | if (toplevel) |
3593 | gtk_widget_child_focus (widget: toplevel, |
3594 | direction: direction == GTK_DIR_UP ? |
3595 | GTK_DIR_TAB_BACKWARD : |
3596 | GTK_DIR_TAB_FORWARD); |
3597 | |
3598 | } |
3599 | |
3600 | gtk_cell_area_set_focus_cell (area: icon_view->priv->cell_area, renderer: cell); |
3601 | return; |
3602 | } |
3603 | |
3604 | if (icon_view->priv->modify_selection_pressed || |
3605 | !icon_view->priv->extend_selection_pressed || |
3606 | !icon_view->priv->anchor_item || |
3607 | icon_view->priv->selection_mode != GTK_SELECTION_MULTIPLE) |
3608 | icon_view->priv->anchor_item = item; |
3609 | |
3610 | cell = gtk_cell_area_get_focus_cell (area: icon_view->priv->cell_area); |
3611 | _gtk_icon_view_set_cursor_item (icon_view, item, cursor_cell: cell); |
3612 | |
3613 | if (!icon_view->priv->modify_selection_pressed && |
3614 | icon_view->priv->selection_mode != GTK_SELECTION_NONE) |
3615 | { |
3616 | dirty = gtk_icon_view_unselect_all_internal (icon_view); |
3617 | dirty = gtk_icon_view_select_all_between (icon_view, |
3618 | anchor: icon_view->priv->anchor_item, |
3619 | cursor: item) || dirty; |
3620 | } |
3621 | |
3622 | gtk_icon_view_scroll_to_item (icon_view, item); |
3623 | |
3624 | if (dirty) |
3625 | g_signal_emit (instance: icon_view, signal_id: icon_view_signals[SELECTION_CHANGED], detail: 0); |
3626 | } |
3627 | |
3628 | static void |
3629 | gtk_icon_view_move_cursor_page_up_down (GtkIconView *icon_view, |
3630 | int count) |
3631 | { |
3632 | GtkIconViewItem *item; |
3633 | gboolean dirty = FALSE; |
3634 | |
3635 | if (!gtk_widget_has_focus (GTK_WIDGET (icon_view))) |
3636 | return; |
3637 | |
3638 | if (!icon_view->priv->cursor_item) |
3639 | { |
3640 | GList *list; |
3641 | |
3642 | if (count > 0) |
3643 | list = icon_view->priv->items; |
3644 | else |
3645 | list = g_list_last (list: icon_view->priv->items); |
3646 | |
3647 | item = list ? list->data : NULL; |
3648 | } |
3649 | else |
3650 | item = find_item_page_up_down (icon_view, |
3651 | current: icon_view->priv->cursor_item, |
3652 | count); |
3653 | |
3654 | if (item == icon_view->priv->cursor_item) |
3655 | gtk_widget_error_bell (GTK_WIDGET (icon_view)); |
3656 | |
3657 | if (!item) |
3658 | return; |
3659 | |
3660 | if (icon_view->priv->modify_selection_pressed || |
3661 | !icon_view->priv->extend_selection_pressed || |
3662 | !icon_view->priv->anchor_item || |
3663 | icon_view->priv->selection_mode != GTK_SELECTION_MULTIPLE) |
3664 | icon_view->priv->anchor_item = item; |
3665 | |
3666 | _gtk_icon_view_set_cursor_item (icon_view, item, NULL); |
3667 | |
3668 | if (!icon_view->priv->modify_selection_pressed && |
3669 | icon_view->priv->selection_mode != GTK_SELECTION_NONE) |
3670 | { |
3671 | dirty = gtk_icon_view_unselect_all_internal (icon_view); |
3672 | dirty = gtk_icon_view_select_all_between (icon_view, |
3673 | anchor: icon_view->priv->anchor_item, |
3674 | cursor: item) || dirty; |
3675 | } |
3676 | |
3677 | gtk_icon_view_scroll_to_item (icon_view, item); |
3678 | |
3679 | if (dirty) |
3680 | g_signal_emit (instance: icon_view, signal_id: icon_view_signals[SELECTION_CHANGED], detail: 0); |
3681 | } |
3682 | |
3683 | static void |
3684 | gtk_icon_view_move_cursor_left_right (GtkIconView *icon_view, |
3685 | int count) |
3686 | { |
3687 | GtkIconViewItem *item; |
3688 | GtkCellRenderer *cell = NULL; |
3689 | gboolean dirty = FALSE; |
3690 | int step; |
3691 | GtkDirectionType direction; |
3692 | |
3693 | if (!gtk_widget_has_focus (GTK_WIDGET (icon_view))) |
3694 | return; |
3695 | |
3696 | direction = count < 0 ? GTK_DIR_LEFT : GTK_DIR_RIGHT; |
3697 | |
3698 | if (!icon_view->priv->cursor_item) |
3699 | { |
3700 | GList *list; |
3701 | |
3702 | if (count > 0) |
3703 | list = icon_view->priv->items; |
3704 | else |
3705 | list = g_list_last (list: icon_view->priv->items); |
3706 | |
3707 | if (list) |
3708 | { |
3709 | item = list->data; |
3710 | |
3711 | /* Give focus to the first cell initially */ |
3712 | _gtk_icon_view_set_cell_data (icon_view, item); |
3713 | gtk_cell_area_focus (area: icon_view->priv->cell_area, direction); |
3714 | } |
3715 | else |
3716 | { |
3717 | item = NULL; |
3718 | } |
3719 | } |
3720 | else |
3721 | { |
3722 | item = icon_view->priv->cursor_item; |
3723 | step = count > 0 ? 1 : -1; |
3724 | |
3725 | /* Save the current focus cell in case we hit the edge */ |
3726 | cell = gtk_cell_area_get_focus_cell (area: icon_view->priv->cell_area); |
3727 | |
3728 | while (item) |
3729 | { |
3730 | _gtk_icon_view_set_cell_data (icon_view, item); |
3731 | |
3732 | if (gtk_cell_area_focus (area: icon_view->priv->cell_area, direction)) |
3733 | break; |
3734 | |
3735 | item = find_item (icon_view, current: item, row_ofs: 0, col_ofs: step); |
3736 | } |
3737 | } |
3738 | |
3739 | if (!item) |
3740 | { |
3741 | if (!gtk_widget_keynav_failed (GTK_WIDGET (icon_view), direction)) |
3742 | { |
3743 | GtkWidget *toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (icon_view))); |
3744 | if (toplevel) |
3745 | gtk_widget_child_focus (widget: toplevel, |
3746 | direction: direction == GTK_DIR_LEFT ? |
3747 | GTK_DIR_TAB_BACKWARD : |
3748 | GTK_DIR_TAB_FORWARD); |
3749 | |
3750 | } |
3751 | |
3752 | gtk_cell_area_set_focus_cell (area: icon_view->priv->cell_area, renderer: cell); |
3753 | return; |
3754 | } |
3755 | |
3756 | if (icon_view->priv->modify_selection_pressed || |
3757 | !icon_view->priv->extend_selection_pressed || |
3758 | !icon_view->priv->anchor_item || |
3759 | icon_view->priv->selection_mode != GTK_SELECTION_MULTIPLE) |
3760 | icon_view->priv->anchor_item = item; |
3761 | |
3762 | cell = gtk_cell_area_get_focus_cell (area: icon_view->priv->cell_area); |
3763 | _gtk_icon_view_set_cursor_item (icon_view, item, cursor_cell: cell); |
3764 | |
3765 | if (!icon_view->priv->modify_selection_pressed && |
3766 | icon_view->priv->selection_mode != GTK_SELECTION_NONE) |
3767 | { |
3768 | dirty = gtk_icon_view_unselect_all_internal (icon_view); |
3769 | dirty = gtk_icon_view_select_all_between (icon_view, |
3770 | anchor: icon_view->priv->anchor_item, |
3771 | cursor: item) || dirty; |
3772 | } |
3773 | |
3774 | gtk_icon_view_scroll_to_item (icon_view, item); |
3775 | |
3776 | if (dirty) |
3777 | g_signal_emit (instance: icon_view, signal_id: icon_view_signals[SELECTION_CHANGED], detail: 0); |
3778 | } |
3779 | |
3780 | static void |
3781 | gtk_icon_view_move_cursor_start_end (GtkIconView *icon_view, |
3782 | int count) |
3783 | { |
3784 | GtkIconViewItem *item; |
3785 | GList *list; |
3786 | gboolean dirty = FALSE; |
3787 | |
3788 | if (!gtk_widget_has_focus (GTK_WIDGET (icon_view))) |
3789 | return; |
3790 | |
3791 | if (count < 0) |
3792 | list = icon_view->priv->items; |
3793 | else |
3794 | list = g_list_last (list: icon_view->priv->items); |
3795 | |
3796 | item = list ? list->data : NULL; |
3797 | |
3798 | if (item == icon_view->priv->cursor_item) |
3799 | gtk_widget_error_bell (GTK_WIDGET (icon_view)); |
3800 | |
3801 | if (!item) |
3802 | return; |
3803 | |
3804 | if (icon_view->priv->modify_selection_pressed || |
3805 | !icon_view->priv->extend_selection_pressed || |
3806 | !icon_view->priv->anchor_item || |
3807 | icon_view->priv->selection_mode != GTK_SELECTION_MULTIPLE) |
3808 | icon_view->priv->anchor_item = item; |
3809 | |
3810 | _gtk_icon_view_set_cursor_item (icon_view, item, NULL); |
3811 | |
3812 | if (!icon_view->priv->modify_selection_pressed && |
3813 | icon_view->priv->selection_mode != GTK_SELECTION_NONE) |
3814 | { |
3815 | dirty = gtk_icon_view_unselect_all_internal (icon_view); |
3816 | dirty = gtk_icon_view_select_all_between (icon_view, |
3817 | anchor: icon_view->priv->anchor_item, |
3818 | cursor: item) || dirty; |
3819 | } |
3820 | |
3821 | gtk_icon_view_scroll_to_item (icon_view, item); |
3822 | |
3823 | if (dirty) |
3824 | g_signal_emit (instance: icon_view, signal_id: icon_view_signals[SELECTION_CHANGED], detail: 0); |
3825 | } |
3826 | |
3827 | /** |
3828 | * gtk_icon_view_scroll_to_path: |
3829 | * @icon_view: A `GtkIconView` |
3830 | * @path: The path of the item to move to. |
3831 | * @use_align: whether to use alignment arguments, or %FALSE. |
3832 | * @row_align: The vertical alignment of the item specified by @path. |
3833 | * @col_align: The horizontal alignment of the item specified by @path. |
3834 | * |
3835 | * Moves the alignments of @icon_view to the position specified by @path. |
3836 | * @row_align determines where the row is placed, and @col_align determines |
3837 | * where @column is placed. Both are expected to be between 0.0 and 1.0. |
3838 | * 0.0 means left/top alignment, 1.0 means right/bottom alignment, 0.5 means |
3839 | * center. |
3840 | * |
3841 | * If @use_align is %FALSE, then the alignment arguments are ignored, and the |
3842 | * tree does the minimum amount of work to scroll the item onto the screen. |
3843 | * This means that the item will be scrolled to the edge closest to its current |
3844 | * position. If the item is currently visible on the screen, nothing is done. |
3845 | * |
3846 | * This function only works if the model is set, and @path is a valid row on |
3847 | * the model. If the model changes before the @icon_view is realized, the |
3848 | * centered path will be modified to reflect this change. |
3849 | **/ |
3850 | void |
3851 | gtk_icon_view_scroll_to_path (GtkIconView *icon_view, |
3852 | GtkTreePath *path, |
3853 | gboolean use_align, |
3854 | float row_align, |
3855 | float col_align) |
3856 | { |
3857 | GtkIconViewItem *item = NULL; |
3858 | GtkWidget *widget; |
3859 | |
3860 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
3861 | g_return_if_fail (path != NULL); |
3862 | g_return_if_fail (row_align >= 0.0 && row_align <= 1.0); |
3863 | g_return_if_fail (col_align >= 0.0 && col_align <= 1.0); |
3864 | |
3865 | widget = GTK_WIDGET (icon_view); |
3866 | |
3867 | if (gtk_tree_path_get_depth (path) > 0) |
3868 | item = g_list_nth_data (list: icon_view->priv->items, |
3869 | n: gtk_tree_path_get_indices(path)[0]); |
3870 | |
3871 | if (!item || item->cell_area.width < 0 || |
3872 | !gtk_widget_get_realized (widget)) |
3873 | { |
3874 | if (icon_view->priv->scroll_to_path) |
3875 | gtk_tree_row_reference_free (reference: icon_view->priv->scroll_to_path); |
3876 | |
3877 | icon_view->priv->scroll_to_path = NULL; |
3878 | |
3879 | if (path) |
3880 | icon_view->priv->scroll_to_path = gtk_tree_row_reference_new_proxy (G_OBJECT (icon_view), model: icon_view->priv->model, path); |
3881 | |
3882 | icon_view->priv->scroll_to_use_align = use_align; |
3883 | icon_view->priv->scroll_to_row_align = row_align; |
3884 | icon_view->priv->scroll_to_col_align = col_align; |
3885 | |
3886 | return; |
3887 | } |
3888 | |
3889 | if (use_align) |
3890 | { |
3891 | int width, height; |
3892 | int x, y; |
3893 | float offset; |
3894 | GdkRectangle item_area = |
3895 | { |
3896 | item->cell_area.x - icon_view->priv->item_padding, |
3897 | item->cell_area.y - icon_view->priv->item_padding, |
3898 | item->cell_area.width + icon_view->priv->item_padding * 2, |
3899 | item->cell_area.height + icon_view->priv->item_padding * 2 |
3900 | }; |
3901 | |
3902 | x =0; |
3903 | y =0; |
3904 | |
3905 | width = gtk_widget_get_width (widget); |
3906 | height = gtk_widget_get_height (widget); |
3907 | |
3908 | offset = y + item_area.y - row_align * (height - item_area.height); |
3909 | |
3910 | gtk_adjustment_set_value (adjustment: icon_view->priv->vadjustment, |
3911 | value: gtk_adjustment_get_value (adjustment: icon_view->priv->vadjustment) + offset); |
3912 | |
3913 | offset = x + item_area.x - col_align * (width - item_area.width); |
3914 | |
3915 | gtk_adjustment_set_value (adjustment: icon_view->priv->hadjustment, |
3916 | value: gtk_adjustment_get_value (adjustment: icon_view->priv->hadjustment) + offset); |
3917 | } |
3918 | else |
3919 | gtk_icon_view_scroll_to_item (icon_view, item); |
3920 | } |
3921 | |
3922 | |
3923 | static void |
3924 | gtk_icon_view_scroll_to_item (GtkIconView *icon_view, |
3925 | GtkIconViewItem *item) |
3926 | { |
3927 | GtkIconViewPrivate *priv = icon_view->priv; |
3928 | GtkWidget *widget = GTK_WIDGET (icon_view); |
3929 | GtkAdjustment *hadj, *vadj; |
3930 | int widget_width, widget_height; |
3931 | int x, y; |
3932 | GdkRectangle item_area; |
3933 | |
3934 | item_area.x = item->cell_area.x - priv->item_padding; |
3935 | item_area.y = item->cell_area.y - priv->item_padding; |
3936 | item_area.width = item->cell_area.width + priv->item_padding * 2; |
3937 | item_area.height = item->cell_area.height + priv->item_padding * 2; |
3938 | |
3939 | widget_width = gtk_widget_get_width (widget); |
3940 | widget_height = gtk_widget_get_height (widget); |
3941 | |
3942 | hadj = icon_view->priv->hadjustment; |
3943 | vadj = icon_view->priv->vadjustment; |
3944 | |
3945 | x = - gtk_adjustment_get_value (adjustment: hadj); |
3946 | y = - gtk_adjustment_get_value (adjustment: vadj); |
3947 | |
3948 | if (y + item_area.y < 0) |
3949 | gtk_adjustment_animate_to_value (adjustment: vadj, |
3950 | value: gtk_adjustment_get_value (adjustment: vadj) |
3951 | + y + item_area.y); |
3952 | else if (y + item_area.y + item_area.height > widget_height) |
3953 | gtk_adjustment_animate_to_value (adjustment: vadj, |
3954 | value: gtk_adjustment_get_value (adjustment: vadj) |
3955 | + y + item_area.y + item_area.height - widget_height); |
3956 | |
3957 | if (x + item_area.x < 0) |
3958 | gtk_adjustment_animate_to_value (adjustment: hadj, |
3959 | value: gtk_adjustment_get_value (adjustment: hadj) |
3960 | + x + item_area.x); |
3961 | else if (x + item_area.x + item_area.width > widget_width) |
3962 | gtk_adjustment_animate_to_value (adjustment: hadj, |
3963 | value: gtk_adjustment_get_value (adjustment: hadj) |
3964 | + x + item_area.x + item_area.width - widget_width); |
3965 | } |
3966 | |
3967 | /* GtkCellLayout implementation */ |
3968 | |
3969 | static void |
3970 | gtk_icon_view_ensure_cell_area (GtkIconView *icon_view, |
3971 | GtkCellArea *cell_area) |
3972 | { |
3973 | GtkIconViewPrivate *priv = icon_view->priv; |
3974 | |
3975 | if (priv->cell_area) |
3976 | return; |
3977 | |
3978 | if (cell_area) |
3979 | priv->cell_area = cell_area; |
3980 | else |
3981 | priv->cell_area = gtk_cell_area_box_new (); |
3982 | |
3983 | g_object_ref_sink (priv->cell_area); |
3984 | |
3985 | if (GTK_IS_ORIENTABLE (priv->cell_area)) |
3986 | gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->cell_area), orientation: priv->item_orientation); |
3987 | |
3988 | priv->cell_area_context = gtk_cell_area_create_context (area: priv->cell_area); |
3989 | |
3990 | priv->add_editable_id = |
3991 | g_signal_connect (priv->cell_area, "add-editable" , |
3992 | G_CALLBACK (gtk_icon_view_add_editable), icon_view); |
3993 | priv->remove_editable_id = |
3994 | g_signal_connect (priv->cell_area, "remove-editable" , |
3995 | G_CALLBACK (gtk_icon_view_remove_editable), icon_view); |
3996 | |
3997 | update_text_cell (icon_view); |
3998 | update_pixbuf_cell (icon_view); |
3999 | } |
4000 | |
4001 | static GtkCellArea * |
4002 | gtk_icon_view_cell_layout_get_area (GtkCellLayout *cell_layout) |
4003 | { |
4004 | GtkIconView *icon_view = GTK_ICON_VIEW (cell_layout); |
4005 | GtkIconViewPrivate *priv = icon_view->priv; |
4006 | |
4007 | if (G_UNLIKELY (!priv->cell_area)) |
4008 | gtk_icon_view_ensure_cell_area (icon_view, NULL); |
4009 | |
4010 | return icon_view->priv->cell_area; |
4011 | } |
4012 | |
4013 | void |
4014 | _gtk_icon_view_set_cell_data (GtkIconView *icon_view, |
4015 | GtkIconViewItem *item) |
4016 | { |
4017 | GtkTreeIter iter; |
4018 | GtkTreePath *path; |
4019 | |
4020 | path = gtk_tree_path_new_from_indices (first_index: item->index, -1); |
4021 | if (!gtk_tree_model_get_iter (tree_model: icon_view->priv->model, iter: &iter, path)) |
4022 | return; |
4023 | gtk_tree_path_free (path); |
4024 | |
4025 | gtk_cell_area_apply_attributes (area: icon_view->priv->cell_area, |
4026 | tree_model: icon_view->priv->model, |
4027 | iter: &iter, FALSE, FALSE); |
4028 | } |
4029 | |
4030 | |
4031 | |
4032 | /* Public API */ |
4033 | |
4034 | |
4035 | /** |
4036 | * gtk_icon_view_new: |
4037 | * |
4038 | * Creates a new `GtkIconView` widget |
4039 | * |
4040 | * Returns: A newly created `GtkIconView` widget |
4041 | **/ |
4042 | GtkWidget * |
4043 | gtk_icon_view_new (void) |
4044 | { |
4045 | return g_object_new (GTK_TYPE_ICON_VIEW, NULL); |
4046 | } |
4047 | |
4048 | /** |
4049 | * gtk_icon_view_new_with_area: |
4050 | * @area: the `GtkCellArea` to use to layout cells |
4051 | * |
4052 | * Creates a new `GtkIconView` widget using the |
4053 | * specified @area to layout cells inside the icons. |
4054 | * |
4055 | * Returns: A newly created `GtkIconView` widget |
4056 | **/ |
4057 | GtkWidget * |
4058 | gtk_icon_view_new_with_area (GtkCellArea *area) |
4059 | { |
4060 | return g_object_new (GTK_TYPE_ICON_VIEW, first_property_name: "cell-area" , area, NULL); |
4061 | } |
4062 | |
4063 | /** |
4064 | * gtk_icon_view_new_with_model: |
4065 | * @model: The model. |
4066 | * |
4067 | * Creates a new `GtkIconView` widget with the model @model. |
4068 | * |
4069 | * Returns: A newly created `GtkIconView` widget. |
4070 | **/ |
4071 | GtkWidget * |
4072 | gtk_icon_view_new_with_model (GtkTreeModel *model) |
4073 | { |
4074 | return g_object_new (GTK_TYPE_ICON_VIEW, first_property_name: "model" , model, NULL); |
4075 | } |
4076 | |
4077 | /** |
4078 | * gtk_icon_view_get_path_at_pos: |
4079 | * @icon_view: A `GtkIconView`. |
4080 | * @x: The x position to be identified |
4081 | * @y: The y position to be identified |
4082 | * |
4083 | * Gets the path for the icon at the given position. |
4084 | * |
4085 | * Returns: (nullable) (transfer full): The `GtkTreePath` corresponding |
4086 | * to the icon or %NULL if no icon exists at that position. |
4087 | **/ |
4088 | GtkTreePath * |
4089 | gtk_icon_view_get_path_at_pos (GtkIconView *icon_view, |
4090 | int x, |
4091 | int y) |
4092 | { |
4093 | GtkIconViewItem *item; |
4094 | GtkTreePath *path; |
4095 | |
4096 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), NULL); |
4097 | |
4098 | item = _gtk_icon_view_get_item_at_coords (icon_view, x, y, TRUE, NULL); |
4099 | |
4100 | if (!item) |
4101 | return NULL; |
4102 | |
4103 | path = gtk_tree_path_new_from_indices (first_index: item->index, -1); |
4104 | |
4105 | return path; |
4106 | } |
4107 | |
4108 | /** |
4109 | * gtk_icon_view_get_item_at_pos: |
4110 | * @icon_view: A `GtkIconView`. |
4111 | * @x: The x position to be identified |
4112 | * @y: The y position to be identified |
4113 | * @path: (out) (optional): Return location for the path |
4114 | * @cell: (out) (optional) (transfer none): Return location for the renderer |
4115 | * responsible for the cell at (@x, @y) |
4116 | * |
4117 | * Gets the path and cell for the icon at the given position. |
4118 | * |
4119 | * Returns: %TRUE if an item exists at the specified position |
4120 | **/ |
4121 | gboolean |
4122 | gtk_icon_view_get_item_at_pos (GtkIconView *icon_view, |
4123 | int x, |
4124 | int y, |
4125 | GtkTreePath **path, |
4126 | GtkCellRenderer **cell) |
4127 | { |
4128 | GtkIconViewItem *item; |
4129 | GtkCellRenderer *renderer = NULL; |
4130 | |
4131 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), FALSE); |
4132 | |
4133 | item = _gtk_icon_view_get_item_at_coords (icon_view, x, y, TRUE, cell_at_pos: &renderer); |
4134 | |
4135 | if (path != NULL) |
4136 | { |
4137 | if (item != NULL) |
4138 | *path = gtk_tree_path_new_from_indices (first_index: item->index, -1); |
4139 | else |
4140 | *path = NULL; |
4141 | } |
4142 | |
4143 | if (cell != NULL) |
4144 | *cell = renderer; |
4145 | |
4146 | return (item != NULL); |
4147 | } |
4148 | |
4149 | /** |
4150 | * gtk_icon_view_get_cell_rect: |
4151 | * @icon_view: a `GtkIconView` |
4152 | * @path: a `GtkTreePath` |
4153 | * @cell: (nullable): a `GtkCellRenderer` |
4154 | * @rect: (out): rectangle to fill with cell rect |
4155 | * |
4156 | * Fills the bounding rectangle in widget coordinates for the cell specified by |
4157 | * @path and @cell. If @cell is %NULL the main cell area is used. |
4158 | * |
4159 | * This function is only valid if @icon_view is realized. |
4160 | * |
4161 | * Returns: %FALSE if there is no such item, %TRUE otherwise |
4162 | */ |
4163 | gboolean |
4164 | gtk_icon_view_get_cell_rect (GtkIconView *icon_view, |
4165 | GtkTreePath *path, |
4166 | GtkCellRenderer *cell, |
4167 | GdkRectangle *rect) |
4168 | { |
4169 | GtkIconViewItem *item = NULL; |
4170 | |
4171 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), FALSE); |
4172 | g_return_val_if_fail (cell == NULL || GTK_IS_CELL_RENDERER (cell), FALSE); |
4173 | |
4174 | if (gtk_tree_path_get_depth (path) > 0) |
4175 | item = g_list_nth_data (list: icon_view->priv->items, |
4176 | n: gtk_tree_path_get_indices(path)[0]); |
4177 | |
4178 | if (!item) |
4179 | return FALSE; |
4180 | |
4181 | if (cell) |
4182 | { |
4183 | GtkCellAreaContext *context; |
4184 | |
4185 | context = g_ptr_array_index (icon_view->priv->row_contexts, item->row); |
4186 | _gtk_icon_view_set_cell_data (icon_view, item); |
4187 | gtk_cell_area_get_cell_allocation (area: icon_view->priv->cell_area, context, |
4188 | GTK_WIDGET (icon_view), |
4189 | renderer: cell, cell_area: &item->cell_area, allocation: rect); |
4190 | } |
4191 | else |
4192 | { |
4193 | rect->x = item->cell_area.x - icon_view->priv->item_padding; |
4194 | rect->y = item->cell_area.y - icon_view->priv->item_padding; |
4195 | rect->width = item->cell_area.width + icon_view->priv->item_padding * 2; |
4196 | rect->height = item->cell_area.height + icon_view->priv->item_padding * 2; |
4197 | } |
4198 | |
4199 | return TRUE; |
4200 | } |
4201 | |
4202 | /** |
4203 | * gtk_icon_view_set_tooltip_item: |
4204 | * @icon_view: a `GtkIconView` |
4205 | * @tooltip: a `GtkTooltip` |
4206 | * @path: a `GtkTreePath` |
4207 | * |
4208 | * Sets the tip area of @tooltip to be the area covered by the item at @path. |
4209 | * See also gtk_icon_view_set_tooltip_column() for a simpler alternative. |
4210 | * See also gtk_tooltip_set_tip_area(). |
4211 | */ |
4212 | void |
4213 | gtk_icon_view_set_tooltip_item (GtkIconView *icon_view, |
4214 | GtkTooltip *tooltip, |
4215 | GtkTreePath *path) |
4216 | { |
4217 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
4218 | g_return_if_fail (GTK_IS_TOOLTIP (tooltip)); |
4219 | |
4220 | gtk_icon_view_set_tooltip_cell (icon_view, tooltip, path, NULL); |
4221 | } |
4222 | |
4223 | /** |
4224 | * gtk_icon_view_set_tooltip_cell: |
4225 | * @icon_view: a `GtkIconView` |
4226 | * @tooltip: a `GtkTooltip` |
4227 | * @path: a `GtkTreePath` |
4228 | * @cell: (nullable): a `GtkCellRenderer` |
4229 | * |
4230 | * Sets the tip area of @tooltip to the area which @cell occupies in |
4231 | * the item pointed to by @path. See also gtk_tooltip_set_tip_area(). |
4232 | * |
4233 | * See also gtk_icon_view_set_tooltip_column() for a simpler alternative. |
4234 | */ |
4235 | void |
4236 | gtk_icon_view_set_tooltip_cell (GtkIconView *icon_view, |
4237 | GtkTooltip *tooltip, |
4238 | GtkTreePath *path, |
4239 | GtkCellRenderer *cell) |
4240 | { |
4241 | GdkRectangle rect; |
4242 | |
4243 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
4244 | g_return_if_fail (GTK_IS_TOOLTIP (tooltip)); |
4245 | g_return_if_fail (cell == NULL || GTK_IS_CELL_RENDERER (cell)); |
4246 | |
4247 | if (!gtk_icon_view_get_cell_rect (icon_view, path, cell, rect: &rect)) |
4248 | return; |
4249 | |
4250 | gtk_tooltip_set_tip_area (tooltip, rect: &rect); |
4251 | } |
4252 | |
4253 | |
4254 | /** |
4255 | * gtk_icon_view_get_tooltip_context: |
4256 | * @icon_view: an `GtkIconView` |
4257 | * @x: the x coordinate (relative to widget coordinates) |
4258 | * @y: the y coordinate (relative to widget coordinates) |
4259 | * @keyboard_tip: whether this is a keyboard tooltip or not |
4260 | * @model: (out) (optional) (transfer none): a pointer to receive a `GtkTreeModel` |
4261 | * @path: (out) (optional): a pointer to receive a `GtkTreePath` |
4262 | * @iter: (out) (optional): a pointer to receive a `GtkTreeIter` |
4263 | * |
4264 | * This function is supposed to be used in a `GtkWidget::query-tooltip` |
4265 | * signal handler for `GtkIconView`. The @x, @y and @keyboard_tip values |
4266 | * which are received in the signal handler, should be passed to this |
4267 | * function without modification. |
4268 | * |
4269 | * The return value indicates whether there is an icon view item at the given |
4270 | * coordinates (%TRUE) or not (%FALSE) for mouse tooltips. For keyboard |
4271 | * tooltips the item returned will be the cursor item. When %TRUE, then any of |
4272 | * @model, @path and @iter which have been provided will be set to point to |
4273 | * that row and the corresponding model. |
4274 | * |
4275 | * Returns: whether or not the given tooltip context points to an item |
4276 | */ |
4277 | gboolean |
4278 | gtk_icon_view_get_tooltip_context (GtkIconView *icon_view, |
4279 | int x, |
4280 | int y, |
4281 | gboolean keyboard_tip, |
4282 | GtkTreeModel **model, |
4283 | GtkTreePath **path, |
4284 | GtkTreeIter *iter) |
4285 | { |
4286 | GtkTreePath *tmppath = NULL; |
4287 | |
4288 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), FALSE); |
4289 | |
4290 | if (keyboard_tip) |
4291 | { |
4292 | gtk_icon_view_get_cursor (icon_view, path: &tmppath, NULL); |
4293 | |
4294 | if (!tmppath) |
4295 | return FALSE; |
4296 | } |
4297 | else |
4298 | { |
4299 | if (!gtk_icon_view_get_item_at_pos (icon_view, x, y, path: &tmppath, NULL)) |
4300 | return FALSE; |
4301 | } |
4302 | |
4303 | if (model) |
4304 | *model = gtk_icon_view_get_model (icon_view); |
4305 | |
4306 | if (iter) |
4307 | gtk_tree_model_get_iter (tree_model: gtk_icon_view_get_model (icon_view), |
4308 | iter, path: tmppath); |
4309 | |
4310 | if (path) |
4311 | *path = tmppath; |
4312 | else |
4313 | gtk_tree_path_free (path: tmppath); |
4314 | |
4315 | return TRUE; |
4316 | } |
4317 | |
4318 | static gboolean |
4319 | gtk_icon_view_set_tooltip_query_cb (GtkWidget *widget, |
4320 | int x, |
4321 | int y, |
4322 | gboolean keyboard_tip, |
4323 | GtkTooltip *tooltip, |
4324 | gpointer data) |
4325 | { |
4326 | char *str; |
4327 | GtkTreeIter iter; |
4328 | GtkTreePath *path; |
4329 | GtkTreeModel *model; |
4330 | GtkIconView *icon_view = GTK_ICON_VIEW (widget); |
4331 | |
4332 | if (!gtk_icon_view_get_tooltip_context (GTK_ICON_VIEW (widget), |
4333 | x, y, |
4334 | keyboard_tip, |
4335 | model: &model, path: &path, iter: &iter)) |
4336 | return FALSE; |
4337 | |
4338 | gtk_tree_model_get (tree_model: model, iter: &iter, icon_view->priv->tooltip_column, &str, -1); |
4339 | |
4340 | if (!str) |
4341 | { |
4342 | gtk_tree_path_free (path); |
4343 | return FALSE; |
4344 | } |
4345 | |
4346 | gtk_tooltip_set_markup (tooltip, markup: str); |
4347 | gtk_icon_view_set_tooltip_item (icon_view, tooltip, path); |
4348 | |
4349 | gtk_tree_path_free (path); |
4350 | g_free (mem: str); |
4351 | |
4352 | return TRUE; |
4353 | } |
4354 | |
4355 | |
4356 | /** |
4357 | * gtk_icon_view_set_tooltip_column: |
4358 | * @icon_view: a `GtkIconView` |
4359 | * @column: an integer, which is a valid column number for @icon_view’s model |
4360 | * |
4361 | * If you only plan to have simple (text-only) tooltips on full items, you |
4362 | * can use this function to have `GtkIconView` handle these automatically |
4363 | * for you. @column should be set to the column in @icon_view’s model |
4364 | * containing the tooltip texts, or -1 to disable this feature. |
4365 | * |
4366 | * When enabled, `GtkWidget:has-tooltip` will be set to %TRUE and |
4367 | * @icon_view will connect a `GtkWidget::query-tooltip` signal handler. |
4368 | * |
4369 | * Note that the signal handler sets the text with gtk_tooltip_set_markup(), |
4370 | * so &, <, etc have to be escaped in the text. |
4371 | */ |
4372 | void |
4373 | gtk_icon_view_set_tooltip_column (GtkIconView *icon_view, |
4374 | int column) |
4375 | { |
4376 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
4377 | |
4378 | if (column == icon_view->priv->tooltip_column) |
4379 | return; |
4380 | |
4381 | if (column == -1) |
4382 | { |
4383 | g_signal_handlers_disconnect_by_func (icon_view, |
4384 | gtk_icon_view_set_tooltip_query_cb, |
4385 | NULL); |
4386 | gtk_widget_set_has_tooltip (GTK_WIDGET (icon_view), FALSE); |
4387 | } |
4388 | else |
4389 | { |
4390 | if (icon_view->priv->tooltip_column == -1) |
4391 | { |
4392 | g_signal_connect (icon_view, "query-tooltip" , |
4393 | G_CALLBACK (gtk_icon_view_set_tooltip_query_cb), NULL); |
4394 | gtk_widget_set_has_tooltip (GTK_WIDGET (icon_view), TRUE); |
4395 | } |
4396 | } |
4397 | |
4398 | icon_view->priv->tooltip_column = column; |
4399 | g_object_notify (G_OBJECT (icon_view), property_name: "tooltip-column" ); |
4400 | } |
4401 | |
4402 | /** |
4403 | * gtk_icon_view_get_tooltip_column: |
4404 | * @icon_view: a `GtkIconView` |
4405 | * |
4406 | * Returns the column of @icon_view’s model which is being used for |
4407 | * displaying tooltips on @icon_view’s rows. |
4408 | * |
4409 | * Returns: the index of the tooltip column that is currently being |
4410 | * used, or -1 if this is disabled. |
4411 | */ |
4412 | int |
4413 | gtk_icon_view_get_tooltip_column (GtkIconView *icon_view) |
4414 | { |
4415 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), 0); |
4416 | |
4417 | return icon_view->priv->tooltip_column; |
4418 | } |
4419 | |
4420 | /** |
4421 | * gtk_icon_view_get_visible_range: |
4422 | * @icon_view: A `GtkIconView` |
4423 | * @start_path: (out) (optional): Return location for start of region |
4424 | * @end_path: (out) (optional): Return location for end of region |
4425 | * |
4426 | * Sets @start_path and @end_path to be the first and last visible path. |
4427 | * Note that there may be invisible paths in between. |
4428 | * |
4429 | * Both paths should be freed with gtk_tree_path_free() after use. |
4430 | * |
4431 | * Returns: %TRUE, if valid paths were placed in @start_path and @end_path |
4432 | */ |
4433 | gboolean |
4434 | gtk_icon_view_get_visible_range (GtkIconView *icon_view, |
4435 | GtkTreePath **start_path, |
4436 | GtkTreePath **end_path) |
4437 | { |
4438 | int start_index = -1; |
4439 | int end_index = -1; |
4440 | GList *icons; |
4441 | |
4442 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), FALSE); |
4443 | |
4444 | if (icon_view->priv->hadjustment == NULL || |
4445 | icon_view->priv->vadjustment == NULL) |
4446 | return FALSE; |
4447 | |
4448 | if (start_path == NULL && end_path == NULL) |
4449 | return FALSE; |
4450 | |
4451 | for (icons = icon_view->priv->items; icons; icons = icons->next) |
4452 | { |
4453 | GtkIconViewItem *item = icons->data; |
4454 | GdkRectangle *item_area = &item->cell_area; |
4455 | |
4456 | if ((item_area->x + item_area->width >= (int)gtk_adjustment_get_value (adjustment: icon_view->priv->hadjustment)) && |
4457 | (item_area->y + item_area->height >= (int)gtk_adjustment_get_value (adjustment: icon_view->priv->vadjustment)) && |
4458 | (item_area->x <= |
4459 | (int) (gtk_adjustment_get_value (adjustment: icon_view->priv->hadjustment) + |
4460 | gtk_adjustment_get_page_size (adjustment: icon_view->priv->hadjustment))) && |
4461 | (item_area->y <= |
4462 | (int) (gtk_adjustment_get_value (adjustment: icon_view->priv->vadjustment) + |
4463 | gtk_adjustment_get_page_size (adjustment: icon_view->priv->vadjustment)))) |
4464 | { |
4465 | if (start_index == -1) |
4466 | start_index = item->index; |
4467 | end_index = item->index; |
4468 | } |
4469 | } |
4470 | |
4471 | if (start_path && start_index != -1) |
4472 | *start_path = gtk_tree_path_new_from_indices (first_index: start_index, -1); |
4473 | if (end_path && end_index != -1) |
4474 | *end_path = gtk_tree_path_new_from_indices (first_index: end_index, -1); |
4475 | |
4476 | return start_index != -1; |
4477 | } |
4478 | |
4479 | /** |
4480 | * gtk_icon_view_selected_foreach: |
4481 | * @icon_view: A `GtkIconView`. |
4482 | * @func: (scope call): The function to call for each selected icon. |
4483 | * @data: User data to pass to the function. |
4484 | * |
4485 | * Calls a function for each selected icon. Note that the model or |
4486 | * selection cannot be modified from within this function. |
4487 | **/ |
4488 | void |
4489 | gtk_icon_view_selected_foreach (GtkIconView *icon_view, |
4490 | GtkIconViewForeachFunc func, |
4491 | gpointer data) |
4492 | { |
4493 | GList *list; |
4494 | |
4495 | for (list = icon_view->priv->items; list; list = list->next) |
4496 | { |
4497 | GtkIconViewItem *item = list->data; |
4498 | GtkTreePath *path = gtk_tree_path_new_from_indices (first_index: item->index, -1); |
4499 | |
4500 | if (item->selected) |
4501 | (* func) (icon_view, path, data); |
4502 | |
4503 | gtk_tree_path_free (path); |
4504 | } |
4505 | } |
4506 | |
4507 | /** |
4508 | * gtk_icon_view_set_selection_mode: |
4509 | * @icon_view: A `GtkIconView`. |
4510 | * @mode: The selection mode |
4511 | * |
4512 | * Sets the selection mode of the @icon_view. |
4513 | **/ |
4514 | void |
4515 | gtk_icon_view_set_selection_mode (GtkIconView *icon_view, |
4516 | GtkSelectionMode mode) |
4517 | { |
4518 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
4519 | |
4520 | if (mode == icon_view->priv->selection_mode) |
4521 | return; |
4522 | |
4523 | if (mode == GTK_SELECTION_NONE || |
4524 | icon_view->priv->selection_mode == GTK_SELECTION_MULTIPLE) |
4525 | gtk_icon_view_unselect_all (icon_view); |
4526 | |
4527 | icon_view->priv->selection_mode = mode; |
4528 | |
4529 | g_object_notify (G_OBJECT (icon_view), property_name: "selection-mode" ); |
4530 | } |
4531 | |
4532 | /** |
4533 | * gtk_icon_view_get_selection_mode: |
4534 | * @icon_view: A `GtkIconView`. |
4535 | * |
4536 | * Gets the selection mode of the @icon_view. |
4537 | * |
4538 | * Returns: the current selection mode |
4539 | **/ |
4540 | GtkSelectionMode |
4541 | gtk_icon_view_get_selection_mode (GtkIconView *icon_view) |
4542 | { |
4543 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), GTK_SELECTION_SINGLE); |
4544 | |
4545 | return icon_view->priv->selection_mode; |
4546 | } |
4547 | |
4548 | /** |
4549 | * gtk_icon_view_set_model: |
4550 | * @icon_view: A `GtkIconView`. |
4551 | * @model: (nullable): The model. |
4552 | * |
4553 | * Sets the model for a `GtkIconView`. |
4554 | * If the @icon_view already has a model set, it will remove |
4555 | * it before setting the new model. If @model is %NULL, then |
4556 | * it will unset the old model. |
4557 | **/ |
4558 | void |
4559 | gtk_icon_view_set_model (GtkIconView *icon_view, |
4560 | GtkTreeModel *model) |
4561 | { |
4562 | gboolean dirty; |
4563 | |
4564 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
4565 | g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model)); |
4566 | |
4567 | if (icon_view->priv->model == model) |
4568 | return; |
4569 | |
4570 | if (icon_view->priv->scroll_to_path) |
4571 | { |
4572 | gtk_tree_row_reference_free (reference: icon_view->priv->scroll_to_path); |
4573 | icon_view->priv->scroll_to_path = NULL; |
4574 | } |
4575 | |
4576 | /* The area can be NULL while disposing */ |
4577 | if (icon_view->priv->cell_area) |
4578 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
4579 | |
4580 | dirty = gtk_icon_view_unselect_all_internal (icon_view); |
4581 | |
4582 | if (model) |
4583 | { |
4584 | if (icon_view->priv->pixbuf_column != -1) |
4585 | { |
4586 | g_return_if_fail (gtk_tree_model_get_column_type (model, icon_view->priv->pixbuf_column) == GDK_TYPE_PIXBUF); |
4587 | } |
4588 | |
4589 | if (icon_view->priv->text_column != -1) |
4590 | { |
4591 | g_return_if_fail (gtk_tree_model_get_column_type (model, icon_view->priv->text_column) == G_TYPE_STRING); |
4592 | } |
4593 | |
4594 | if (icon_view->priv->markup_column != -1) |
4595 | { |
4596 | g_return_if_fail (gtk_tree_model_get_column_type (model, icon_view->priv->markup_column) == G_TYPE_STRING); |
4597 | } |
4598 | |
4599 | } |
4600 | |
4601 | if (icon_view->priv->model) |
4602 | { |
4603 | g_signal_handlers_disconnect_by_func (icon_view->priv->model, |
4604 | gtk_icon_view_row_changed, |
4605 | icon_view); |
4606 | g_signal_handlers_disconnect_by_func (icon_view->priv->model, |
4607 | gtk_icon_view_row_inserted, |
4608 | icon_view); |
4609 | g_signal_handlers_disconnect_by_func (icon_view->priv->model, |
4610 | gtk_icon_view_row_deleted, |
4611 | icon_view); |
4612 | g_signal_handlers_disconnect_by_func (icon_view->priv->model, |
4613 | gtk_icon_view_rows_reordered, |
4614 | icon_view); |
4615 | |
4616 | g_object_unref (object: icon_view->priv->model); |
4617 | |
4618 | g_list_free_full (list: icon_view->priv->items, free_func: (GDestroyNotify) gtk_icon_view_item_free); |
4619 | icon_view->priv->items = NULL; |
4620 | icon_view->priv->anchor_item = NULL; |
4621 | icon_view->priv->cursor_item = NULL; |
4622 | icon_view->priv->last_single_clicked = NULL; |
4623 | icon_view->priv->last_prelight = NULL; |
4624 | icon_view->priv->width = 0; |
4625 | icon_view->priv->height = 0; |
4626 | } |
4627 | |
4628 | icon_view->priv->model = model; |
4629 | |
4630 | if (icon_view->priv->model) |
4631 | { |
4632 | g_object_ref (icon_view->priv->model); |
4633 | g_signal_connect (icon_view->priv->model, |
4634 | "row-changed" , |
4635 | G_CALLBACK (gtk_icon_view_row_changed), |
4636 | icon_view); |
4637 | g_signal_connect (icon_view->priv->model, |
4638 | "row-inserted" , |
4639 | G_CALLBACK (gtk_icon_view_row_inserted), |
4640 | icon_view); |
4641 | g_signal_connect (icon_view->priv->model, |
4642 | "row-deleted" , |
4643 | G_CALLBACK (gtk_icon_view_row_deleted), |
4644 | icon_view); |
4645 | g_signal_connect (icon_view->priv->model, |
4646 | "rows-reordered" , |
4647 | G_CALLBACK (gtk_icon_view_rows_reordered), |
4648 | icon_view); |
4649 | |
4650 | gtk_icon_view_build_items (icon_view); |
4651 | } |
4652 | |
4653 | g_object_notify (G_OBJECT (icon_view), property_name: "model" ); |
4654 | |
4655 | if (dirty) |
4656 | g_signal_emit (instance: icon_view, signal_id: icon_view_signals[SELECTION_CHANGED], detail: 0); |
4657 | |
4658 | gtk_widget_queue_resize (GTK_WIDGET (icon_view)); |
4659 | } |
4660 | |
4661 | /** |
4662 | * gtk_icon_view_get_model: |
4663 | * @icon_view: a `GtkIconView` |
4664 | * |
4665 | * Returns the model the `GtkIconView` is based on. Returns %NULL if the |
4666 | * model is unset. |
4667 | * |
4668 | * Returns: (nullable) (transfer none): The currently used `GtkTreeModel` |
4669 | */ |
4670 | GtkTreeModel * |
4671 | gtk_icon_view_get_model (GtkIconView *icon_view) |
4672 | { |
4673 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), NULL); |
4674 | |
4675 | return icon_view->priv->model; |
4676 | } |
4677 | |
4678 | static void |
4679 | update_text_cell (GtkIconView *icon_view) |
4680 | { |
4681 | if (!icon_view->priv->cell_area) |
4682 | return; |
4683 | |
4684 | if (icon_view->priv->text_column == -1 && |
4685 | icon_view->priv->markup_column == -1) |
4686 | { |
4687 | if (icon_view->priv->text_cell != NULL) |
4688 | { |
4689 | gtk_cell_area_remove (area: icon_view->priv->cell_area, |
4690 | renderer: icon_view->priv->text_cell); |
4691 | icon_view->priv->text_cell = NULL; |
4692 | } |
4693 | } |
4694 | else |
4695 | { |
4696 | if (icon_view->priv->text_cell == NULL) |
4697 | { |
4698 | icon_view->priv->text_cell = gtk_cell_renderer_text_new (); |
4699 | |
4700 | gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (icon_view), cell: icon_view->priv->text_cell, FALSE); |
4701 | } |
4702 | |
4703 | if (icon_view->priv->markup_column != -1) |
4704 | gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (icon_view), |
4705 | cell: icon_view->priv->text_cell, |
4706 | "markup" , icon_view->priv->markup_column, |
4707 | NULL); |
4708 | else |
4709 | gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (icon_view), |
4710 | cell: icon_view->priv->text_cell, |
4711 | "text" , icon_view->priv->text_column, |
4712 | NULL); |
4713 | |
4714 | if (icon_view->priv->item_orientation == GTK_ORIENTATION_VERTICAL) |
4715 | g_object_set (object: icon_view->priv->text_cell, |
4716 | first_property_name: "alignment" , PANGO_ALIGN_CENTER, |
4717 | "wrap-mode" , PANGO_WRAP_WORD_CHAR, |
4718 | "xalign" , 0.5, |
4719 | "yalign" , 0.0, |
4720 | NULL); |
4721 | else |
4722 | g_object_set (object: icon_view->priv->text_cell, |
4723 | first_property_name: "alignment" , PANGO_ALIGN_LEFT, |
4724 | "wrap-mode" , PANGO_WRAP_WORD_CHAR, |
4725 | "xalign" , 0.0, |
4726 | "yalign" , 0.5, |
4727 | NULL); |
4728 | } |
4729 | } |
4730 | |
4731 | static void |
4732 | update_pixbuf_cell (GtkIconView *icon_view) |
4733 | { |
4734 | if (!icon_view->priv->cell_area) |
4735 | return; |
4736 | |
4737 | if (icon_view->priv->pixbuf_column == -1) |
4738 | { |
4739 | if (icon_view->priv->pixbuf_cell != NULL) |
4740 | { |
4741 | gtk_cell_area_remove (area: icon_view->priv->cell_area, |
4742 | renderer: icon_view->priv->pixbuf_cell); |
4743 | |
4744 | icon_view->priv->pixbuf_cell = NULL; |
4745 | } |
4746 | } |
4747 | else |
4748 | { |
4749 | if (icon_view->priv->pixbuf_cell == NULL) |
4750 | { |
4751 | icon_view->priv->pixbuf_cell = gtk_cell_renderer_pixbuf_new (); |
4752 | |
4753 | gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (icon_view), cell: icon_view->priv->pixbuf_cell, FALSE); |
4754 | } |
4755 | |
4756 | gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (icon_view), |
4757 | cell: icon_view->priv->pixbuf_cell, |
4758 | "pixbuf" , icon_view->priv->pixbuf_column, |
4759 | NULL); |
4760 | |
4761 | if (icon_view->priv->item_orientation == GTK_ORIENTATION_VERTICAL) |
4762 | g_object_set (object: icon_view->priv->pixbuf_cell, |
4763 | first_property_name: "xalign" , 0.5, |
4764 | "yalign" , 1.0, |
4765 | NULL); |
4766 | else |
4767 | g_object_set (object: icon_view->priv->pixbuf_cell, |
4768 | first_property_name: "xalign" , 0.0, |
4769 | "yalign" , 0.0, |
4770 | NULL); |
4771 | } |
4772 | } |
4773 | |
4774 | /** |
4775 | * gtk_icon_view_set_text_column: |
4776 | * @icon_view: A `GtkIconView`. |
4777 | * @column: A column in the currently used model, or -1 to display no text |
4778 | * |
4779 | * Sets the column with text for @icon_view to be @column. The text |
4780 | * column must be of type `G_TYPE_STRING`. |
4781 | **/ |
4782 | void |
4783 | gtk_icon_view_set_text_column (GtkIconView *icon_view, |
4784 | int column) |
4785 | { |
4786 | if (column == icon_view->priv->text_column) |
4787 | return; |
4788 | |
4789 | if (column == -1) |
4790 | icon_view->priv->text_column = -1; |
4791 | else |
4792 | { |
4793 | if (icon_view->priv->model != NULL) |
4794 | { |
4795 | g_return_if_fail (gtk_tree_model_get_column_type (icon_view->priv->model, column) == G_TYPE_STRING); |
4796 | } |
4797 | |
4798 | icon_view->priv->text_column = column; |
4799 | } |
4800 | |
4801 | if (icon_view->priv->cell_area) |
4802 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
4803 | |
4804 | update_text_cell (icon_view); |
4805 | |
4806 | gtk_icon_view_invalidate_sizes (icon_view); |
4807 | |
4808 | g_object_notify (G_OBJECT (icon_view), property_name: "text-column" ); |
4809 | } |
4810 | |
4811 | /** |
4812 | * gtk_icon_view_get_text_column: |
4813 | * @icon_view: A `GtkIconView`. |
4814 | * |
4815 | * Returns the column with text for @icon_view. |
4816 | * |
4817 | * Returns: the text column, or -1 if it’s unset. |
4818 | */ |
4819 | int |
4820 | gtk_icon_view_get_text_column (GtkIconView *icon_view) |
4821 | { |
4822 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), -1); |
4823 | |
4824 | return icon_view->priv->text_column; |
4825 | } |
4826 | |
4827 | /** |
4828 | * gtk_icon_view_set_markup_column: |
4829 | * @icon_view: A `GtkIconView`. |
4830 | * @column: A column in the currently used model, or -1 to display no text |
4831 | * |
4832 | * Sets the column with markup information for @icon_view to be |
4833 | * @column. The markup column must be of type `G_TYPE_STRING`. |
4834 | * If the markup column is set to something, it overrides |
4835 | * the text column set by gtk_icon_view_set_text_column(). |
4836 | **/ |
4837 | void |
4838 | gtk_icon_view_set_markup_column (GtkIconView *icon_view, |
4839 | int column) |
4840 | { |
4841 | if (column == icon_view->priv->markup_column) |
4842 | return; |
4843 | |
4844 | if (column == -1) |
4845 | icon_view->priv->markup_column = -1; |
4846 | else |
4847 | { |
4848 | if (icon_view->priv->model != NULL) |
4849 | { |
4850 | g_return_if_fail (gtk_tree_model_get_column_type (icon_view->priv->model, column) == G_TYPE_STRING); |
4851 | } |
4852 | |
4853 | icon_view->priv->markup_column = column; |
4854 | } |
4855 | |
4856 | if (icon_view->priv->cell_area) |
4857 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
4858 | |
4859 | update_text_cell (icon_view); |
4860 | |
4861 | gtk_icon_view_invalidate_sizes (icon_view); |
4862 | |
4863 | g_object_notify (G_OBJECT (icon_view), property_name: "markup-column" ); |
4864 | } |
4865 | |
4866 | /** |
4867 | * gtk_icon_view_get_markup_column: |
4868 | * @icon_view: A `GtkIconView`. |
4869 | * |
4870 | * Returns the column with markup text for @icon_view. |
4871 | * |
4872 | * Returns: the markup column, or -1 if it’s unset. |
4873 | */ |
4874 | int |
4875 | gtk_icon_view_get_markup_column (GtkIconView *icon_view) |
4876 | { |
4877 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), -1); |
4878 | |
4879 | return icon_view->priv->markup_column; |
4880 | } |
4881 | |
4882 | /** |
4883 | * gtk_icon_view_set_pixbuf_column: |
4884 | * @icon_view: A `GtkIconView`. |
4885 | * @column: A column in the currently used model, or -1 to disable |
4886 | * |
4887 | * Sets the column with pixbufs for @icon_view to be @column. The pixbuf |
4888 | * column must be of type `GDK_TYPE_PIXBUF` |
4889 | **/ |
4890 | void |
4891 | gtk_icon_view_set_pixbuf_column (GtkIconView *icon_view, |
4892 | int column) |
4893 | { |
4894 | if (column == icon_view->priv->pixbuf_column) |
4895 | return; |
4896 | |
4897 | if (column == -1) |
4898 | icon_view->priv->pixbuf_column = -1; |
4899 | else |
4900 | { |
4901 | if (icon_view->priv->model != NULL) |
4902 | { |
4903 | g_return_if_fail (gtk_tree_model_get_column_type (icon_view->priv->model, column) == GDK_TYPE_PIXBUF); |
4904 | } |
4905 | |
4906 | icon_view->priv->pixbuf_column = column; |
4907 | } |
4908 | |
4909 | if (icon_view->priv->cell_area) |
4910 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
4911 | |
4912 | update_pixbuf_cell (icon_view); |
4913 | |
4914 | gtk_icon_view_invalidate_sizes (icon_view); |
4915 | |
4916 | g_object_notify (G_OBJECT (icon_view), property_name: "pixbuf-column" ); |
4917 | |
4918 | } |
4919 | |
4920 | /** |
4921 | * gtk_icon_view_get_pixbuf_column: |
4922 | * @icon_view: A `GtkIconView`. |
4923 | * |
4924 | * Returns the column with pixbufs for @icon_view. |
4925 | * |
4926 | * Returns: the pixbuf column, or -1 if it’s unset. |
4927 | */ |
4928 | int |
4929 | gtk_icon_view_get_pixbuf_column (GtkIconView *icon_view) |
4930 | { |
4931 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), -1); |
4932 | |
4933 | return icon_view->priv->pixbuf_column; |
4934 | } |
4935 | |
4936 | /** |
4937 | * gtk_icon_view_select_path: |
4938 | * @icon_view: A `GtkIconView`. |
4939 | * @path: The `GtkTreePath` to be selected. |
4940 | * |
4941 | * Selects the row at @path. |
4942 | **/ |
4943 | void |
4944 | gtk_icon_view_select_path (GtkIconView *icon_view, |
4945 | GtkTreePath *path) |
4946 | { |
4947 | GtkIconViewItem *item = NULL; |
4948 | |
4949 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
4950 | g_return_if_fail (icon_view->priv->model != NULL); |
4951 | g_return_if_fail (path != NULL); |
4952 | |
4953 | if (gtk_tree_path_get_depth (path) > 0) |
4954 | item = g_list_nth_data (list: icon_view->priv->items, |
4955 | n: gtk_tree_path_get_indices(path)[0]); |
4956 | |
4957 | if (item) |
4958 | _gtk_icon_view_select_item (icon_view, item); |
4959 | } |
4960 | |
4961 | /** |
4962 | * gtk_icon_view_unselect_path: |
4963 | * @icon_view: A `GtkIconView`. |
4964 | * @path: The `GtkTreePath` to be unselected. |
4965 | * |
4966 | * Unselects the row at @path. |
4967 | **/ |
4968 | void |
4969 | gtk_icon_view_unselect_path (GtkIconView *icon_view, |
4970 | GtkTreePath *path) |
4971 | { |
4972 | GtkIconViewItem *item; |
4973 | |
4974 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
4975 | g_return_if_fail (icon_view->priv->model != NULL); |
4976 | g_return_if_fail (path != NULL); |
4977 | |
4978 | item = g_list_nth_data (list: icon_view->priv->items, |
4979 | n: gtk_tree_path_get_indices(path)[0]); |
4980 | |
4981 | if (!item) |
4982 | return; |
4983 | |
4984 | _gtk_icon_view_unselect_item (icon_view, item); |
4985 | } |
4986 | |
4987 | /** |
4988 | * gtk_icon_view_get_selected_items: |
4989 | * @icon_view: A `GtkIconView`. |
4990 | * |
4991 | * Creates a list of paths of all selected items. Additionally, if you are |
4992 | * planning on modifying the model after calling this function, you may |
4993 | * want to convert the returned list into a list of `GtkTreeRowReferences`. |
4994 | * To do this, you can use gtk_tree_row_reference_new(). |
4995 | * |
4996 | * To free the return value, use `g_list_free_full`: |
4997 | * |[<!-- language="C" --> |
4998 | * GtkWidget *icon_view = gtk_icon_view_new (); |
4999 | * // Use icon_view |
5000 | * |
5001 | * GList *list = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (icon_view)); |
5002 | * |
5003 | * // use list |
5004 | * |
5005 | * g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free); |
5006 | * ]| |
5007 | * |
5008 | * Returns: (element-type GtkTreePath) (transfer full): A `GList` containing a `GtkTreePath` for each selected row. |
5009 | **/ |
5010 | GList * |
5011 | gtk_icon_view_get_selected_items (GtkIconView *icon_view) |
5012 | { |
5013 | GList *list; |
5014 | GList *selected = NULL; |
5015 | |
5016 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), NULL); |
5017 | |
5018 | for (list = icon_view->priv->items; list != NULL; list = list->next) |
5019 | { |
5020 | GtkIconViewItem *item = list->data; |
5021 | |
5022 | if (item->selected) |
5023 | { |
5024 | GtkTreePath *path = gtk_tree_path_new_from_indices (first_index: item->index, -1); |
5025 | |
5026 | selected = g_list_prepend (list: selected, data: path); |
5027 | } |
5028 | } |
5029 | |
5030 | return selected; |
5031 | } |
5032 | |
5033 | /** |
5034 | * gtk_icon_view_select_all: |
5035 | * @icon_view: A `GtkIconView`. |
5036 | * |
5037 | * Selects all the icons. @icon_view must has its selection mode set |
5038 | * to %GTK_SELECTION_MULTIPLE. |
5039 | **/ |
5040 | void |
5041 | gtk_icon_view_select_all (GtkIconView *icon_view) |
5042 | { |
5043 | GList *items; |
5044 | gboolean dirty = FALSE; |
5045 | |
5046 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
5047 | |
5048 | if (icon_view->priv->selection_mode != GTK_SELECTION_MULTIPLE) |
5049 | return; |
5050 | |
5051 | for (items = icon_view->priv->items; items; items = items->next) |
5052 | { |
5053 | GtkIconViewItem *item = items->data; |
5054 | |
5055 | if (!item->selected) |
5056 | { |
5057 | dirty = TRUE; |
5058 | item->selected = TRUE; |
5059 | gtk_icon_view_queue_draw_item (icon_view, item); |
5060 | } |
5061 | } |
5062 | |
5063 | if (dirty) |
5064 | g_signal_emit (instance: icon_view, signal_id: icon_view_signals[SELECTION_CHANGED], detail: 0); |
5065 | } |
5066 | |
5067 | /** |
5068 | * gtk_icon_view_unselect_all: |
5069 | * @icon_view: A `GtkIconView`. |
5070 | * |
5071 | * Unselects all the icons. |
5072 | **/ |
5073 | void |
5074 | gtk_icon_view_unselect_all (GtkIconView *icon_view) |
5075 | { |
5076 | gboolean dirty = FALSE; |
5077 | |
5078 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
5079 | |
5080 | if (icon_view->priv->selection_mode == GTK_SELECTION_BROWSE) |
5081 | return; |
5082 | |
5083 | dirty = gtk_icon_view_unselect_all_internal (icon_view); |
5084 | |
5085 | if (dirty) |
5086 | g_signal_emit (instance: icon_view, signal_id: icon_view_signals[SELECTION_CHANGED], detail: 0); |
5087 | } |
5088 | |
5089 | /** |
5090 | * gtk_icon_view_path_is_selected: |
5091 | * @icon_view: A `GtkIconView`. |
5092 | * @path: A `GtkTreePath` to check selection on. |
5093 | * |
5094 | * Returns %TRUE if the icon pointed to by @path is currently |
5095 | * selected. If @path does not point to a valid location, %FALSE is returned. |
5096 | * |
5097 | * Returns: %TRUE if @path is selected. |
5098 | **/ |
5099 | gboolean |
5100 | gtk_icon_view_path_is_selected (GtkIconView *icon_view, |
5101 | GtkTreePath *path) |
5102 | { |
5103 | GtkIconViewItem *item; |
5104 | |
5105 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), FALSE); |
5106 | g_return_val_if_fail (icon_view->priv->model != NULL, FALSE); |
5107 | g_return_val_if_fail (path != NULL, FALSE); |
5108 | |
5109 | item = g_list_nth_data (list: icon_view->priv->items, |
5110 | n: gtk_tree_path_get_indices(path)[0]); |
5111 | |
5112 | if (!item) |
5113 | return FALSE; |
5114 | |
5115 | return item->selected; |
5116 | } |
5117 | |
5118 | /** |
5119 | * gtk_icon_view_get_item_row: |
5120 | * @icon_view: a `GtkIconView` |
5121 | * @path: the `GtkTreePath` of the item |
5122 | * |
5123 | * Gets the row in which the item @path is currently |
5124 | * displayed. Row numbers start at 0. |
5125 | * |
5126 | * Returns: The row in which the item is displayed |
5127 | */ |
5128 | int |
5129 | gtk_icon_view_get_item_row (GtkIconView *icon_view, |
5130 | GtkTreePath *path) |
5131 | { |
5132 | GtkIconViewItem *item; |
5133 | |
5134 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), -1); |
5135 | g_return_val_if_fail (icon_view->priv->model != NULL, -1); |
5136 | g_return_val_if_fail (path != NULL, -1); |
5137 | |
5138 | item = g_list_nth_data (list: icon_view->priv->items, |
5139 | n: gtk_tree_path_get_indices(path)[0]); |
5140 | |
5141 | if (!item) |
5142 | return -1; |
5143 | |
5144 | return item->row; |
5145 | } |
5146 | |
5147 | /** |
5148 | * gtk_icon_view_get_item_column: |
5149 | * @icon_view: a `GtkIconView` |
5150 | * @path: the `GtkTreePath` of the item |
5151 | * |
5152 | * Gets the column in which the item @path is currently |
5153 | * displayed. Column numbers start at 0. |
5154 | * |
5155 | * Returns: The column in which the item is displayed |
5156 | */ |
5157 | int |
5158 | gtk_icon_view_get_item_column (GtkIconView *icon_view, |
5159 | GtkTreePath *path) |
5160 | { |
5161 | GtkIconViewItem *item; |
5162 | |
5163 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), -1); |
5164 | g_return_val_if_fail (icon_view->priv->model != NULL, -1); |
5165 | g_return_val_if_fail (path != NULL, -1); |
5166 | |
5167 | item = g_list_nth_data (list: icon_view->priv->items, |
5168 | n: gtk_tree_path_get_indices(path)[0]); |
5169 | |
5170 | if (!item) |
5171 | return -1; |
5172 | |
5173 | return item->col; |
5174 | } |
5175 | |
5176 | /** |
5177 | * gtk_icon_view_item_activated: |
5178 | * @icon_view: A `GtkIconView` |
5179 | * @path: The `GtkTreePath` to be activated |
5180 | * |
5181 | * Activates the item determined by @path. |
5182 | **/ |
5183 | void |
5184 | gtk_icon_view_item_activated (GtkIconView *icon_view, |
5185 | GtkTreePath *path) |
5186 | { |
5187 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
5188 | g_return_if_fail (path != NULL); |
5189 | |
5190 | g_signal_emit (instance: icon_view, signal_id: icon_view_signals[ITEM_ACTIVATED], detail: 0, path); |
5191 | } |
5192 | |
5193 | /** |
5194 | * gtk_icon_view_set_item_orientation: |
5195 | * @icon_view: a `GtkIconView` |
5196 | * @orientation: the relative position of texts and icons |
5197 | * |
5198 | * Sets the ::item-orientation property which determines whether the labels |
5199 | * are drawn beside the icons instead of below. |
5200 | **/ |
5201 | void |
5202 | gtk_icon_view_set_item_orientation (GtkIconView *icon_view, |
5203 | GtkOrientation orientation) |
5204 | { |
5205 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
5206 | |
5207 | if (icon_view->priv->item_orientation != orientation) |
5208 | { |
5209 | icon_view->priv->item_orientation = orientation; |
5210 | |
5211 | if (icon_view->priv->cell_area) |
5212 | { |
5213 | if (GTK_IS_ORIENTABLE (icon_view->priv->cell_area)) |
5214 | gtk_orientable_set_orientation (GTK_ORIENTABLE (icon_view->priv->cell_area), |
5215 | orientation: icon_view->priv->item_orientation); |
5216 | |
5217 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
5218 | } |
5219 | |
5220 | gtk_icon_view_invalidate_sizes (icon_view); |
5221 | |
5222 | update_text_cell (icon_view); |
5223 | update_pixbuf_cell (icon_view); |
5224 | |
5225 | g_object_notify (G_OBJECT (icon_view), property_name: "item-orientation" ); |
5226 | } |
5227 | } |
5228 | |
5229 | /** |
5230 | * gtk_icon_view_get_item_orientation: |
5231 | * @icon_view: a `GtkIconView` |
5232 | * |
5233 | * Returns the value of the ::item-orientation property which determines |
5234 | * whether the labels are drawn beside the icons instead of below. |
5235 | * |
5236 | * Returns: the relative position of texts and icons |
5237 | **/ |
5238 | GtkOrientation |
5239 | gtk_icon_view_get_item_orientation (GtkIconView *icon_view) |
5240 | { |
5241 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), |
5242 | GTK_ORIENTATION_VERTICAL); |
5243 | |
5244 | return icon_view->priv->item_orientation; |
5245 | } |
5246 | |
5247 | /** |
5248 | * gtk_icon_view_set_columns: |
5249 | * @icon_view: a `GtkIconView` |
5250 | * @columns: the number of columns |
5251 | * |
5252 | * Sets the ::columns property which determines in how |
5253 | * many columns the icons are arranged. If @columns is |
5254 | * -1, the number of columns will be chosen automatically |
5255 | * to fill the available area. |
5256 | */ |
5257 | void |
5258 | gtk_icon_view_set_columns (GtkIconView *icon_view, |
5259 | int columns) |
5260 | { |
5261 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
5262 | |
5263 | if (icon_view->priv->columns != columns) |
5264 | { |
5265 | icon_view->priv->columns = columns; |
5266 | |
5267 | if (icon_view->priv->cell_area) |
5268 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
5269 | |
5270 | gtk_widget_queue_resize (GTK_WIDGET (icon_view)); |
5271 | |
5272 | g_object_notify (G_OBJECT (icon_view), property_name: "columns" ); |
5273 | } |
5274 | } |
5275 | |
5276 | /** |
5277 | * gtk_icon_view_get_columns: |
5278 | * @icon_view: a `GtkIconView` |
5279 | * |
5280 | * Returns the value of the ::columns property. |
5281 | * |
5282 | * Returns: the number of columns, or -1 |
5283 | */ |
5284 | int |
5285 | gtk_icon_view_get_columns (GtkIconView *icon_view) |
5286 | { |
5287 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), -1); |
5288 | |
5289 | return icon_view->priv->columns; |
5290 | } |
5291 | |
5292 | /** |
5293 | * gtk_icon_view_set_item_width: |
5294 | * @icon_view: a `GtkIconView` |
5295 | * @item_width: the width for each item |
5296 | * |
5297 | * Sets the ::item-width property which specifies the width |
5298 | * to use for each item. If it is set to -1, the icon view will |
5299 | * automatically determine a suitable item size. |
5300 | */ |
5301 | void |
5302 | gtk_icon_view_set_item_width (GtkIconView *icon_view, |
5303 | int item_width) |
5304 | { |
5305 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
5306 | |
5307 | if (icon_view->priv->item_width != item_width) |
5308 | { |
5309 | icon_view->priv->item_width = item_width; |
5310 | |
5311 | if (icon_view->priv->cell_area) |
5312 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
5313 | |
5314 | gtk_icon_view_invalidate_sizes (icon_view); |
5315 | |
5316 | update_text_cell (icon_view); |
5317 | |
5318 | g_object_notify (G_OBJECT (icon_view), property_name: "item-width" ); |
5319 | } |
5320 | } |
5321 | |
5322 | /** |
5323 | * gtk_icon_view_get_item_width: |
5324 | * @icon_view: a `GtkIconView` |
5325 | * |
5326 | * Returns the value of the ::item-width property. |
5327 | * |
5328 | * Returns: the width of a single item, or -1 |
5329 | */ |
5330 | int |
5331 | gtk_icon_view_get_item_width (GtkIconView *icon_view) |
5332 | { |
5333 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), -1); |
5334 | |
5335 | return icon_view->priv->item_width; |
5336 | } |
5337 | |
5338 | |
5339 | /** |
5340 | * gtk_icon_view_set_spacing: |
5341 | * @icon_view: a `GtkIconView` |
5342 | * @spacing: the spacing |
5343 | * |
5344 | * Sets the ::spacing property which specifies the space |
5345 | * which is inserted between the cells (i.e. the icon and |
5346 | * the text) of an item. |
5347 | */ |
5348 | void |
5349 | gtk_icon_view_set_spacing (GtkIconView *icon_view, |
5350 | int spacing) |
5351 | { |
5352 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
5353 | |
5354 | if (icon_view->priv->spacing != spacing) |
5355 | { |
5356 | icon_view->priv->spacing = spacing; |
5357 | |
5358 | if (icon_view->priv->cell_area) |
5359 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
5360 | |
5361 | gtk_icon_view_invalidate_sizes (icon_view); |
5362 | |
5363 | g_object_notify (G_OBJECT (icon_view), property_name: "spacing" ); |
5364 | } |
5365 | } |
5366 | |
5367 | /** |
5368 | * gtk_icon_view_get_spacing: |
5369 | * @icon_view: a `GtkIconView` |
5370 | * |
5371 | * Returns the value of the ::spacing property. |
5372 | * |
5373 | * Returns: the space between cells |
5374 | */ |
5375 | int |
5376 | gtk_icon_view_get_spacing (GtkIconView *icon_view) |
5377 | { |
5378 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), -1); |
5379 | |
5380 | return icon_view->priv->spacing; |
5381 | } |
5382 | |
5383 | /** |
5384 | * gtk_icon_view_set_row_spacing: |
5385 | * @icon_view: a `GtkIconView` |
5386 | * @row_spacing: the row spacing |
5387 | * |
5388 | * Sets the ::row-spacing property which specifies the space |
5389 | * which is inserted between the rows of the icon view. |
5390 | */ |
5391 | void |
5392 | gtk_icon_view_set_row_spacing (GtkIconView *icon_view, |
5393 | int row_spacing) |
5394 | { |
5395 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
5396 | |
5397 | if (icon_view->priv->row_spacing != row_spacing) |
5398 | { |
5399 | icon_view->priv->row_spacing = row_spacing; |
5400 | |
5401 | if (icon_view->priv->cell_area) |
5402 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
5403 | |
5404 | gtk_icon_view_invalidate_sizes (icon_view); |
5405 | |
5406 | g_object_notify (G_OBJECT (icon_view), property_name: "row-spacing" ); |
5407 | } |
5408 | } |
5409 | |
5410 | /** |
5411 | * gtk_icon_view_get_row_spacing: |
5412 | * @icon_view: a `GtkIconView` |
5413 | * |
5414 | * Returns the value of the ::row-spacing property. |
5415 | * |
5416 | * Returns: the space between rows |
5417 | */ |
5418 | int |
5419 | gtk_icon_view_get_row_spacing (GtkIconView *icon_view) |
5420 | { |
5421 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), -1); |
5422 | |
5423 | return icon_view->priv->row_spacing; |
5424 | } |
5425 | |
5426 | /** |
5427 | * gtk_icon_view_set_column_spacing: |
5428 | * @icon_view: a `GtkIconView` |
5429 | * @column_spacing: the column spacing |
5430 | * |
5431 | * Sets the ::column-spacing property which specifies the space |
5432 | * which is inserted between the columns of the icon view. |
5433 | */ |
5434 | void |
5435 | gtk_icon_view_set_column_spacing (GtkIconView *icon_view, |
5436 | int column_spacing) |
5437 | { |
5438 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
5439 | |
5440 | if (icon_view->priv->column_spacing != column_spacing) |
5441 | { |
5442 | icon_view->priv->column_spacing = column_spacing; |
5443 | |
5444 | if (icon_view->priv->cell_area) |
5445 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
5446 | |
5447 | gtk_icon_view_invalidate_sizes (icon_view); |
5448 | |
5449 | g_object_notify (G_OBJECT (icon_view), property_name: "column-spacing" ); |
5450 | } |
5451 | } |
5452 | |
5453 | /** |
5454 | * gtk_icon_view_get_column_spacing: |
5455 | * @icon_view: a `GtkIconView` |
5456 | * |
5457 | * Returns the value of the ::column-spacing property. |
5458 | * |
5459 | * Returns: the space between columns |
5460 | */ |
5461 | int |
5462 | gtk_icon_view_get_column_spacing (GtkIconView *icon_view) |
5463 | { |
5464 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), -1); |
5465 | |
5466 | return icon_view->priv->column_spacing; |
5467 | } |
5468 | |
5469 | /** |
5470 | * gtk_icon_view_set_margin: |
5471 | * @icon_view: a `GtkIconView` |
5472 | * @margin: the margin |
5473 | * |
5474 | * Sets the ::margin property which specifies the space |
5475 | * which is inserted at the top, bottom, left and right |
5476 | * of the icon view. |
5477 | */ |
5478 | void |
5479 | gtk_icon_view_set_margin (GtkIconView *icon_view, |
5480 | int margin) |
5481 | { |
5482 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
5483 | |
5484 | if (icon_view->priv->margin != margin) |
5485 | { |
5486 | icon_view->priv->margin = margin; |
5487 | |
5488 | if (icon_view->priv->cell_area) |
5489 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
5490 | |
5491 | gtk_icon_view_invalidate_sizes (icon_view); |
5492 | |
5493 | g_object_notify (G_OBJECT (icon_view), property_name: "margin" ); |
5494 | } |
5495 | } |
5496 | |
5497 | /** |
5498 | * gtk_icon_view_get_margin: |
5499 | * @icon_view: a `GtkIconView` |
5500 | * |
5501 | * Returns the value of the ::margin property. |
5502 | * |
5503 | * Returns: the space at the borders |
5504 | */ |
5505 | int |
5506 | gtk_icon_view_get_margin (GtkIconView *icon_view) |
5507 | { |
5508 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), -1); |
5509 | |
5510 | return icon_view->priv->margin; |
5511 | } |
5512 | |
5513 | /** |
5514 | * gtk_icon_view_set_item_padding: |
5515 | * @icon_view: a `GtkIconView` |
5516 | * @item_padding: the item padding |
5517 | * |
5518 | * Sets the `GtkIconView`:item-padding property which specifies the padding |
5519 | * around each of the icon view’s items. |
5520 | */ |
5521 | void |
5522 | gtk_icon_view_set_item_padding (GtkIconView *icon_view, |
5523 | int item_padding) |
5524 | { |
5525 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
5526 | |
5527 | if (icon_view->priv->item_padding != item_padding) |
5528 | { |
5529 | icon_view->priv->item_padding = item_padding; |
5530 | |
5531 | if (icon_view->priv->cell_area) |
5532 | gtk_cell_area_stop_editing (area: icon_view->priv->cell_area, TRUE); |
5533 | |
5534 | gtk_icon_view_invalidate_sizes (icon_view); |
5535 | |
5536 | g_object_notify (G_OBJECT (icon_view), property_name: "item-padding" ); |
5537 | } |
5538 | } |
5539 | |
5540 | /** |
5541 | * gtk_icon_view_get_item_padding: |
5542 | * @icon_view: a `GtkIconView` |
5543 | * |
5544 | * Returns the value of the ::item-padding property. |
5545 | * |
5546 | * Returns: the padding around items |
5547 | */ |
5548 | int |
5549 | gtk_icon_view_get_item_padding (GtkIconView *icon_view) |
5550 | { |
5551 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), -1); |
5552 | |
5553 | return icon_view->priv->item_padding; |
5554 | } |
5555 | |
5556 | /* Get/set whether drag_motion requested the drag data and |
5557 | * drag_data_received should thus not actually insert the data, |
5558 | * since the data doesn’t result from a drop. |
5559 | */ |
5560 | static void |
5561 | set_status_pending (GdkDrop *drop, |
5562 | GdkDragAction suggested_action) |
5563 | { |
5564 | g_object_set_data (G_OBJECT (drop), |
5565 | I_("gtk-icon-view-status-pending" ), |
5566 | GINT_TO_POINTER (suggested_action)); |
5567 | } |
5568 | |
5569 | static GdkDragAction |
5570 | get_status_pending (GdkDrop *drop) |
5571 | { |
5572 | return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (drop), |
5573 | "gtk-icon-view-status-pending" )); |
5574 | } |
5575 | |
5576 | static void |
5577 | unset_reorderable (GtkIconView *icon_view) |
5578 | { |
5579 | if (icon_view->priv->reorderable) |
5580 | { |
5581 | icon_view->priv->reorderable = FALSE; |
5582 | g_object_notify (G_OBJECT (icon_view), property_name: "reorderable" ); |
5583 | } |
5584 | } |
5585 | |
5586 | typedef struct |
5587 | { |
5588 | GtkTreeRowReference *dest_row; |
5589 | gboolean empty_view_drop; |
5590 | gboolean drop_append_mode; |
5591 | } DestRow; |
5592 | |
5593 | static void |
5594 | dest_row_free (gpointer data) |
5595 | { |
5596 | DestRow *dr = (DestRow *)data; |
5597 | |
5598 | gtk_tree_row_reference_free (reference: dr->dest_row); |
5599 | g_free (mem: dr); |
5600 | } |
5601 | |
5602 | static void |
5603 | set_dest_row (GdkDrop *drop, |
5604 | GtkTreeModel *model, |
5605 | GtkTreePath *dest_row, |
5606 | gboolean empty_view_drop, |
5607 | gboolean drop_append_mode) |
5608 | { |
5609 | DestRow *dr; |
5610 | |
5611 | if (!dest_row) |
5612 | { |
5613 | g_object_set_data_full (G_OBJECT (drop), |
5614 | I_("gtk-icon-view-dest-row" ), |
5615 | NULL, NULL); |
5616 | return; |
5617 | } |
5618 | |
5619 | dr = g_new0 (DestRow, 1); |
5620 | |
5621 | dr->dest_row = gtk_tree_row_reference_new (model, path: dest_row); |
5622 | dr->empty_view_drop = empty_view_drop; |
5623 | dr->drop_append_mode = drop_append_mode; |
5624 | g_object_set_data_full (G_OBJECT (drop), |
5625 | I_("gtk-icon-view-dest-row" ), |
5626 | data: dr, destroy: (GDestroyNotify) dest_row_free); |
5627 | } |
5628 | |
5629 | static GtkTreePath* |
5630 | get_dest_row (GdkDrop *drop) |
5631 | { |
5632 | DestRow *dr; |
5633 | |
5634 | dr = g_object_get_data (G_OBJECT (drop), key: "gtk-icon-view-dest-row" ); |
5635 | |
5636 | if (dr) |
5637 | { |
5638 | GtkTreePath *path = NULL; |
5639 | |
5640 | if (dr->dest_row) |
5641 | path = gtk_tree_row_reference_get_path (reference: dr->dest_row); |
5642 | else if (dr->empty_view_drop) |
5643 | path = gtk_tree_path_new_from_indices (first_index: 0, -1); |
5644 | else |
5645 | path = NULL; |
5646 | |
5647 | if (path && dr->drop_append_mode) |
5648 | gtk_tree_path_next (path); |
5649 | |
5650 | return path; |
5651 | } |
5652 | else |
5653 | return NULL; |
5654 | } |
5655 | |
5656 | static gboolean |
5657 | check_model_dnd (GtkTreeModel *model, |
5658 | GType required_iface, |
5659 | const char *signal) |
5660 | { |
5661 | if (model == NULL || !G_TYPE_CHECK_INSTANCE_TYPE ((model), required_iface)) |
5662 | { |
5663 | g_warning ("You must override the default '%s' handler " |
5664 | "on GtkIconView when using models that don't support " |
5665 | "the %s interface and enabling drag-and-drop. The simplest way to do this " |
5666 | "is to connect to '%s' and call " |
5667 | "g_signal_stop_emission_by_name() in your signal handler to prevent " |
5668 | "the default handler from running. Look at the source code " |
5669 | "for the default handler in gtkiconview.c to get an idea what " |
5670 | "your handler should do. (gtkiconview.c is in the GTK source " |
5671 | "code.) If you're using GTK from a language other than C, " |
5672 | "there may be a more natural way to override default handlers, e.g. via derivation." , |
5673 | signal, g_type_name (required_iface), signal); |
5674 | return FALSE; |
5675 | } |
5676 | else |
5677 | return TRUE; |
5678 | } |
5679 | |
5680 | static void |
5681 | remove_scroll_timeout (GtkIconView *icon_view) |
5682 | { |
5683 | if (icon_view->priv->scroll_timeout_id != 0) |
5684 | { |
5685 | g_source_remove (tag: icon_view->priv->scroll_timeout_id); |
5686 | |
5687 | icon_view->priv->scroll_timeout_id = 0; |
5688 | } |
5689 | } |
5690 | |
5691 | static void |
5692 | gtk_icon_view_autoscroll (GtkIconView *icon_view) |
5693 | { |
5694 | int px, py, width, height; |
5695 | int hoffset, voffset; |
5696 | |
5697 | px = icon_view->priv->event_last_x; |
5698 | py = icon_view->priv->event_last_y; |
5699 | |
5700 | width = gtk_widget_get_width (GTK_WIDGET (icon_view)); |
5701 | height = gtk_widget_get_height (GTK_WIDGET (icon_view)); |
5702 | |
5703 | /* see if we are near the edge. */ |
5704 | voffset = py - 2 * SCROLL_EDGE_SIZE; |
5705 | if (voffset > 0) |
5706 | voffset = MAX (py - (height - 2 * SCROLL_EDGE_SIZE), 0); |
5707 | |
5708 | hoffset = px - 2 * SCROLL_EDGE_SIZE; |
5709 | if (hoffset > 0) |
5710 | hoffset = MAX (px - (width - 2 * SCROLL_EDGE_SIZE), 0); |
5711 | |
5712 | if (voffset != 0) |
5713 | gtk_adjustment_set_value (adjustment: icon_view->priv->vadjustment, |
5714 | value: gtk_adjustment_get_value (adjustment: icon_view->priv->vadjustment) + voffset); |
5715 | |
5716 | if (hoffset != 0) |
5717 | gtk_adjustment_set_value (adjustment: icon_view->priv->hadjustment, |
5718 | value: gtk_adjustment_get_value (adjustment: icon_view->priv->hadjustment) + hoffset); |
5719 | } |
5720 | |
5721 | static gboolean |
5722 | drag_scroll_timeout (gpointer data) |
5723 | { |
5724 | gtk_icon_view_autoscroll (icon_view: data); |
5725 | |
5726 | return TRUE; |
5727 | } |
5728 | |
5729 | static GdkDragAction |
5730 | gtk_icon_view_get_action (GtkWidget *widget, |
5731 | GdkDrop *drop) |
5732 | { |
5733 | GtkIconView *iconview = GTK_ICON_VIEW (widget); |
5734 | GdkDrag *drag = gdk_drop_get_drag (self: drop); |
5735 | GdkDragAction actions; |
5736 | |
5737 | actions = gdk_drop_get_actions (self: drop); |
5738 | |
5739 | if (drag == iconview->priv->drag && |
5740 | actions & GDK_ACTION_MOVE) |
5741 | return GDK_ACTION_MOVE; |
5742 | |
5743 | if (actions & GDK_ACTION_COPY) |
5744 | return GDK_ACTION_COPY; |
5745 | |
5746 | if (actions & GDK_ACTION_MOVE) |
5747 | return GDK_ACTION_MOVE; |
5748 | |
5749 | if (actions & GDK_ACTION_LINK) |
5750 | return GDK_ACTION_LINK; |
5751 | |
5752 | return 0; |
5753 | } |
5754 | |
5755 | static gboolean |
5756 | set_destination (GtkIconView *icon_view, |
5757 | GdkDrop *drop, |
5758 | GtkDropTargetAsync *dest, |
5759 | int x, |
5760 | int y, |
5761 | GdkDragAction *suggested_action, |
5762 | GType *target) |
5763 | { |
5764 | GtkWidget *widget; |
5765 | GtkTreePath *path = NULL; |
5766 | GtkIconViewDropPosition pos; |
5767 | GtkIconViewDropPosition old_pos; |
5768 | GtkTreePath *old_dest_path = NULL; |
5769 | GdkContentFormats *formats; |
5770 | gboolean can_drop = FALSE; |
5771 | |
5772 | widget = GTK_WIDGET (icon_view); |
5773 | |
5774 | *suggested_action = 0; |
5775 | *target = G_TYPE_INVALID; |
5776 | |
5777 | if (!icon_view->priv->dest_set) |
5778 | { |
5779 | /* someone unset us as a drag dest, note that if |
5780 | * we return FALSE drag_leave isn't called |
5781 | */ |
5782 | |
5783 | gtk_icon_view_set_drag_dest_item (icon_view, |
5784 | NULL, |
5785 | pos: GTK_ICON_VIEW_DROP_LEFT); |
5786 | |
5787 | remove_scroll_timeout (GTK_ICON_VIEW (widget)); |
5788 | |
5789 | return FALSE; /* no longer a drop site */ |
5790 | } |
5791 | |
5792 | formats = gtk_drop_target_async_get_formats (self: dest); |
5793 | *target = gdk_content_formats_match_gtype (first: formats, second: formats); |
5794 | if (*target == G_TYPE_INVALID) |
5795 | return FALSE; |
5796 | |
5797 | if (!gtk_icon_view_get_dest_item_at_pos (icon_view, drag_x: x, drag_y: y, path: &path, pos: &pos)) |
5798 | { |
5799 | int n_children; |
5800 | GtkTreeModel *model; |
5801 | |
5802 | /* the row got dropped on empty space, let's setup a special case |
5803 | */ |
5804 | |
5805 | if (path) |
5806 | gtk_tree_path_free (path); |
5807 | |
5808 | model = gtk_icon_view_get_model (icon_view); |
5809 | |
5810 | n_children = gtk_tree_model_iter_n_children (tree_model: model, NULL); |
5811 | if (n_children) |
5812 | { |
5813 | pos = GTK_ICON_VIEW_DROP_BELOW; |
5814 | path = gtk_tree_path_new_from_indices (first_index: n_children - 1, -1); |
5815 | } |
5816 | else |
5817 | { |
5818 | pos = GTK_ICON_VIEW_DROP_ABOVE; |
5819 | path = gtk_tree_path_new_from_indices (first_index: 0, -1); |
5820 | } |
5821 | |
5822 | can_drop = TRUE; |
5823 | |
5824 | goto out; |
5825 | } |
5826 | |
5827 | g_assert (path); |
5828 | |
5829 | gtk_icon_view_get_drag_dest_item (icon_view, |
5830 | path: &old_dest_path, |
5831 | pos: &old_pos); |
5832 | |
5833 | if (old_dest_path) |
5834 | gtk_tree_path_free (path: old_dest_path); |
5835 | |
5836 | if (TRUE /* FIXME if the location droppable predicate */) |
5837 | { |
5838 | can_drop = TRUE; |
5839 | } |
5840 | |
5841 | out: |
5842 | if (can_drop) |
5843 | { |
5844 | *suggested_action = gtk_icon_view_get_action (widget, drop); |
5845 | |
5846 | gtk_icon_view_set_drag_dest_item (GTK_ICON_VIEW (widget), |
5847 | path, pos); |
5848 | } |
5849 | else |
5850 | { |
5851 | /* can't drop here */ |
5852 | gtk_icon_view_set_drag_dest_item (GTK_ICON_VIEW (widget), |
5853 | NULL, |
5854 | pos: GTK_ICON_VIEW_DROP_LEFT); |
5855 | } |
5856 | |
5857 | if (path) |
5858 | gtk_tree_path_free (path); |
5859 | |
5860 | return TRUE; |
5861 | } |
5862 | |
5863 | static GtkTreePath* |
5864 | get_logical_destination (GtkIconView *icon_view, |
5865 | gboolean *drop_append_mode) |
5866 | { |
5867 | /* adjust path to point to the row the drop goes in front of */ |
5868 | GtkTreePath *path = NULL; |
5869 | GtkIconViewDropPosition pos; |
5870 | |
5871 | *drop_append_mode = FALSE; |
5872 | |
5873 | gtk_icon_view_get_drag_dest_item (icon_view, path: &path, pos: &pos); |
5874 | |
5875 | if (path == NULL) |
5876 | return NULL; |
5877 | |
5878 | if (pos == GTK_ICON_VIEW_DROP_RIGHT || |
5879 | pos == GTK_ICON_VIEW_DROP_BELOW) |
5880 | { |
5881 | GtkTreeIter iter; |
5882 | GtkTreeModel *model = icon_view->priv->model; |
5883 | |
5884 | if (!gtk_tree_model_get_iter (tree_model: model, iter: &iter, path) || |
5885 | !gtk_tree_model_iter_next (tree_model: model, iter: &iter)) |
5886 | *drop_append_mode = TRUE; |
5887 | else |
5888 | { |
5889 | *drop_append_mode = FALSE; |
5890 | gtk_tree_path_next (path); |
5891 | } |
5892 | } |
5893 | |
5894 | return path; |
5895 | } |
5896 | |
5897 | static gboolean |
5898 | gtk_icon_view_maybe_begin_drag (GtkIconView *icon_view, |
5899 | double x, |
5900 | double y, |
5901 | GdkDevice *device) |
5902 | { |
5903 | GtkTreePath *path = NULL; |
5904 | GtkTreeModel *model; |
5905 | gboolean retval = FALSE; |
5906 | GdkContentProvider *content; |
5907 | GdkPaintable *icon; |
5908 | GtkIconViewItem *item; |
5909 | GdkSurface *surface; |
5910 | GdkDrag *drag; |
5911 | |
5912 | if (!icon_view->priv->source_set) |
5913 | goto out; |
5914 | |
5915 | if (icon_view->priv->pressed_button < 0) |
5916 | goto out; |
5917 | |
5918 | if (!gtk_drag_check_threshold_double (GTK_WIDGET (icon_view), |
5919 | start_x: icon_view->priv->press_start_x, |
5920 | start_y: icon_view->priv->press_start_y, |
5921 | current_x: x, current_y: y)) |
5922 | goto out; |
5923 | |
5924 | model = gtk_icon_view_get_model (icon_view); |
5925 | |
5926 | if (model == NULL) |
5927 | goto out; |
5928 | |
5929 | icon_view->priv->pressed_button = -1; |
5930 | |
5931 | item = _gtk_icon_view_get_item_at_coords (icon_view, |
5932 | x: icon_view->priv->press_start_x, |
5933 | y: icon_view->priv->press_start_y, |
5934 | TRUE, |
5935 | NULL); |
5936 | |
5937 | if (item == NULL) |
5938 | goto out; |
5939 | |
5940 | path = gtk_tree_path_new_from_indices (first_index: item->index, -1); |
5941 | |
5942 | if (!GTK_IS_TREE_DRAG_SOURCE (model) || |
5943 | !gtk_tree_drag_source_row_draggable (GTK_TREE_DRAG_SOURCE (model), path)) |
5944 | goto out; |
5945 | |
5946 | /* FIXME Check whether we're a start button, if not return FALSE and |
5947 | * free path |
5948 | */ |
5949 | |
5950 | /* Now we can begin the drag */ |
5951 | |
5952 | retval = TRUE; |
5953 | |
5954 | surface = gtk_native_get_surface (self: gtk_widget_get_native (GTK_WIDGET (icon_view))); |
5955 | |
5956 | content = gtk_icon_view_drag_data_get (icon_view, source_row: path); |
5957 | if (content == NULL) |
5958 | goto out; |
5959 | |
5960 | drag = gdk_drag_begin (surface, |
5961 | device, |
5962 | content, |
5963 | actions: icon_view->priv->source_actions, |
5964 | dx: icon_view->priv->press_start_x, |
5965 | dy: icon_view->priv->press_start_y); |
5966 | |
5967 | g_object_unref (object: content); |
5968 | |
5969 | g_signal_connect (drag, "dnd-finished" , G_CALLBACK (gtk_icon_view_dnd_finished_cb), icon_view); |
5970 | |
5971 | icon_view->priv->source_item = gtk_tree_row_reference_new (model, path); |
5972 | |
5973 | x = icon_view->priv->press_start_x - item->cell_area.x + icon_view->priv->item_padding; |
5974 | y = icon_view->priv->press_start_y - item->cell_area.y + icon_view->priv->item_padding; |
5975 | |
5976 | icon = gtk_icon_view_create_drag_icon (icon_view, path); |
5977 | gtk_drag_icon_set_from_paintable (drag, paintable: icon, hot_x: x, hot_y: y); |
5978 | g_object_unref (object: icon); |
5979 | |
5980 | icon_view->priv->drag = drag; |
5981 | |
5982 | g_object_unref (object: drag); |
5983 | |
5984 | out: |
5985 | if (path) |
5986 | gtk_tree_path_free (path); |
5987 | |
5988 | return retval; |
5989 | } |
5990 | |
5991 | /* Source side drag signals */ |
5992 | static GdkContentProvider * |
5993 | gtk_icon_view_drag_data_get (GtkIconView *icon_view, |
5994 | GtkTreePath *source_row) |
5995 | { |
5996 | GdkContentProvider *content; |
5997 | GtkTreeModel *model; |
5998 | |
5999 | model = gtk_icon_view_get_model (icon_view); |
6000 | |
6001 | if (model == NULL) |
6002 | return NULL; |
6003 | |
6004 | if (!icon_view->priv->source_set) |
6005 | return NULL; |
6006 | |
6007 | /* We can implement the GTK_TREE_MODEL_ROW target generically for |
6008 | * any model; for DragSource models there are some other formats |
6009 | * we also support. |
6010 | */ |
6011 | |
6012 | if (GTK_IS_TREE_DRAG_SOURCE (model)) |
6013 | content = gtk_tree_drag_source_drag_data_get (GTK_TREE_DRAG_SOURCE (model), path: source_row); |
6014 | else |
6015 | content = NULL; |
6016 | |
6017 | /* If drag_data_get does nothing, try providing row data. */ |
6018 | if (content == NULL) |
6019 | content = gtk_tree_create_row_drag_content (tree_model: model, path: source_row); |
6020 | |
6021 | return content; |
6022 | } |
6023 | |
6024 | static void |
6025 | gtk_icon_view_dnd_finished_cb (GdkDrag *drag, |
6026 | GtkWidget *widget) |
6027 | { |
6028 | GtkTreeModel *model; |
6029 | GtkIconView *icon_view; |
6030 | GtkTreePath *source_row; |
6031 | |
6032 | if (gdk_drag_get_selected_action (drag) != GDK_ACTION_MOVE) |
6033 | return; |
6034 | |
6035 | icon_view = GTK_ICON_VIEW (widget); |
6036 | model = gtk_icon_view_get_model (icon_view); |
6037 | |
6038 | if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_SOURCE, signal: "drag-data-delete" )) |
6039 | return; |
6040 | |
6041 | if (!icon_view->priv->source_set) |
6042 | return; |
6043 | |
6044 | source_row = gtk_tree_row_reference_get_path (reference: icon_view->priv->source_item); |
6045 | if (source_row == NULL) |
6046 | return; |
6047 | |
6048 | gtk_tree_drag_source_drag_data_delete (GTK_TREE_DRAG_SOURCE (model), path: source_row); |
6049 | |
6050 | gtk_tree_path_free (path: source_row); |
6051 | |
6052 | g_clear_pointer (&icon_view->priv->source_item, gtk_tree_row_reference_free); |
6053 | icon_view->priv->drag = NULL; |
6054 | } |
6055 | |
6056 | /* Target side drag signals */ |
6057 | static void |
6058 | gtk_icon_view_drag_leave (GtkDropTargetAsync *dest, |
6059 | GdkDrop *drop, |
6060 | GtkIconView *icon_view) |
6061 | { |
6062 | /* unset any highlight row */ |
6063 | gtk_icon_view_set_drag_dest_item (icon_view, |
6064 | NULL, |
6065 | pos: GTK_ICON_VIEW_DROP_LEFT); |
6066 | |
6067 | remove_scroll_timeout (icon_view); |
6068 | } |
6069 | |
6070 | static GdkDragAction |
6071 | gtk_icon_view_drag_motion (GtkDropTargetAsync *dest, |
6072 | GdkDrop *drop, |
6073 | double x, |
6074 | double y, |
6075 | GtkIconView *icon_view) |
6076 | { |
6077 | GtkTreePath *path = NULL; |
6078 | GtkIconViewDropPosition pos; |
6079 | GdkDragAction suggested_action = 0; |
6080 | GType target; |
6081 | gboolean empty; |
6082 | GdkDragAction result; |
6083 | |
6084 | if (!set_destination (icon_view, drop, dest, x, y, suggested_action: &suggested_action, target: &target)) |
6085 | return 0; |
6086 | |
6087 | gtk_icon_view_get_drag_dest_item (icon_view, path: &path, pos: &pos); |
6088 | |
6089 | /* we only know this *after* set_desination_row */ |
6090 | empty = icon_view->priv->empty_view_drop; |
6091 | |
6092 | if (path == NULL && !empty) |
6093 | { |
6094 | /* Can't drop here. */ |
6095 | result = 0; |
6096 | } |
6097 | else |
6098 | { |
6099 | if (icon_view->priv->scroll_timeout_id == 0) |
6100 | { |
6101 | icon_view->priv->scroll_timeout_id = g_timeout_add (interval: 50, function: drag_scroll_timeout, data: icon_view); |
6102 | gdk_source_set_static_name_by_id (tag: icon_view->priv->scroll_timeout_id, name: "[gtk] drag_scroll_timeout" ); |
6103 | } |
6104 | |
6105 | if (target == GTK_TYPE_TREE_ROW_DATA) |
6106 | { |
6107 | /* Request data so we can use the source row when |
6108 | * determining whether to accept the drop |
6109 | */ |
6110 | set_status_pending (drop, suggested_action); |
6111 | gdk_drop_read_value_async (self: drop, type: target, G_PRIORITY_DEFAULT, NULL, callback: gtk_icon_view_drag_data_received, user_data: icon_view); |
6112 | } |
6113 | else |
6114 | { |
6115 | set_status_pending (drop, suggested_action: 0); |
6116 | } |
6117 | result = suggested_action; |
6118 | } |
6119 | |
6120 | if (path) |
6121 | gtk_tree_path_free (path); |
6122 | |
6123 | return result; |
6124 | } |
6125 | |
6126 | static gboolean |
6127 | gtk_icon_view_drag_drop (GtkDropTargetAsync *dest, |
6128 | GdkDrop *drop, |
6129 | double x, |
6130 | double y, |
6131 | GtkIconView *icon_view) |
6132 | { |
6133 | GtkTreePath *path; |
6134 | GdkDragAction suggested_action = 0; |
6135 | GType target = G_TYPE_INVALID; |
6136 | GtkTreeModel *model; |
6137 | gboolean drop_append_mode; |
6138 | |
6139 | model = gtk_icon_view_get_model (icon_view); |
6140 | |
6141 | remove_scroll_timeout (icon_view); |
6142 | |
6143 | if (!icon_view->priv->dest_set) |
6144 | return FALSE; |
6145 | |
6146 | if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_DEST, signal: "drop" )) |
6147 | return FALSE; |
6148 | |
6149 | if (!set_destination (icon_view, drop, dest, x, y, suggested_action: &suggested_action, target: &target)) |
6150 | return FALSE; |
6151 | |
6152 | path = get_logical_destination (icon_view, drop_append_mode: &drop_append_mode); |
6153 | |
6154 | if (target != G_TYPE_INVALID && path != NULL) |
6155 | { |
6156 | /* in case a motion had requested drag data, change things so we |
6157 | * treat drag data receives as a drop. |
6158 | */ |
6159 | set_status_pending (drop, suggested_action: 0); |
6160 | set_dest_row (drop, model, dest_row: path, |
6161 | empty_view_drop: icon_view->priv->empty_view_drop, drop_append_mode); |
6162 | } |
6163 | |
6164 | if (path) |
6165 | gtk_tree_path_free (path); |
6166 | |
6167 | /* Unset this thing */ |
6168 | gtk_icon_view_set_drag_dest_item (icon_view, NULL, pos: GTK_ICON_VIEW_DROP_LEFT); |
6169 | |
6170 | if (target != G_TYPE_INVALID) |
6171 | { |
6172 | gdk_drop_read_value_async (self: drop, type: target, G_PRIORITY_DEFAULT, NULL, callback: gtk_icon_view_drag_data_received, user_data: icon_view); |
6173 | return TRUE; |
6174 | } |
6175 | else |
6176 | return FALSE; |
6177 | } |
6178 | |
6179 | static void |
6180 | gtk_icon_view_drag_data_received (GObject *source, |
6181 | GAsyncResult *result, |
6182 | gpointer data) |
6183 | { |
6184 | GtkIconView *icon_view = data; |
6185 | GdkDrop *drop = GDK_DROP (source); |
6186 | GtkTreePath *path; |
6187 | GtkTreeModel *model; |
6188 | GtkTreePath *dest_row; |
6189 | GdkDragAction suggested_action; |
6190 | gboolean drop_append_mode; |
6191 | const GValue *value; |
6192 | |
6193 | value = gdk_drop_read_value_finish (self: drop, result, NULL); |
6194 | if (!value) |
6195 | return; |
6196 | |
6197 | model = gtk_icon_view_get_model (icon_view); |
6198 | |
6199 | if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_DEST, signal: "drag-data-received" )) |
6200 | return; |
6201 | |
6202 | if (!icon_view->priv->dest_set) |
6203 | return; |
6204 | |
6205 | suggested_action = get_status_pending (drop); |
6206 | |
6207 | if (suggested_action) |
6208 | { |
6209 | /* We are getting this data due to a request in drag_motion, |
6210 | * rather than due to a request in drag_drop, so we are just |
6211 | * supposed to call drag_status, not actually paste in the |
6212 | * data. |
6213 | */ |
6214 | path = get_logical_destination (icon_view, drop_append_mode: &drop_append_mode); |
6215 | |
6216 | if (path == NULL) |
6217 | suggested_action = 0; |
6218 | |
6219 | if (suggested_action) |
6220 | { |
6221 | if (!gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (model), |
6222 | dest_path: path, |
6223 | value)) |
6224 | suggested_action = 0; |
6225 | } |
6226 | |
6227 | if (path) |
6228 | gtk_tree_path_free (path); |
6229 | |
6230 | /* If you can't drop, remove user drop indicator until the next motion */ |
6231 | if (suggested_action == 0) |
6232 | gtk_icon_view_set_drag_dest_item (icon_view, |
6233 | NULL, |
6234 | pos: GTK_ICON_VIEW_DROP_LEFT); |
6235 | return; |
6236 | } |
6237 | |
6238 | |
6239 | dest_row = get_dest_row (drop); |
6240 | |
6241 | if (dest_row == NULL) |
6242 | return; |
6243 | |
6244 | suggested_action = gtk_icon_view_get_action (GTK_WIDGET (icon_view), drop); |
6245 | |
6246 | if (suggested_action && |
6247 | !gtk_tree_drag_dest_drag_data_received (GTK_TREE_DRAG_DEST (model), |
6248 | dest: dest_row, |
6249 | value)) |
6250 | suggested_action = 0; |
6251 | |
6252 | gdk_drop_finish (self: drop, action: suggested_action); |
6253 | |
6254 | gtk_tree_path_free (path: dest_row); |
6255 | |
6256 | /* drop dest_row */ |
6257 | set_dest_row (drop, NULL, NULL, FALSE, FALSE); |
6258 | } |
6259 | |
6260 | /* Drag-and-Drop support */ |
6261 | |
6262 | /** |
6263 | * gtk_icon_view_enable_model_drag_source: |
6264 | * @icon_view: a `GtkIconView` |
6265 | * @start_button_mask: Mask of allowed buttons to start drag |
6266 | * @formats: the formats that the drag will support |
6267 | * @actions: the bitmask of possible actions for a drag from this |
6268 | * widget |
6269 | * |
6270 | * Turns @icon_view into a drag source for automatic DND. Calling this |
6271 | * method sets `GtkIconView`:reorderable to %FALSE. |
6272 | **/ |
6273 | void |
6274 | gtk_icon_view_enable_model_drag_source (GtkIconView *icon_view, |
6275 | GdkModifierType start_button_mask, |
6276 | GdkContentFormats *formats, |
6277 | GdkDragAction actions) |
6278 | { |
6279 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
6280 | |
6281 | icon_view->priv->source_formats = gdk_content_formats_ref (formats); |
6282 | icon_view->priv->source_actions = actions; |
6283 | |
6284 | icon_view->priv->source_set = TRUE; |
6285 | |
6286 | unset_reorderable (icon_view); |
6287 | } |
6288 | |
6289 | /** |
6290 | * gtk_icon_view_enable_model_drag_dest: |
6291 | * @icon_view: a `GtkIconView` |
6292 | * @formats: the formats that the drag will support |
6293 | * @actions: the bitmask of possible actions for a drag to this |
6294 | * widget |
6295 | * |
6296 | * Turns @icon_view into a drop destination for automatic DND. Calling this |
6297 | * method sets `GtkIconView`:reorderable to %FALSE. |
6298 | **/ |
6299 | void |
6300 | gtk_icon_view_enable_model_drag_dest (GtkIconView *icon_view, |
6301 | GdkContentFormats *formats, |
6302 | GdkDragAction actions) |
6303 | { |
6304 | GtkCssNode *widget_node; |
6305 | |
6306 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
6307 | |
6308 | icon_view->priv->dest = gtk_drop_target_async_new (formats: gdk_content_formats_ref (formats), actions); |
6309 | g_signal_connect (icon_view->priv->dest, "drag-leave" , G_CALLBACK (gtk_icon_view_drag_leave), icon_view); |
6310 | g_signal_connect (icon_view->priv->dest, "drag-enter" , G_CALLBACK (gtk_icon_view_drag_motion), icon_view); |
6311 | g_signal_connect (icon_view->priv->dest, "drag-motion" , G_CALLBACK (gtk_icon_view_drag_motion), icon_view); |
6312 | g_signal_connect (icon_view->priv->dest, "drop" , G_CALLBACK (gtk_icon_view_drag_drop), icon_view); |
6313 | gtk_widget_add_controller (GTK_WIDGET (icon_view), GTK_EVENT_CONTROLLER (icon_view->priv->dest)); |
6314 | |
6315 | icon_view->priv->dest_actions = actions; |
6316 | |
6317 | icon_view->priv->dest_set = TRUE; |
6318 | |
6319 | unset_reorderable (icon_view); |
6320 | |
6321 | widget_node = gtk_widget_get_css_node (GTK_WIDGET (icon_view)); |
6322 | icon_view->priv->dndnode = gtk_css_node_new (); |
6323 | gtk_css_node_set_name (cssnode: icon_view->priv->dndnode, name: g_quark_from_static_string (string: "dndtarget" )); |
6324 | gtk_css_node_set_parent (cssnode: icon_view->priv->dndnode, parent: widget_node); |
6325 | gtk_css_node_set_state (cssnode: icon_view->priv->dndnode, state_flags: gtk_css_node_get_state (cssnode: widget_node)); |
6326 | g_object_unref (object: icon_view->priv->dndnode); |
6327 | } |
6328 | |
6329 | /** |
6330 | * gtk_icon_view_unset_model_drag_source: |
6331 | * @icon_view: a `GtkIconView` |
6332 | * |
6333 | * Undoes the effect of gtk_icon_view_enable_model_drag_source(). Calling this |
6334 | * method sets `GtkIconView`:reorderable to %FALSE. |
6335 | **/ |
6336 | void |
6337 | gtk_icon_view_unset_model_drag_source (GtkIconView *icon_view) |
6338 | { |
6339 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
6340 | |
6341 | if (icon_view->priv->source_set) |
6342 | { |
6343 | g_clear_pointer (&icon_view->priv->source_formats, gdk_content_formats_unref); |
6344 | icon_view->priv->source_set = FALSE; |
6345 | } |
6346 | |
6347 | unset_reorderable (icon_view); |
6348 | } |
6349 | |
6350 | /** |
6351 | * gtk_icon_view_unset_model_drag_dest: |
6352 | * @icon_view: a `GtkIconView` |
6353 | * |
6354 | * Undoes the effect of gtk_icon_view_enable_model_drag_dest(). Calling this |
6355 | * method sets `GtkIconView`:reorderable to %FALSE. |
6356 | **/ |
6357 | void |
6358 | gtk_icon_view_unset_model_drag_dest (GtkIconView *icon_view) |
6359 | { |
6360 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
6361 | |
6362 | if (icon_view->priv->dest_set) |
6363 | { |
6364 | gtk_widget_remove_controller (GTK_WIDGET (icon_view), GTK_EVENT_CONTROLLER (icon_view->priv->dest)); |
6365 | icon_view->priv->dest = NULL; |
6366 | icon_view->priv->dest_set = FALSE; |
6367 | |
6368 | gtk_css_node_set_parent (cssnode: icon_view->priv->dndnode, NULL); |
6369 | icon_view->priv->dndnode = NULL; |
6370 | } |
6371 | |
6372 | unset_reorderable (icon_view); |
6373 | } |
6374 | |
6375 | /* These are useful to implement your own custom stuff. */ |
6376 | /** |
6377 | * gtk_icon_view_set_drag_dest_item: |
6378 | * @icon_view: a `GtkIconView` |
6379 | * @path: (nullable): The path of the item to highlight |
6380 | * @pos: Specifies where to drop, relative to the item |
6381 | * |
6382 | * Sets the item that is highlighted for feedback. |
6383 | */ |
6384 | void |
6385 | gtk_icon_view_set_drag_dest_item (GtkIconView *icon_view, |
6386 | GtkTreePath *path, |
6387 | GtkIconViewDropPosition pos) |
6388 | { |
6389 | /* Note; this function is exported to allow a custom DND |
6390 | * implementation, so it can't touch TreeViewDragInfo |
6391 | */ |
6392 | |
6393 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
6394 | |
6395 | if (icon_view->priv->dest_item) |
6396 | { |
6397 | GtkTreePath *current_path; |
6398 | current_path = gtk_tree_row_reference_get_path (reference: icon_view->priv->dest_item); |
6399 | gtk_tree_row_reference_free (reference: icon_view->priv->dest_item); |
6400 | icon_view->priv->dest_item = NULL; |
6401 | |
6402 | gtk_icon_view_queue_draw_path (icon_view, path: current_path); |
6403 | gtk_tree_path_free (path: current_path); |
6404 | } |
6405 | |
6406 | /* special case a drop on an empty model */ |
6407 | icon_view->priv->empty_view_drop = FALSE; |
6408 | if (pos == GTK_ICON_VIEW_DROP_ABOVE && path |
6409 | && gtk_tree_path_get_depth (path) == 1 |
6410 | && gtk_tree_path_get_indices (path)[0] == 0) |
6411 | { |
6412 | int n_children; |
6413 | |
6414 | n_children = gtk_tree_model_iter_n_children (tree_model: icon_view->priv->model, |
6415 | NULL); |
6416 | |
6417 | if (n_children == 0) |
6418 | icon_view->priv->empty_view_drop = TRUE; |
6419 | } |
6420 | |
6421 | icon_view->priv->dest_pos = pos; |
6422 | |
6423 | if (path) |
6424 | { |
6425 | icon_view->priv->dest_item = |
6426 | gtk_tree_row_reference_new_proxy (G_OBJECT (icon_view), |
6427 | model: icon_view->priv->model, path); |
6428 | |
6429 | gtk_icon_view_queue_draw_path (icon_view, path); |
6430 | } |
6431 | } |
6432 | |
6433 | /** |
6434 | * gtk_icon_view_get_drag_dest_item: |
6435 | * @icon_view: a `GtkIconView` |
6436 | * @path: (out) (nullable) (optional): Return location for the path of |
6437 | * the highlighted item |
6438 | * @pos: (out) (optional): Return location for the drop position |
6439 | * |
6440 | * Gets information about the item that is highlighted for feedback. |
6441 | */ |
6442 | void |
6443 | gtk_icon_view_get_drag_dest_item (GtkIconView *icon_view, |
6444 | GtkTreePath **path, |
6445 | GtkIconViewDropPosition *pos) |
6446 | { |
6447 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
6448 | |
6449 | if (path) |
6450 | { |
6451 | if (icon_view->priv->dest_item) |
6452 | *path = gtk_tree_row_reference_get_path (reference: icon_view->priv->dest_item); |
6453 | else |
6454 | *path = NULL; |
6455 | } |
6456 | |
6457 | if (pos) |
6458 | *pos = icon_view->priv->dest_pos; |
6459 | } |
6460 | |
6461 | /** |
6462 | * gtk_icon_view_get_dest_item_at_pos: |
6463 | * @icon_view: a `GtkIconView` |
6464 | * @drag_x: the position to determine the destination item for |
6465 | * @drag_y: the position to determine the destination item for |
6466 | * @path: (out) (optional): Return location for the path of the item |
6467 | * @pos: (out) (optional): Return location for the drop position |
6468 | * |
6469 | * Determines the destination item for a given position. |
6470 | * |
6471 | * Returns: whether there is an item at the given position. |
6472 | **/ |
6473 | gboolean |
6474 | gtk_icon_view_get_dest_item_at_pos (GtkIconView *icon_view, |
6475 | int drag_x, |
6476 | int drag_y, |
6477 | GtkTreePath **path, |
6478 | GtkIconViewDropPosition *pos) |
6479 | { |
6480 | GtkIconViewItem *item; |
6481 | |
6482 | /* Note; this function is exported to allow a custom DND |
6483 | * implementation, so it can't touch TreeViewDragInfo |
6484 | */ |
6485 | |
6486 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), FALSE); |
6487 | g_return_val_if_fail (drag_x >= 0, FALSE); |
6488 | g_return_val_if_fail (drag_y >= 0, FALSE); |
6489 | |
6490 | |
6491 | if (path) |
6492 | *path = NULL; |
6493 | |
6494 | item = _gtk_icon_view_get_item_at_coords (icon_view, |
6495 | x: drag_x + gtk_adjustment_get_value (adjustment: icon_view->priv->hadjustment), |
6496 | y: drag_y + gtk_adjustment_get_value (adjustment: icon_view->priv->vadjustment), |
6497 | FALSE, NULL); |
6498 | |
6499 | if (item == NULL) |
6500 | return FALSE; |
6501 | |
6502 | if (path) |
6503 | *path = gtk_tree_path_new_from_indices (first_index: item->index, -1); |
6504 | |
6505 | if (pos) |
6506 | { |
6507 | if (drag_x < item->cell_area.x + item->cell_area.width / 4) |
6508 | *pos = GTK_ICON_VIEW_DROP_LEFT; |
6509 | else if (drag_x > item->cell_area.x + item->cell_area.width * 3 / 4) |
6510 | *pos = GTK_ICON_VIEW_DROP_RIGHT; |
6511 | else if (drag_y < item->cell_area.y + item->cell_area.height / 4) |
6512 | *pos = GTK_ICON_VIEW_DROP_ABOVE; |
6513 | else if (drag_y > item->cell_area.y + item->cell_area.height * 3 / 4) |
6514 | *pos = GTK_ICON_VIEW_DROP_BELOW; |
6515 | else |
6516 | *pos = GTK_ICON_VIEW_DROP_INTO; |
6517 | } |
6518 | |
6519 | return TRUE; |
6520 | } |
6521 | |
6522 | /** |
6523 | * gtk_icon_view_create_drag_icon: |
6524 | * @icon_view: a `GtkIconView` |
6525 | * @path: a `GtkTreePath` in @icon_view |
6526 | * |
6527 | * Creates a `GdkPaintable` representation of the item at @path. |
6528 | * This image is used for a drag icon. |
6529 | * |
6530 | * Returns: (transfer full) (nullable): a newly-allocated `GdkPaintable` of the drag icon. |
6531 | **/ |
6532 | GdkPaintable * |
6533 | gtk_icon_view_create_drag_icon (GtkIconView *icon_view, |
6534 | GtkTreePath *path) |
6535 | { |
6536 | GtkWidget *widget; |
6537 | GtkSnapshot *snapshot; |
6538 | GdkPaintable *paintable; |
6539 | GList *l; |
6540 | int index; |
6541 | |
6542 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), NULL); |
6543 | g_return_val_if_fail (path != NULL, NULL); |
6544 | |
6545 | widget = GTK_WIDGET (icon_view); |
6546 | |
6547 | if (!gtk_widget_get_realized (widget)) |
6548 | return NULL; |
6549 | |
6550 | index = gtk_tree_path_get_indices (path)[0]; |
6551 | |
6552 | for (l = icon_view->priv->items; l; l = l->next) |
6553 | { |
6554 | GtkIconViewItem *item = l->data; |
6555 | |
6556 | if (index == item->index) |
6557 | { |
6558 | snapshot = gtk_snapshot_new (); |
6559 | gtk_icon_view_snapshot_item (icon_view, snapshot, item, |
6560 | x: icon_view->priv->item_padding, |
6561 | y: icon_view->priv->item_padding, |
6562 | FALSE); |
6563 | paintable = gtk_snapshot_free_to_paintable (snapshot, NULL); |
6564 | |
6565 | return paintable; |
6566 | } |
6567 | } |
6568 | |
6569 | return NULL; |
6570 | } |
6571 | |
6572 | /** |
6573 | * gtk_icon_view_get_reorderable: |
6574 | * @icon_view: a `GtkIconView` |
6575 | * |
6576 | * Retrieves whether the user can reorder the list via drag-and-drop. |
6577 | * See gtk_icon_view_set_reorderable(). |
6578 | * |
6579 | * Returns: %TRUE if the list can be reordered. |
6580 | **/ |
6581 | gboolean |
6582 | gtk_icon_view_get_reorderable (GtkIconView *icon_view) |
6583 | { |
6584 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), FALSE); |
6585 | |
6586 | return icon_view->priv->reorderable; |
6587 | } |
6588 | |
6589 | /** |
6590 | * gtk_icon_view_set_reorderable: |
6591 | * @icon_view: A `GtkIconView`. |
6592 | * @reorderable: %TRUE, if the list of items can be reordered. |
6593 | * |
6594 | * This function is a convenience function to allow you to reorder models that |
6595 | * support the `GtkTreeDragSourceIface` and the `GtkTreeDragDestIface`. Both |
6596 | * `GtkTreeStore` and `GtkListStore` support these. If @reorderable is %TRUE, then |
6597 | * the user can reorder the model by dragging and dropping rows. The |
6598 | * developer can listen to these changes by connecting to the model's |
6599 | * row_inserted and row_deleted signals. The reordering is implemented by setting up |
6600 | * the icon view as a drag source and destination. Therefore, drag and |
6601 | * drop can not be used in a reorderable view for any other purpose. |
6602 | * |
6603 | * This function does not give you any degree of control over the order -- any |
6604 | * reordering is allowed. If more control is needed, you should probably |
6605 | * handle drag and drop manually. |
6606 | **/ |
6607 | void |
6608 | gtk_icon_view_set_reorderable (GtkIconView *icon_view, |
6609 | gboolean reorderable) |
6610 | { |
6611 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
6612 | |
6613 | reorderable = reorderable != FALSE; |
6614 | |
6615 | if (icon_view->priv->reorderable == reorderable) |
6616 | return; |
6617 | |
6618 | if (reorderable) |
6619 | { |
6620 | GdkContentFormats *formats = gdk_content_formats_new_for_gtype (GTK_TYPE_TREE_ROW_DATA); |
6621 | gtk_icon_view_enable_model_drag_source (icon_view, |
6622 | start_button_mask: GDK_BUTTON1_MASK, |
6623 | formats, |
6624 | actions: GDK_ACTION_MOVE); |
6625 | gtk_icon_view_enable_model_drag_dest (icon_view, |
6626 | formats, |
6627 | actions: GDK_ACTION_MOVE); |
6628 | gdk_content_formats_unref (formats); |
6629 | } |
6630 | else |
6631 | { |
6632 | gtk_icon_view_unset_model_drag_source (icon_view); |
6633 | gtk_icon_view_unset_model_drag_dest (icon_view); |
6634 | } |
6635 | |
6636 | icon_view->priv->reorderable = reorderable; |
6637 | |
6638 | g_object_notify (G_OBJECT (icon_view), property_name: "reorderable" ); |
6639 | } |
6640 | |
6641 | /** |
6642 | * gtk_icon_view_set_activate_on_single_click: |
6643 | * @icon_view: a `GtkIconView` |
6644 | * @single: %TRUE to emit item-activated on a single click |
6645 | * |
6646 | * Causes the `GtkIconView`::item-activated signal to be emitted on |
6647 | * a single click instead of a double click. |
6648 | **/ |
6649 | void |
6650 | gtk_icon_view_set_activate_on_single_click (GtkIconView *icon_view, |
6651 | gboolean single) |
6652 | { |
6653 | g_return_if_fail (GTK_IS_ICON_VIEW (icon_view)); |
6654 | |
6655 | single = single != FALSE; |
6656 | |
6657 | if (icon_view->priv->activate_on_single_click == single) |
6658 | return; |
6659 | |
6660 | icon_view->priv->activate_on_single_click = single; |
6661 | g_object_notify (G_OBJECT (icon_view), property_name: "activate-on-single-click" ); |
6662 | } |
6663 | |
6664 | /** |
6665 | * gtk_icon_view_get_activate_on_single_click: |
6666 | * @icon_view: a `GtkIconView` |
6667 | * |
6668 | * Gets the setting set by gtk_icon_view_set_activate_on_single_click(). |
6669 | * |
6670 | * Returns: %TRUE if item-activated will be emitted on a single click |
6671 | **/ |
6672 | gboolean |
6673 | gtk_icon_view_get_activate_on_single_click (GtkIconView *icon_view) |
6674 | { |
6675 | g_return_val_if_fail (GTK_IS_ICON_VIEW (icon_view), FALSE); |
6676 | |
6677 | return icon_view->priv->activate_on_single_click; |
6678 | } |
6679 | |
6680 | static gboolean |
6681 | gtk_icon_view_buildable_custom_tag_start (GtkBuildable *buildable, |
6682 | GtkBuilder *builder, |
6683 | GObject *child, |
6684 | const char *tagname, |
6685 | GtkBuildableParser *parser, |
6686 | gpointer *data) |
6687 | { |
6688 | if (parent_buildable_iface->custom_tag_start (buildable, builder, child, |
6689 | tagname, parser, data)) |
6690 | return TRUE; |
6691 | |
6692 | return _gtk_cell_layout_buildable_custom_tag_start (buildable, builder, child, |
6693 | tagname, parser, data); |
6694 | } |
6695 | |
6696 | static void |
6697 | gtk_icon_view_buildable_custom_tag_end (GtkBuildable *buildable, |
6698 | GtkBuilder *builder, |
6699 | GObject *child, |
6700 | const char *tagname, |
6701 | gpointer data) |
6702 | { |
6703 | if (!_gtk_cell_layout_buildable_custom_tag_end (buildable, builder, |
6704 | child, tagname, data)) |
6705 | parent_buildable_iface->custom_tag_end (buildable, builder, |
6706 | child, tagname, data); |
6707 | } |
6708 | |