1 /*
2 A widget to display and manipulate tabular data
3 Copyright (C) 2016, 2017, 2019, 2020 John Darrington
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /*
20 This work is derived from Timm Bäder's original work GD_MODEL_LIST_BOX.
21 Downloaded from https://github.com/baedert/listbox-c
22 His copyright notice is below.
23 */
24
25 /*
26 * Copyright 2015 Timm Bäder
27 *
28 * This program is free software: you can redistribute it and/or modify
29 * it under the terms of the GNU General Public License as published by
30 * the Free Software Foundation, either version 3 of the License, or
31 * (at your option) any later version.
32 *
33 * This program is distributed in the hope that it will be useful,
34 * but WITHOUT ANY WARRANTY; without even the implied warranty of
35 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 * GNU General Public License for more details.
37 *
38 * You should have received a copy of the GNU General Public License
39 * along with this program. If not, see <http://www.gnu.org/licenses/>.
40 */
41
42 #include <config.h>
43 #include "ssw-sheet-axis.h"
44 #include "ssw-marshaller.h"
45 #include <math.h>
46 #include <stdlib.h>
47
48
49 #define P_(X) (X)
50
51 struct _SswSheetAxisPrivate
52 {
53 GtkOrientation orientation;
54
55 /* Adjustment in the direction of the bin's orientation */
56 GtkAdjustment *adjustment;
57
58 GPtrArray *widgets;
59 GPtrArray *pool;
60 GdkWindow *bin_window;
61
62 GListModel *model;
63
64 guint model_from;
65 guint model_to;
66 gdouble bin_start_diff;
67
68 gint (*get_allocated_p_size) (GtkWidget *);
69 gint (*get_allocated_q_size) (GtkWidget *);
70
71 void (*get_preferred_p_for_q) (GtkWidget *, gint, gint *, gint *);
72 gint (*get_window_p_size) (GdkWindow *);
73
74 void (*set_q_offset) (GtkAllocation *, gint);
75 void (*set_p_offset) (GtkAllocation *, gint);
76 void (*set_q_size) (GtkAllocation *, gint);
77 void (*set_p_size) (GtkAllocation *, gint);
78
79 gint (*get_q_size) (GtkAllocation *);
80 gint (*get_p_size) (GtkAllocation *);
81
82 /* A table of all the items whose sizes have been manually overridden */
83 GHashTable *size_override;
84
85 GtkGesture *button_gest;
86 gint n_press;
87 gint press_id;
88 guint button;
89 guint state;
90
91 /* The cursor to be shown during resizing of the rows/columns */
92 GdkCursor *resize_cursor;
93 GtkGesture *resize_gest;
94 gint resize_target;
95 gint new_size;
96
97 GtkGesture *drag_gest;
98 gulong drag_handler_id;
99 GtkTargetList *drag_target_list;
100
101 gboolean dispose_has_run;
102 };
103
104 typedef struct _SswSheetAxisPrivate SswSheetAxisPrivate;
105
106 static void
axis_debug(const char * fmt,...)107 axis_debug (const char *fmt, ...)
108 {
109 #if 0
110 va_list args;
111 va_start (args, fmt);
112 g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args);
113 va_end (args);
114 #endif
115 }
116
117 enum {CHANGED,
118 HEADER_CLICKED,
119 HEADER_DOUBLE_CLICKED,
120 HEADER_BUTTON_PRESSED,
121 HEADER_BUTTON_RELEASED,
122 DRAG_N_DROP,
123 n_SIGNALS};
124
125 static guint signals [n_SIGNALS];
126
127 static void
zz_set_alloc_x(GtkAllocation * alloc,gint offset)128 zz_set_alloc_x (GtkAllocation *alloc, gint offset)
129 {
130 alloc->x = offset;
131 }
132
133 static gint
zz_get_alloc_width(GtkAllocation * alloc)134 zz_get_alloc_width (GtkAllocation *alloc)
135 {
136 return alloc->width;
137 }
138
139 static void
zz_set_alloc_width(GtkAllocation * alloc,gint size)140 zz_set_alloc_width (GtkAllocation *alloc, gint size)
141 {
142 alloc->width = size;
143 }
144
145 static void
zz_set_alloc_y(GtkAllocation * alloc,gint offset)146 zz_set_alloc_y (GtkAllocation *alloc, gint offset)
147 {
148 alloc->y = offset;
149 }
150
151 static gint
zz_get_alloc_height(GtkAllocation * alloc)152 zz_get_alloc_height (GtkAllocation *alloc)
153 {
154 return alloc->height;
155 }
156
157 static void
zz_set_alloc_height(GtkAllocation * alloc,gint size)158 zz_set_alloc_height (GtkAllocation *alloc, gint size)
159 {
160 alloc->height = size;
161 }
162
163 G_DEFINE_TYPE_WITH_CODE (SswSheetAxis, ssw_sheet_axis,
164 GTK_TYPE_CONTAINER,
165 G_ADD_PRIVATE (SswSheetAxis)
166 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL));
167
168 #define PRIV_DECL(l) SswSheetAxisPrivate *priv = ((SswSheetAxisPrivate *)ssw_sheet_axis_get_instance_private ((SswSheetAxis *)l))
169 #define PRIV(l) ((SswSheetAxisPrivate *)ssw_sheet_axis_get_instance_private ((SswSheetAxis *)l))
170
171 gboolean
ssw_sheet_axis_rtl(SswSheetAxis * axis)172 ssw_sheet_axis_rtl (SswSheetAxis *axis)
173 {
174 PRIV_DECL (axis);
175
176 return ((priv->orientation == GTK_ORIENTATION_HORIZONTAL)
177 &&
178 (GTK_TEXT_DIR_RTL == gtk_widget_get_direction (GTK_WIDGET (axis))));
179 }
180
181
182 #define Foreach_Item {int i; for (i = 0; i < priv->widgets->len; i ++){ \
183 GtkWidget *item \
184 = g_ptr_array_index (priv->widgets, ssw_sheet_axis_rtl (axis) ? \
185 (priv->widgets->len - i - 1) : i);
186
187
188
189 #define Foreach_Item_Fwd {int i; for (i = 0; i < priv->widgets->len; i ++){ \
190 GtkWidget *item \
191 = g_ptr_array_index (priv->widgets, i);
192
193 #define EndFor }} while(0)
194
195 enum
196 {
197 PROP_0,
198 PROP_ADJUSTMENT,
199 PROP_ORIENTATION,
200 PROP_DRAGGABLE
201 };
202
203
204 static void
__axis_set_value(SswSheetAxis * axis,gdouble x)205 __axis_set_value (SswSheetAxis *axis, gdouble x)
206 {
207 PRIV_DECL (axis);
208
209 if (ssw_sheet_axis_rtl (axis))
210 {
211 gdouble u = gtk_adjustment_get_upper (priv->adjustment);
212 gdouble ps = gtk_adjustment_get_page_size (priv->adjustment);
213 gtk_adjustment_set_value (priv->adjustment, u - ps - x);
214 }
215 else
216 gtk_adjustment_set_value (priv->adjustment, x);
217 }
218
219 static gdouble
__axis_get_value(SswSheetAxis * axis)220 __axis_get_value (SswSheetAxis *axis)
221 {
222 PRIV_DECL (axis);
223
224 if (ssw_sheet_axis_rtl (axis))
225 {
226 gdouble u = gtk_adjustment_get_upper (priv->adjustment);
227 gdouble ps = gtk_adjustment_get_page_size (priv->adjustment);
228 return u - ps - gtk_adjustment_get_value (priv->adjustment);
229 }
230 else
231 return gtk_adjustment_get_value (priv->adjustment);
232 }
233
234 static void
stopped(GtkGesture * g,gpointer ud)235 stopped (GtkGesture *g, gpointer ud)
236 {
237 SswSheetAxis *axis = SSW_SHEET_AXIS (ud);
238
239 PRIV_DECL (axis);
240
241 /* I don't know why this is necessary. The gesture should not have
242 got this far if this is the case. */
243 GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (axis));
244 if (gdk_window_get_cursor (win) == priv->resize_cursor)
245 {
246 priv->n_press = 0;
247 return;
248 }
249
250 if (priv->n_press == 1 && priv->button == 1)
251 g_signal_emit (axis, signals [HEADER_CLICKED], 0,
252 priv->press_id, priv->state);
253
254
255 if (priv->n_press == 2 && priv->button == 1)
256 g_signal_emit (axis, signals [HEADER_DOUBLE_CLICKED], 0,
257 priv->press_id, priv->state);
258
259 priv->n_press = 0;
260 priv->button = 0;
261 priv->state = 0;
262 }
263
264
265 static void
button_pressed(GtkGesture * g,gint n_press,gdouble x,gdouble y,gpointer ud)266 button_pressed (GtkGesture *g,
267 gint n_press, gdouble x, gdouble y, gpointer ud)
268 {
269 SswSheetAxis *axis = SSW_SHEET_AXIS (ud);
270
271 const GdkEvent *e = gtk_gesture_get_last_event
272 (g, gtk_gesture_get_last_updated_sequence (g));
273
274 /* This can happen if the gesture is denied for any reason */
275 if (!e || e->type != GDK_BUTTON_PRESS)
276 return;
277
278 GdkWindow *win = ((const GdkEventButton *)e)->window;
279 GObject *widget = NULL;
280 gdk_window_get_user_data (win, (gpointer*)&widget);
281 gint id = GPOINTER_TO_INT (g_object_get_data (widget, "item-id"));
282
283 guint button = ((const GdkEventButton *)e)->button;
284 guint state = ((const GdkEventButton *)e)->state;
285
286 g_signal_emit (axis, signals [HEADER_BUTTON_PRESSED], 0, id, button, state);
287 }
288
289 static void
button_released(GtkGesture * g,gint n_press,gdouble x,gdouble y,gpointer ud)290 button_released (GtkGesture *g,
291 gint n_press, gdouble x, gdouble y, gpointer ud)
292 {
293 SswSheetAxis *axis = SSW_SHEET_AXIS (ud);
294 PRIV_DECL (axis);
295 const GdkEvent *e = gtk_gesture_get_last_event (g,
296 gtk_gesture_get_last_updated_sequence (g));
297
298 /* This can happen if the gesture is denied for any reason */
299 if (!e || e->type != GDK_BUTTON_RELEASE)
300 return;
301
302 GdkWindow *win = ((const GdkEventButton *)e)->window;
303 GObject *widget = NULL;
304 gdk_window_get_user_data (win, (gpointer*)&widget);
305 gint id = GPOINTER_TO_INT (g_object_get_data (widget, "item-id"));
306
307 if (!gtk_widget_get_sensitive (GTK_WIDGET (widget)))
308 return;
309
310 priv->n_press = n_press;
311 priv->press_id = id;
312 priv->button = ((const GdkEventButton *)e)->button;
313 priv->state = ((const GdkEventButton *)e)->state;
314
315 g_signal_emit (axis, signals [HEADER_BUTTON_RELEASED], 0,
316 priv->press_id, priv->button, priv->state);
317 }
318
319 static GtkWidget *
get_widget(SswSheetAxis * axis,guint index)320 get_widget (SswSheetAxis *axis, guint index)
321 {
322 PRIV_DECL (axis);
323
324 GtkWidget *new_widget = g_list_model_get_item (priv->model, index);
325
326 if (priv->pool->len > 0)
327 {
328 GtkWidget *old_widget = g_ptr_array_remove_index_fast (priv->pool, 0);
329 g_object_unref (old_widget);
330 }
331
332 g_object_set_data (G_OBJECT (new_widget), "item-id", GINT_TO_POINTER (index));
333
334 gtk_widget_set_sensitive (new_widget, index < ssw_sheet_axis_get_size (axis));
335 if (index >= ssw_sheet_axis_get_size (axis))
336 g_object_set (new_widget,
337 "has-tooltip", FALSE,
338 NULL);
339
340 /* Check if the size of this item has been specifically overridden
341 by the user. If it has set the size request accordingly. */
342 gpointer size = g_hash_table_lookup (priv->size_override, GINT_TO_POINTER (index));
343 if (size)
344 {
345 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
346 gtk_widget_set_size_request (new_widget, GPOINTER_TO_INT (size), -1);
347 else
348 gtk_widget_set_size_request (new_widget, -1, GPOINTER_TO_INT (size));
349 }
350
351 if (g_object_is_floating (new_widget))
352 g_object_ref_sink (new_widget);
353
354 g_assert (GTK_IS_WIDGET (new_widget));
355
356 /* We just enforce this here. */
357 gtk_widget_show (new_widget);
358
359 return new_widget;
360 }
361
362
363 static void
insert_child_ginternal(SswSheetAxis * axis,GtkWidget * widget,guint index)364 insert_child_ginternal (SswSheetAxis *axis, GtkWidget *widget, guint index)
365 {
366 PRIV_DECL (axis);
367
368 g_assert (gtk_widget_get_realized (GTK_WIDGET (axis)));
369
370 g_object_ref (widget);
371
372 gtk_widget_set_parent_window (widget, priv->bin_window);
373 gtk_widget_set_parent (widget, GTK_WIDGET (axis));
374
375 g_ptr_array_insert (priv->widgets, index, widget);
376 }
377
378 static void
remove_child_ginternal(SswSheetAxis * axis,GtkWidget * widget)379 remove_child_ginternal (SswSheetAxis *axis, GtkWidget *widget)
380 {
381 PRIV_DECL (axis);
382
383 g_object_unref (widget);
384
385 gtk_widget_unparent (widget);
386 g_ptr_array_remove (priv->widgets, widget);
387 g_ptr_array_add (priv->pool, widget);
388 }
389
390 static inline gint
bin_start(SswSheetAxis * axis)391 bin_start (SswSheetAxis *axis)
392 {
393 return -__axis_get_value (axis) +
394 PRIV (axis)->bin_start_diff;
395 }
396
397
398 static void
free_limit(gpointer data)399 free_limit (gpointer data)
400 {
401 g_slice_free (SswGeometry, data);
402 }
403
404 static void
position_children(SswSheetAxis * axis)405 position_children (SswSheetAxis *axis)
406 {
407 GtkAllocation alloc;
408 GtkAllocation child_alloc;
409 PRIV_DECL (axis);
410 gint offset = 0;
411
412 gtk_widget_get_allocation (GTK_WIDGET (axis), &alloc);
413
414 priv->set_q_offset (&child_alloc, 0);
415 priv->set_q_size (&child_alloc, priv->get_q_size (&alloc));
416
417
418 if (axis->cell_limits)
419 g_ptr_array_free (axis->cell_limits, TRUE);
420 axis->cell_limits = g_ptr_array_new_full (priv->widgets->len,
421 free_limit);
422
423 axis->last_cell = priv->model_to;
424 axis->first_cell = priv->model_from;
425
426 Foreach_Item
427 gint size;
428
429 priv->get_preferred_p_for_q (item, priv->get_q_size (&alloc),
430 &size, NULL);
431 priv->set_p_offset (&child_alloc, offset);
432 priv->set_p_size (&child_alloc, size);
433 gtk_widget_size_allocate (item, &child_alloc);
434
435 offset += size;
436
437 EndFor;
438
439
440 guint width = gtk_widget_get_allocated_width (GTK_WIDGET (axis));
441
442 offset = 0;
443 Foreach_Item_Fwd
444 gint size;
445
446 priv->get_preferred_p_for_q (item, priv->get_q_size (&alloc),
447 &size, NULL);
448
449 SswGeometry *geom = g_slice_new (SswGeometry);
450 geom->position = offset + bin_start (axis);
451
452 if (ssw_sheet_axis_rtl (axis))
453 geom->position = width - geom->position - size;
454
455 geom->size = size;
456 g_ptr_array_insert (axis->cell_limits, i, geom);
457
458 offset += size;
459 EndFor;
460 }
461
462 static inline gint
item_size(SswSheetAxis * axis,GtkWidget * w)463 item_size (SswSheetAxis *axis, GtkWidget *w)
464 {
465 PRIV_DECL (axis);
466 gint min, nat;
467 priv->get_preferred_p_for_q (w,
468 priv->get_allocated_q_size (GTK_WIDGET(axis)),
469 &min, &nat);
470
471 return nat;
472 }
473
474 static inline gint
item_start(SswSheetAxis * axis,guint index)475 item_start (SswSheetAxis *axis, guint index)
476 {
477 gint y = 0;
478 gint i;
479 PRIV_DECL (axis);
480
481 for (i = 0; i < index; i++)
482 y += item_size (axis, g_ptr_array_index (priv->widgets, i));
483
484 return y;
485 }
486
487 static gdouble
estimated_widget_size(SswSheetAxis * axis)488 estimated_widget_size (SswSheetAxis *axis)
489 {
490 gdouble avg_widget_size = 0;
491 PRIV_DECL (axis);
492
493 Foreach_Item avg_widget_size += item_size (axis, item);
494 EndFor;
495
496 if (priv->widgets->len > 0)
497 avg_widget_size /= priv->widgets->len;
498 else
499 return 0;
500
501 return avg_widget_size;
502 }
503
504 static gboolean
bin_window_full(SswSheetAxis * axis)505 bin_window_full (SswSheetAxis *axis)
506 {
507 gint bin_size;
508 gint widget_size;
509 PRIV_DECL (axis);
510
511 if (gtk_widget_get_realized (GTK_WIDGET (axis)))
512 bin_size = priv->get_window_p_size (priv->bin_window);
513 else
514 bin_size = 0;
515
516 widget_size = priv->get_allocated_p_size (GTK_WIDGET (axis));
517
518 /*
519 * We consider the bin_window "full" if either the end of it is out of view,
520 * OR it already contains all the items.
521 */
522 return (bin_start (axis) + bin_size > widget_size) ||
523 (priv->model_to - priv->model_from ==
524 ssw_sheet_axis_get_extent (axis));
525 }
526
527 static gint
estimated_list_size(SswSheetAxis * axis,guint * start_part,guint * end_part)528 estimated_list_size (SswSheetAxis *axis,
529 guint *start_part, guint *end_part)
530 {
531 gdouble avg_size;
532 gint start_widgets;
533 gint end_widgets;
534 gint exact_size = 0;
535 PRIV_DECL (axis);
536
537 avg_size = estimated_widget_size (axis);
538 start_widgets = priv->model_from;
539 end_widgets = ssw_sheet_axis_get_extent (axis) - priv->model_to;
540
541 g_assert (start_widgets + end_widgets + priv->widgets->len ==
542 ssw_sheet_axis_get_extent (axis));
543
544 Foreach_Item
545 exact_size += item_size (axis, item);
546 EndFor;
547
548 if (start_part)
549 *start_part = start_widgets * avg_size;
550
551 if (end_part)
552 *end_part = end_widgets * avg_size;
553
554 return (start_widgets * avg_size) + exact_size + (end_widgets * avg_size);
555 }
556
557
558 static void
update_bin_window(SswSheetAxis * axis)559 update_bin_window (SswSheetAxis *axis)
560 {
561 GtkAllocation alloc;
562 gint size = 0;
563 PRIV_DECL (axis);
564
565 gtk_widget_get_allocation (GTK_WIDGET (axis), &alloc);
566
567 Foreach_Item
568 gint min = item_size (axis, item);
569 g_assert (min >= 0);
570 size += min;
571 EndFor;
572
573 if (size == 0)
574 size = 1;
575
576 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
577 {
578 if (size != gdk_window_get_height (priv->bin_window) ||
579 alloc.width != gdk_window_get_width (priv->bin_window))
580 {
581 gdk_window_move_resize (priv->bin_window,
582 0, bin_start (axis), alloc.width, size);
583 }
584 else
585 gdk_window_move (priv->bin_window, 0, bin_start (axis));
586 }
587 else if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
588 {
589 gint xoffset = ssw_sheet_axis_rtl (axis)
590 ? (alloc.width - bin_start (axis) -
591 priv->get_window_p_size (priv->bin_window))
592 : bin_start (axis);
593
594 if (size != gdk_window_get_width (priv->bin_window) ||
595 alloc.height != gdk_window_get_height (priv->bin_window))
596 {
597 gdk_window_move_resize (priv->bin_window,
598 xoffset,
599 0, size, alloc.height);
600 }
601 else
602 gdk_window_move (priv->bin_window, xoffset, 0);
603 }
604 }
605
606 static void
configure_adjustment(SswSheetAxis * axis)607 configure_adjustment (SswSheetAxis *axis)
608 {
609 gint widget_size;
610 gint list_size;
611 gdouble cur_upper;
612 gdouble cur_value;
613 gdouble page_size;
614 PRIV_DECL (axis);
615
616 widget_size = priv->get_allocated_p_size (GTK_WIDGET (axis));
617 list_size = estimated_list_size (axis, NULL, NULL);
618 cur_upper = gtk_adjustment_get_upper (priv->adjustment);
619 page_size = gtk_adjustment_get_page_size (priv->adjustment);
620 cur_value = __axis_get_value (axis);
621
622 if ((gint) cur_upper != MAX (list_size, widget_size))
623 {
624 axis_debug ("%p New upper: %d (%d, %d)", priv, MAX (list_size, widget_size), list_size, widget_size);
625 gtk_adjustment_set_upper (priv->adjustment,
626 MAX (list_size, widget_size));
627 }
628 else if (list_size == 0)
629 gtk_adjustment_set_upper (priv->adjustment, widget_size);
630
631
632 if ((gint) page_size != widget_size)
633 gtk_adjustment_set_page_size (priv->adjustment, widget_size);
634
635 if (cur_value > cur_upper - widget_size)
636 {
637 __axis_set_value (axis, cur_upper - widget_size);
638 }
639 }
640
641 #define NOT_BOTH(A,B) !((A)&&(B))
642
643 static void
ensure_visible_widgets(SswSheetAxis * axis,gboolean force_reload)644 ensure_visible_widgets (SswSheetAxis *axis, gboolean force_reload)
645 {
646 GtkWidget *widget = GTK_WIDGET (axis);
647 gint bin_size;
648 gboolean start_removed, start_added, end_removed, end_added;
649 PRIV_DECL (axis);
650
651 if (!gtk_widget_get_mapped (widget))
652 return;
653
654 const gint widget_size = priv->get_allocated_p_size (widget);
655 bin_size = priv->get_window_p_size (priv->bin_window);
656
657 if (bin_size == 1)
658 bin_size = 0;
659
660 /* This "out of sight" case happens when the new value is so different from the old one
661 * that we rather just remove all widgets and adjust the model_from/model_to values
662 * This mode only works when some widgets are already there to estimate the widget size
663 */
664 if (priv->widgets->len > 0 && bin_size > 0 &&
665 (bin_start (axis) + bin_size < 0 || bin_start (axis) >= widget_size || force_reload))
666 {
667 gdouble avg_widget_size = estimated_widget_size (axis);
668 gdouble percentage;
669 gdouble value = __axis_get_value (axis);
670 gdouble upper = gtk_adjustment_get_upper (priv->adjustment);
671 gdouble page_size = gtk_adjustment_get_page_size (priv->adjustment);
672 guint start_widget_index;
673 gint i;
674
675 for (i = priv->widgets->len - 1; i >= 0; i--)
676 remove_child_ginternal (axis, g_ptr_array_index (priv->widgets, i));
677 bin_size = 0; /* The window is empty now, obviously */
678
679 g_assert (priv->widgets->len == 0);
680
681 /* Percentage of the current adjustment value */
682 percentage = value / (upper - page_size);
683
684 start_widget_index =
685 (guint) (ssw_sheet_axis_get_extent (axis) * percentage);
686
687 if (start_widget_index > ssw_sheet_axis_get_extent (axis))
688 {
689 /* XXX Can this still happen? */
690 priv->model_from = ssw_sheet_axis_get_extent (axis);
691 priv->model_to = ssw_sheet_axis_get_extent (axis);
692 priv->bin_start_diff = value + page_size;
693 g_assert (FALSE);
694 }
695 else
696 {
697 priv->model_from = start_widget_index;
698 priv->model_to = start_widget_index;
699 priv->bin_start_diff = start_widget_index * avg_widget_size;
700 }
701
702 g_assert (priv->model_from <= ssw_sheet_axis_get_extent (axis));
703 g_assert (priv->model_to <= ssw_sheet_axis_get_extent (axis));
704
705 if (bin_start (axis) > widget_size)
706 g_critical ("Start of widget is outside of the container");
707 }
708
709 /* Remove start widgets */
710 {
711 gint i;
712 start_removed = FALSE;
713
714 for (i = priv->widgets->len - 1; i >= 0; i--)
715 {
716 GtkWidget *w = g_ptr_array_index (priv->widgets, i);
717 gint w_size = item_size (axis, w);
718 if (bin_start (axis) + item_start (axis, i) + w_size < 0)
719 {
720 axis_debug ("%p Removing start widget %d", priv, i);
721 priv->bin_start_diff += w_size;
722 bin_size -= w_size;
723 remove_child_ginternal (axis, w);
724 priv->model_from++;
725 start_removed = TRUE;
726 }
727 }
728 }
729
730 /* Add start widgets */
731 {
732 start_added = FALSE;
733 while (priv->model_from > 0 && bin_start (axis) >= 0)
734 {
735 GtkWidget *new_widget;
736 gint min;
737 priv->model_from--;
738
739 new_widget = get_widget (axis, priv->model_from);
740 g_assert (new_widget != NULL);
741 insert_child_ginternal (axis, new_widget, 0);
742 min = item_size (axis, new_widget);
743 priv->bin_start_diff -= min;
744 bin_size += min;
745 start_added = TRUE;
746 axis_debug ("%p Adding start widget for index %d", priv, priv->model_from);
747 }
748 }
749
750 /* Remove end widgets */
751 {
752 end_removed = FALSE;
753 gint i;
754 for (i = priv->widgets->len - 1; i >= 0; i--)
755 {
756 GtkWidget *w = g_ptr_array_index (priv->widgets, i);
757 gint y = bin_start (axis) + item_start (axis, i);
758
759 axis_debug ("%p %d: %d + %d > %d", priv, i, bin_start (axis), item_start (axis, i), widget_size);
760 if (y > widget_size)
761 {
762 axis_debug ("%p Removing widget %d", priv, i);
763 gint w_size = item_size (axis, w);
764 remove_child_ginternal (axis, w);
765 bin_size -= w_size;
766 priv->model_to--;
767 end_removed = TRUE;
768 }
769 else
770 break;
771 }
772 }
773
774 /* Insert end widgets */
775 {
776 end_added = FALSE;
777 axis_debug ("%p %d + %d <= %d", priv, bin_start (axis), bin_size, widget_size);
778 while (bin_start (axis) + bin_size <= widget_size &&
779 priv->model_to < ssw_sheet_axis_get_extent (axis))
780 {
781 GtkWidget *new_widget;
782 gint min;
783
784 axis_debug ("%p Inserting end widget for position %u at %u", priv, priv->model_to, priv->widgets->len);
785 new_widget = get_widget (axis, priv->model_to);
786 insert_child_ginternal (axis, new_widget, priv->widgets->len);
787 min = item_size (axis, new_widget);
788 bin_size += min;
789
790 priv->model_to++;
791 end_added = TRUE;
792 }
793 }
794
795 {
796 gdouble new_upper;
797 guint start_part, end_part;
798 gint bin_window_y = bin_start (axis);
799
800 new_upper = estimated_list_size (axis, &start_part, &end_part);
801
802 if (new_upper > gtk_adjustment_get_upper (priv->adjustment))
803 priv->bin_start_diff =
804 MAX (start_part, __axis_get_value (axis));
805 else
806 priv->bin_start_diff =
807 MIN (start_part, __axis_get_value (axis));
808
809 configure_adjustment (axis);
810
811 axis_debug ("%p Setting value to %f - %d", priv, priv->bin_start_diff, bin_window_y);
812 __axis_set_value (axis,
813 priv->bin_start_diff - bin_window_y);
814 if (__axis_get_value (axis) < priv->bin_start_diff)
815 {
816 __axis_set_value (axis, priv->bin_start_diff);
817 axis_debug ("%p Case 1", priv);
818 }
819
820 if (bin_start (axis) > 0)
821 {
822 axis_debug ("%p CRAP", priv);
823 priv->bin_start_diff = __axis_get_value (axis);
824 }
825 }
826
827 update_bin_window (axis);
828 position_children (axis);
829 configure_adjustment (axis);
830
831 gtk_widget_queue_draw (widget);
832
833 g_signal_emit (axis, signals [CHANGED], 0);
834 }
835
836 static void
value_changed_cb(GtkAdjustment * adjustment,gpointer user_data)837 value_changed_cb (GtkAdjustment *adjustment, gpointer user_data)
838 {
839 SswSheetAxis *axis = SSW_SHEET_AXIS (user_data);
840 ensure_visible_widgets (axis, FALSE);
841 }
842
843 static void
items_changed_cb(GListModel * model,guint position,guint removed,guint added,gpointer user_data)844 items_changed_cb (GListModel *model,
845 guint position,
846 guint removed, guint added, gpointer user_data)
847 {
848 SswSheetAxis *axis = SSW_SHEET_AXIS (user_data);
849 gint i;
850 PRIV_DECL (axis);
851
852 if (! gtk_widget_get_realized (GTK_WIDGET (axis)))
853 return;
854
855 /* If the change is out of our visible range anyway,
856 then we don't care. */
857 if (position > priv->model_to && bin_window_full (axis))
858 {
859 configure_adjustment (axis);
860 return;
861 }
862
863 /* Empty the current view */
864 for (i = priv->widgets->len - 1; i >= 0; i--)
865 remove_child_ginternal (axis, g_ptr_array_index (priv->widgets, i));
866
867 priv->model_to = priv->model_from;
868 update_bin_window (axis);
869 ensure_visible_widgets (axis, FALSE);
870 }
871
872 /* GtkContainer vfuncs {{{ */
873 static void
__add(GtkContainer * container,GtkWidget * child)874 __add (GtkContainer *container, GtkWidget *child)
875 {
876 g_error ("Don't use that.");
877 }
878
879 static void
__remove(GtkContainer * container,GtkWidget * child)880 __remove (GtkContainer *container, GtkWidget *child)
881 {
882 g_assert (gtk_widget_get_parent (child) == GTK_WIDGET (container));
883 /*g_assert (gtk_widget_get_parent_window (child) == priv->bin_window); XXX ??? */
884 }
885
886 static void
__forall(GtkContainer * w,gboolean include_ginternals,GtkCallback callback,gpointer callback_data)887 __forall (GtkContainer *w,
888 gboolean include_ginternals,
889 GtkCallback callback, gpointer callback_data)
890 {
891 SswSheetAxis *axis = SSW_SHEET_AXIS (w);
892 PRIV_DECL (axis);
893
894 Foreach_Item
895 (*callback) (item, callback_data);
896 EndFor;
897 }
898
899 /* }}} */
900
901 /* GtkWidget vfuncs {{{ */
902 static void
__size_allocate(GtkWidget * widget,GtkAllocation * allocation)903 __size_allocate (GtkWidget *widget, GtkAllocation *allocation)
904 {
905 PRIV_DECL (widget);
906 gboolean size_changed =
907 priv->get_p_size (allocation) !=
908 priv->get_allocated_p_size (widget);
909
910 gtk_widget_set_allocation (widget, allocation);
911
912 position_children ((SswSheetAxis *) widget);
913
914 if (gtk_widget_get_realized (widget))
915 {
916 gdk_window_move_resize (gtk_widget_get_window (widget),
917 allocation->x, allocation->y,
918 allocation->width, allocation->height);
919
920 update_bin_window ((SswSheetAxis *) widget);
921 ensure_visible_widgets ((SswSheetAxis *) widget, FALSE);
922 }
923
924
925 // if (!bin_window_full ((SswSheetAxis *)widget) && size_changed)
926 if (size_changed)
927 ensure_visible_widgets ((SswSheetAxis *) widget, FALSE);
928
929 configure_adjustment ((SswSheetAxis *) widget);
930 }
931
932 static gboolean
__draw(GtkWidget * w,cairo_t * ct)933 __draw (GtkWidget *w, cairo_t *ct)
934 {
935 SswSheetAxis *axis = SSW_SHEET_AXIS (w);
936 GtkAllocation alloc;
937 GtkStyleContext *context;
938
939 PRIV_DECL (axis);
940
941 context = gtk_widget_get_style_context (w);
942 gtk_widget_get_allocation (w, &alloc);
943
944 gtk_render_background (context, ct, 0, 0, alloc.width, alloc.height);
945
946 if (gtk_cairo_should_draw_window (ct, priv->bin_window))
947 Foreach_Item
948 gtk_container_propagate_draw (GTK_CONTAINER (axis), item, ct);
949 EndFor;
950
951 return GDK_EVENT_PROPAGATE;
952 }
953
954 static void
__realize(GtkWidget * w)955 __realize (GtkWidget *w)
956 {
957 SswSheetAxis *axis = SSW_SHEET_AXIS (w);
958 PRIV_DECL (axis);
959 GtkAllocation allocation;
960 GdkWindowAttr attributes = { 0, };
961 GdkWindow *window;
962
963 gtk_widget_get_allocation (w, &allocation);
964
965 attributes.x = allocation.x;
966 attributes.y = allocation.y;
967 attributes.width = allocation.width;
968 attributes.height = allocation.height;
969 attributes.window_type = GDK_WINDOW_CHILD;
970 attributes.event_mask = gtk_widget_get_events (w) |
971 GDK_ALL_EVENTS_MASK;
972 attributes.wclass = GDK_INPUT_OUTPUT;
973
974 GdkWindow *pwin = gtk_widget_get_parent_window (w);
975 window = gdk_window_new (pwin,
976 &attributes, GDK_WA_X | GDK_WA_Y);
977 gtk_widget_register_window (w, window);
978 gtk_widget_set_window (w, window);
979
980 priv->bin_window =
981 gdk_window_new (window, &attributes, GDK_WA_X | GDK_WA_Y);
982 gtk_widget_register_window (w, priv->bin_window);
983 gdk_window_show (priv->bin_window);
984
985 Foreach_Item
986 gtk_widget_set_parent_window (item, priv->bin_window);
987 EndFor;
988
989 GdkDisplay *display = gtk_widget_get_display (w);
990
991 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
992 priv->resize_cursor = gdk_cursor_new_for_display (display, GDK_SB_V_DOUBLE_ARROW);
993 else
994 priv->resize_cursor = gdk_cursor_new_for_display (display, GDK_SB_H_DOUBLE_ARROW);
995 gtk_widget_set_realized (w, TRUE);
996 }
997
998 static void
__unrealize(GtkWidget * widget)999 __unrealize (GtkWidget *widget)
1000 {
1001 PRIV_DECL (widget);
1002
1003 g_object_unref (priv->resize_cursor);
1004
1005 if (priv->bin_window != NULL)
1006 {
1007 gtk_widget_unregister_window (widget, priv->bin_window);
1008 gdk_window_destroy (priv->bin_window);
1009 priv->bin_window = NULL;
1010 }
1011
1012 gtk_widget_set_realized (widget, FALSE);
1013
1014 GTK_WIDGET_CLASS (ssw_sheet_axis_parent_class)->unrealize (widget);
1015 }
1016
1017 static void
__map(GtkWidget * widget)1018 __map (GtkWidget *widget)
1019 {
1020 GTK_WIDGET_CLASS (ssw_sheet_axis_parent_class)->map (widget);
1021
1022 ensure_visible_widgets ((SswSheetAxis *)widget, TRUE);
1023 }
1024
1025 static void
__get_preferred_width(GtkWidget * widget,gint * min,gint * nat)1026 __get_preferred_width (GtkWidget *widget, gint *min, gint *nat)
1027 {
1028 *min = 0;
1029 *nat = 0;
1030 }
1031
1032 static void
__get_preferred_height(GtkWidget * widget,gint * min,gint * nat)1033 __get_preferred_height (GtkWidget *widget, gint *min, gint *nat)
1034 {
1035 *min = 0;
1036 *nat = 0;
1037 }
1038
1039 /* }}} */
1040
1041
1042
1043 static void
__set_orientation(GObject * object)1044 __set_orientation (GObject *object)
1045 {
1046 PRIV_DECL (object);
1047
1048 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
1049 {
1050 g_object_set (object, "vexpand", TRUE, NULL);
1051 priv->get_allocated_p_size = gtk_widget_get_allocated_height;
1052 priv->get_allocated_q_size = gtk_widget_get_allocated_width;
1053 priv->get_preferred_p_for_q =
1054 gtk_widget_get_preferred_height_for_width;
1055 priv->get_window_p_size = gdk_window_get_height;
1056
1057 priv->set_q_offset = zz_set_alloc_x;
1058
1059 priv->get_q_size = zz_get_alloc_width;
1060 priv->set_q_size = zz_set_alloc_width;
1061
1062 priv->set_p_offset = zz_set_alloc_y;
1063
1064 priv->get_p_size = zz_get_alloc_height;
1065 priv->set_p_size = zz_set_alloc_height;
1066 }
1067 else
1068 {
1069 g_object_set (object, "hexpand", TRUE, NULL);
1070
1071 priv->get_allocated_p_size = gtk_widget_get_allocated_width;
1072 priv->get_allocated_q_size = gtk_widget_get_allocated_height;
1073 priv->get_preferred_p_for_q =
1074 gtk_widget_get_preferred_width_for_height;
1075 priv->get_window_p_size = gdk_window_get_width;
1076
1077 priv->set_q_offset = zz_set_alloc_y;
1078
1079 priv->get_q_size = zz_get_alloc_height;
1080 priv->set_q_size = zz_set_alloc_height;
1081
1082 priv->set_p_offset = zz_set_alloc_x;
1083
1084 priv->get_p_size = zz_get_alloc_width;
1085 priv->set_p_size = zz_set_alloc_width;
1086 }
1087 }
1088
1089 static gboolean
on_drag_drop(GtkWidget * widget,GdkDragContext * context,gint x_,gint y_,guint time,gpointer user_data)1090 on_drag_drop (GtkWidget *widget,
1091 GdkDragContext *context,
1092 gint x_,
1093 gint y_,
1094 guint time,
1095 gpointer user_data)
1096 {
1097 /* For reasons I don't understand, the x_ and y_ parameters of this
1098 function are wrong when the target is beyond the size of the axis.
1099 So we don't use them. Instead we get the pointer position
1100 explicitly. */
1101 gint xx, yy;
1102 GdkWindow *win = gtk_widget_get_window (widget);
1103 GdkDevice *device = gdk_drag_context_get_device (context);
1104 gdk_window_get_device_position (win, device, &xx, &yy, NULL);
1105
1106 gint posn;
1107 switch (gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)))
1108 {
1109 case GTK_ORIENTATION_HORIZONTAL:
1110 posn = xx;
1111 break;
1112 case GTK_ORIENTATION_VERTICAL:
1113 posn = yy;
1114 break;
1115 default:
1116 g_assert_not_reached ();
1117 break;
1118 }
1119
1120 gint to = ssw_sheet_axis_find_cell (SSW_SHEET_AXIS (widget), posn,
1121 NULL, NULL);
1122
1123 gint from = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
1124 "from"));
1125
1126 if (to >= ssw_sheet_axis_get_size (SSW_SHEET_AXIS (widget)))
1127 to = ssw_sheet_axis_get_size (SSW_SHEET_AXIS (widget));
1128
1129 g_signal_emit (widget, signals [DRAG_N_DROP], 0, from, to);
1130 gtk_drag_finish (context, TRUE, TRUE, time);
1131
1132 return TRUE;
1133 }
1134
1135 static void
setup_drag_operation(GtkGesture * g,gdouble x_,gdouble y_,gpointer ud)1136 setup_drag_operation (GtkGesture *g, gdouble x_, gdouble y_, gpointer ud)
1137 {
1138 SswSheetAxis *axis = SSW_SHEET_AXIS (ud);
1139 PRIV_DECL (axis);
1140
1141 GdkEventSequence *seq =
1142 gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (g));
1143
1144 gdouble x, y;
1145 gtk_gesture_get_point (g, seq, &x, &y);
1146
1147 gint posn;
1148 switch (gtk_orientable_get_orientation (GTK_ORIENTABLE (axis)))
1149 {
1150 case GTK_ORIENTATION_HORIZONTAL:
1151 posn = x;
1152 break;
1153 case GTK_ORIENTATION_VERTICAL:
1154 posn = y;
1155 break;
1156 default:
1157 g_assert_not_reached ();
1158 break;
1159 }
1160
1161 gint which = ssw_sheet_axis_find_cell (axis, posn, NULL, NULL);
1162
1163 if (which < ssw_sheet_axis_get_size (axis))
1164 {
1165 gtk_gesture_set_sequence_state (g, seq, GTK_EVENT_SEQUENCE_CLAIMED);
1166 const GdkEvent *ev = gtk_gesture_get_last_event (g, seq);
1167 GdkEvent *e = gdk_event_copy (ev);
1168 gtk_drag_begin_with_coordinates (GTK_WIDGET (axis),
1169 priv->drag_target_list,
1170 GDK_ACTION_MOVE, 1,
1171 e, x, y);
1172 g_object_set_data (G_OBJECT (axis), "from", GINT_TO_POINTER (which));
1173 gdk_event_free (e);
1174 }
1175 }
1176
1177 static void
__set_pq_adjustments(GObject * object)1178 __set_pq_adjustments (GObject *object)
1179 {
1180 if (PRIV (object)->adjustment)
1181 g_signal_connect (G_OBJECT (PRIV (object)->adjustment),
1182 "value-changed", G_CALLBACK (value_changed_cb),
1183 object);
1184 }
1185
1186 /* GObject vfuncs {{{ */
1187 static void
__set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1188 __set_property (GObject *object,
1189 guint prop_id, const GValue *value, GParamSpec *pspec)
1190 {
1191 switch (prop_id)
1192 {
1193 case PROP_ADJUSTMENT:
1194 g_set_object (&PRIV (object)->adjustment, g_value_get_object (value));
1195 __set_pq_adjustments (object);
1196 break;
1197 case PROP_ORIENTATION:
1198 PRIV (object)->orientation = g_value_get_enum (value);
1199 __set_orientation (object);
1200 __set_pq_adjustments (object);
1201 break;
1202 case PROP_DRAGGABLE:
1203 if (g_value_get_boolean (value))
1204 {
1205 GtkTargetEntry te = {"move-axis-item", GTK_TARGET_SAME_APP, 0};
1206 gtk_drag_dest_set (GTK_WIDGET (object), GTK_DEST_DEFAULT_ALL, &te,
1207 1, GDK_ACTION_MOVE);
1208 PRIV (object)->drag_target_list = gtk_target_list_new (&te, 1);
1209 PRIV (object)->drag_handler_id =
1210 g_signal_connect (PRIV (object)->drag_gest, "pressed",
1211 G_CALLBACK (setup_drag_operation), object);
1212 }
1213 else
1214 {
1215 PRIV (object)->drag_target_list = NULL;
1216 if (PRIV (object)->drag_handler_id > 0)
1217 g_signal_handler_disconnect (PRIV (object)->drag_gest,
1218 PRIV (object)->drag_handler_id);
1219 PRIV (object)->drag_handler_id = 0;
1220 }
1221 break;
1222 default:
1223 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1224 break;
1225 }
1226 }
1227
1228 static void
__get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1229 __get_property (GObject *object,
1230 guint prop_id, GValue *value, GParamSpec *pspec)
1231 {
1232 switch (prop_id)
1233 {
1234 case PROP_ADJUSTMENT:
1235 g_value_set_object (value, PRIV (object)->adjustment);
1236 break;
1237 case PROP_ORIENTATION:
1238 g_value_set_enum (value, PRIV (object)->orientation);
1239 break;
1240 case PROP_DRAGGABLE:
1241 g_value_set_boolean (value, PRIV (object)->drag_target_list != NULL);
1242 break;
1243 default:
1244 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1245 break;
1246 }
1247 }
1248
1249 static void
__dispose(GObject * obj)1250 __dispose (GObject *obj)
1251 {
1252 SswSheetAxis *axis = SSW_SHEET_AXIS (obj);
1253 PRIV_DECL (obj);
1254 if (priv->dispose_has_run)
1255 return;
1256
1257 priv->dispose_has_run = TRUE;
1258
1259 g_object_unref (priv->button_gest);
1260 g_object_unref (priv->resize_gest);
1261 g_object_unref (priv->drag_gest);
1262 if (priv->adjustment)
1263 g_object_unref (priv->adjustment);
1264
1265 if (priv->drag_target_list)
1266 gtk_target_list_unref (priv->drag_target_list);
1267
1268 G_OBJECT_CLASS (ssw_sheet_axis_parent_class)->dispose (obj);
1269 }
1270
1271 static void
__finalize(GObject * obj)1272 __finalize (GObject *obj)
1273 {
1274 SswSheetAxis *axis = SSW_SHEET_AXIS (obj);
1275 PRIV_DECL (obj);
1276
1277 g_hash_table_destroy (priv->size_override);
1278 g_ptr_array_free (priv->pool, TRUE);
1279 g_ptr_array_free (priv->widgets, TRUE);
1280
1281 if (axis->cell_limits)
1282 g_ptr_array_free (axis->cell_limits, TRUE);
1283
1284 G_OBJECT_CLASS (ssw_sheet_axis_parent_class)->finalize (obj);
1285 }
1286
1287 /* }}} */
1288
1289 GtkWidget *
ssw_sheet_axis_new(GtkOrientation orientation)1290 ssw_sheet_axis_new (GtkOrientation orientation)
1291 {
1292 return GTK_WIDGET (g_object_new (SSW_TYPE_SHEET_AXIS,
1293 "orientation", orientation,
1294 NULL));
1295 }
1296
1297 void
ssw_sheet_axis_set_model(SswSheetAxis * axis,GListModel * model)1298 ssw_sheet_axis_set_model (SswSheetAxis *axis, GListModel *model)
1299 {
1300 PRIV_DECL (axis);
1301
1302 if (priv->model != NULL)
1303 g_object_unref (priv->model);
1304
1305 PRIV (axis)->model = model;
1306 if (model != NULL)
1307 {
1308 g_signal_connect_object (G_OBJECT (model), "items-changed",
1309 G_CALLBACK (items_changed_cb), axis, 0);
1310 g_object_ref (model);
1311 }
1312
1313 ensure_visible_widgets (axis, TRUE);
1314 }
1315
1316 GListModel *
ssw_sheet_axis_get_model(SswSheetAxis * axis)1317 ssw_sheet_axis_get_model (SswSheetAxis *axis)
1318 {
1319 return PRIV (axis)->model;
1320 }
1321
1322
1323 static void
__direction_changed(GtkWidget * w,GtkTextDirection prev_dir)1324 __direction_changed (GtkWidget *w, GtkTextDirection prev_dir)
1325 {
1326 SswSheetAxis *axis = SSW_SHEET_AXIS (w);
1327 PRIV_DECL (axis);
1328
1329 if (gtk_orientable_get_orientation (GTK_ORIENTABLE (w)) ==
1330 GTK_ORIENTATION_HORIZONTAL)
1331 {
1332 gdouble u = gtk_adjustment_get_upper (priv->adjustment);
1333 gdouble ps = gtk_adjustment_get_page_size (priv->adjustment);
1334 gdouble val = gtk_adjustment_get_value (priv->adjustment);
1335
1336 gtk_adjustment_set_value (priv->adjustment, u - ps - val);
1337 }
1338
1339 GTK_WIDGET_CLASS (ssw_sheet_axis_parent_class)->direction_changed (w, prev_dir);
1340 }
1341
1342 static void
ssw_sheet_axis_class_init(SswSheetAxisClass * class)1343 ssw_sheet_axis_class_init (SswSheetAxisClass *class)
1344 {
1345 GObjectClass *object_class = G_OBJECT_CLASS (class);
1346 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
1347 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
1348
1349 GParamSpec *adjust_spec =
1350 g_param_spec_object ("adjustment",
1351 P_("Adjustment"),
1352 P_("The Adjustment"),
1353 GTK_TYPE_ADJUSTMENT,
1354 G_PARAM_READWRITE);
1355
1356 GParamSpec *draggable_spec =
1357 g_param_spec_boolean ("draggable",
1358 P_("Draggable"),
1359 P_("Whether items in the axis can be reordered using drag and drop"),
1360 FALSE,
1361 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
1362
1363 object_class->set_property = __set_property;
1364 object_class->get_property = __get_property;
1365 object_class->dispose = __dispose;
1366 object_class->finalize = __finalize;
1367
1368 widget_class->draw = __draw;
1369 widget_class->size_allocate = __size_allocate;
1370 widget_class->map = __map;
1371 widget_class->get_preferred_width = __get_preferred_width;
1372 widget_class->get_preferred_height = __get_preferred_height;
1373 widget_class->realize = __realize;
1374 widget_class->unrealize = __unrealize;
1375 widget_class->direction_changed = __direction_changed;
1376
1377 container_class->add = __add;
1378 container_class->remove = __remove;
1379 container_class->forall = __forall;
1380
1381 signals [CHANGED] =
1382 g_signal_new ("changed",
1383 G_TYPE_FROM_CLASS (class),
1384 G_SIGNAL_RUN_FIRST,
1385 0,
1386 NULL, NULL,
1387 g_cclosure_marshal_VOID__VOID,
1388 G_TYPE_NONE,
1389 0);
1390
1391
1392 signals [HEADER_CLICKED] =
1393 g_signal_new ("header-clicked",
1394 G_TYPE_FROM_CLASS (class),
1395 G_SIGNAL_RUN_FIRST,
1396 0,
1397 NULL, NULL,
1398 ssw_cclosure_marshal_VOID__INT_UINT,
1399 G_TYPE_NONE,
1400 2,
1401 G_TYPE_INT,
1402 G_TYPE_UINT);
1403
1404 signals [HEADER_DOUBLE_CLICKED] =
1405 g_signal_new ("header-double-clicked",
1406 G_TYPE_FROM_CLASS (class),
1407 G_SIGNAL_RUN_FIRST,
1408 0,
1409 NULL, NULL,
1410 ssw_cclosure_marshal_VOID__INT_UINT,
1411 G_TYPE_NONE,
1412 2,
1413 G_TYPE_INT,
1414 G_TYPE_UINT);
1415
1416 signals [HEADER_BUTTON_PRESSED] =
1417 g_signal_new ("header-button-pressed",
1418 G_TYPE_FROM_CLASS (class),
1419 G_SIGNAL_RUN_FIRST,
1420 0,
1421 NULL, NULL,
1422 ssw_cclosure_marshal_VOID__INT_UINT_UINT,
1423 G_TYPE_NONE,
1424 3,
1425 G_TYPE_INT,
1426 G_TYPE_UINT,
1427 G_TYPE_UINT);
1428
1429 signals [HEADER_BUTTON_RELEASED] =
1430 g_signal_new ("header-button-released",
1431 G_TYPE_FROM_CLASS (class),
1432 G_SIGNAL_RUN_FIRST,
1433 0,
1434 NULL, NULL,
1435 ssw_cclosure_marshal_VOID__INT_UINT_UINT,
1436 G_TYPE_NONE,
1437 3,
1438 G_TYPE_INT,
1439 G_TYPE_UINT,
1440 G_TYPE_UINT);
1441
1442
1443 signals [DRAG_N_DROP] =
1444 g_signal_new ("drag-n-dropped",
1445 G_TYPE_FROM_CLASS (class),
1446 G_SIGNAL_RUN_FIRST,
1447 0,
1448 NULL, NULL,
1449 ssw_cclosure_marshal_VOID__INT_INT,
1450 G_TYPE_NONE,
1451 2,
1452 G_TYPE_INT,
1453 G_TYPE_INT);
1454
1455
1456 g_object_class_install_property (object_class,
1457 PROP_ADJUSTMENT,
1458 adjust_spec);
1459
1460 g_object_class_install_property (object_class,
1461 PROP_DRAGGABLE,
1462 draggable_spec);
1463
1464 g_object_class_override_property (object_class, PROP_ORIENTATION,
1465 "orientation");
1466 }
1467
1468 static gboolean
on_motion_notify(SswSheetAxis * axis,GdkEventMotion * e,gpointer ud)1469 on_motion_notify (SswSheetAxis *axis, GdkEventMotion *e, gpointer ud)
1470 {
1471 const gint threshold = 5;
1472 PRIV_DECL (axis);
1473
1474 gdouble x, y;
1475 gdk_window_coords_to_parent (e->window, e->x, e->y, &x, &y);
1476
1477 gdouble posn = (priv->orientation == GTK_ORIENTATION_HORIZONTAL) ? x : y;
1478
1479 gint pos, size;
1480 ssw_sheet_axis_find_cell (axis, posn, &pos, &size);
1481
1482 gint boundary =
1483 (fabs (pos - posn) < fabs (pos + size - posn)) ? pos : pos + size;
1484
1485 gdouble diff = fabs (boundary - posn);
1486
1487 GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (axis));
1488
1489 if (diff < threshold && pos > 0)
1490 gdk_window_set_cursor (win, priv->resize_cursor);
1491 else
1492 gdk_window_set_cursor (win, NULL);
1493
1494 return FALSE;
1495 }
1496
1497 static void
on_resize_drag_begin(GtkGesture * g,gdouble x,gdouble y,gpointer ud)1498 on_resize_drag_begin (GtkGesture *g, gdouble x, gdouble y, gpointer ud)
1499 {
1500 SswSheetAxis *axis = SSW_SHEET_AXIS (ud);
1501 PRIV_DECL (axis);
1502
1503 gdouble posn = (priv->orientation == GTK_ORIENTATION_HORIZONTAL) ? x : y;
1504
1505 gint pos, size;
1506 gint which = ssw_sheet_axis_find_cell (axis, posn, &pos, &size);
1507
1508 gint target =
1509 (fabs (pos - posn) < fabs (pos + size - posn)) ? which - 1: which ;
1510
1511 priv->resize_target = target;
1512 }
1513
1514 static gboolean
do_resize(gpointer ud)1515 do_resize (gpointer ud)
1516 {
1517 SswSheetAxis *axis = SSW_SHEET_AXIS (ud);
1518 PRIV_DECL (axis);
1519
1520 ssw_sheet_axis_override_size (axis, priv->resize_target, priv->new_size);
1521
1522 return FALSE;
1523 }
1524
1525 static void
on_resize_end(GtkGesture * g,gdouble x,gdouble y,gpointer ud)1526 on_resize_end (GtkGesture *g, gdouble x, gdouble y, gpointer ud)
1527 {
1528 SswSheetAxis *axis = SSW_SHEET_AXIS (ud);
1529 PRIV_DECL (axis);
1530
1531 gint old_size;
1532 gdouble delta = (priv->orientation == GTK_ORIENTATION_HORIZONTAL) ? x : y;
1533
1534 ssw_sheet_axis_find_boundary (axis, priv->resize_target, NULL, &old_size);
1535 priv->new_size = old_size + delta;
1536
1537 /* I have no idea why this must be in an idle callback.
1538 I get critical warnings if I call it directly */
1539 g_idle_add (do_resize, axis);
1540 }
1541
1542 static void
on_resize_begin(GtkGesture * g,GdkEventSequence * seq,gpointer ud)1543 on_resize_begin (GtkGesture *g, GdkEventSequence *seq, gpointer ud)
1544 {
1545 SswSheetAxis *axis = SSW_SHEET_AXIS (ud);
1546 PRIV_DECL (axis);
1547
1548 GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (axis));
1549 if (gdk_window_get_cursor (win) == priv->resize_cursor)
1550 gtk_gesture_set_sequence_state (g, seq, GTK_EVENT_SEQUENCE_CLAIMED);
1551 }
1552
1553 static void
on_multi_press_begin(GtkGesture * g,GdkEventSequence * seq,gpointer ud)1554 on_multi_press_begin (GtkGesture *g, GdkEventSequence *seq, gpointer ud)
1555 {
1556 SswSheetAxis *axis = SSW_SHEET_AXIS (ud);
1557 PRIV_DECL (axis);
1558
1559 GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (axis));
1560 if (gdk_window_get_cursor (win) != priv->resize_cursor)
1561 {
1562 gtk_gesture_set_sequence_state (g, seq, GTK_EVENT_SEQUENCE_CLAIMED);
1563 }
1564 }
1565
1566 /* Note: This is a GtkWidget handler, NOT a GtkGesture handler */
1567 static void
update_pointer(GtkWidget * widget,GdkDragContext * context,gpointer user_data)1568 update_pointer (GtkWidget *widget,
1569 GdkDragContext *context,
1570 gpointer user_data)
1571 {
1572 /* This forces the pointer to move. For some reason, without it,
1573 Dnd icon stays at 0,0 until the user moves the mouse. */
1574 GdkDevice *device = gdk_drag_context_get_device (context);
1575 GdkScreen *screen = NULL;
1576 gint x, y;
1577 gdk_device_get_position (device, &screen, &x, &y);
1578 gdk_device_warp (device, screen, x, y);
1579 }
1580
1581 static void
ssw_sheet_axis_init(SswSheetAxis * axis)1582 ssw_sheet_axis_init (SswSheetAxis *axis)
1583 {
1584 PRIV_DECL (axis);
1585
1586 gtk_widget_set_has_window (GTK_WIDGET (axis), TRUE);
1587
1588 GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (axis));
1589
1590 priv->adjustment = NULL;
1591 priv->widgets = g_ptr_array_new ();
1592 priv->pool = g_ptr_array_new ();
1593 priv->model_from = 0;
1594 priv->model_to = 0;
1595 priv->bin_start_diff = 0;
1596 priv->dispose_has_run = FALSE;
1597
1598 priv->size_override = g_hash_table_new (g_direct_hash, g_direct_equal);
1599
1600 gtk_style_context_add_class (context, "list");
1601 axis->cell_limits = NULL;
1602
1603 priv->resize_gest = gtk_gesture_drag_new (GTK_WIDGET (axis));
1604 priv->button_gest = gtk_gesture_multi_press_new (GTK_WIDGET (axis));
1605 priv->drag_gest = gtk_gesture_long_press_new (GTK_WIDGET (axis));
1606
1607 g_object_set (priv->drag_gest, "delay-factor", 0.5, NULL);
1608
1609 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->drag_gest),
1610 GTK_PHASE_CAPTURE);
1611
1612 g_signal_connect (axis, "drag-begin", G_CALLBACK (update_pointer), axis);
1613
1614 /* Listen on all buttons */
1615 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->button_gest), 0);
1616
1617 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->button_gest),
1618 GTK_PHASE_CAPTURE);
1619
1620 g_signal_connect (priv->button_gest, "begin", G_CALLBACK (on_multi_press_begin), axis);
1621 g_signal_connect (priv->button_gest, "released", G_CALLBACK (button_released), axis);
1622 g_signal_connect (priv->button_gest, "pressed", G_CALLBACK (button_pressed), axis);
1623 g_signal_connect (priv->button_gest, "stopped", G_CALLBACK (stopped), axis);
1624 priv->n_press = 0;
1625 g_signal_connect (axis, "motion-notify-event", G_CALLBACK (on_motion_notify), axis);
1626
1627
1628 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->resize_gest),
1629 GTK_PHASE_CAPTURE);
1630
1631 g_signal_connect (priv->resize_gest, "begin", G_CALLBACK (on_resize_begin), axis);
1632 g_signal_connect (priv->resize_gest, "drag-begin", G_CALLBACK (on_resize_drag_begin),
1633 axis);
1634
1635 g_signal_connect (priv->resize_gest, "drag-end", G_CALLBACK (on_resize_end), axis);
1636
1637 priv->drag_handler_id = 0;
1638 priv->drag_target_list = NULL;
1639 g_signal_connect (axis, "drag-drop", G_CALLBACK (on_drag_drop), NULL);
1640 }
1641
1642 static void ssw_sheet_axis_jump_start_with_offset (SswSheetAxis *axis, gint whereto, gint offs);
1643
1644 static void ssw_sheet_axis_jump_end_with_offset (SswSheetAxis *axis, gint whereto, gint offs);
1645
1646
1647 /* Request a specific size for item POS. Overriding the natural
1648 size for the item. */
1649 void
ssw_sheet_axis_override_size(SswSheetAxis * axis,gint pos,gint size)1650 ssw_sheet_axis_override_size (SswSheetAxis *axis, gint pos, gint size)
1651 {
1652 PRIV_DECL (axis);
1653
1654 GListModel *model = ssw_sheet_axis_get_model (axis);
1655 GType t = G_OBJECT_TYPE (model);
1656 gboolean done_resize = FALSE;
1657 guint sig = g_signal_lookup ("resize-item", t);
1658 if (sig != 0)
1659 g_signal_emit (model, sig, 0, pos, size, &done_resize);
1660
1661 if (done_resize)
1662 return;
1663
1664 g_hash_table_insert (priv->size_override,
1665 GINT_TO_POINTER (pos),
1666 GINT_TO_POINTER (size));
1667
1668 guint width = gtk_widget_get_allocated_width (GTK_WIDGET (axis));
1669
1670 gint loc;
1671 gint start_cell =
1672 ssw_sheet_axis_find_cell (axis,
1673 ssw_sheet_axis_rtl (axis) ? width : 0,
1674 &loc, NULL);
1675
1676 ensure_visible_widgets (axis, TRUE);
1677
1678 if (ssw_sheet_axis_rtl (axis))
1679 ssw_sheet_axis_jump_start_with_offset (axis, 1 + start_cell, loc - width);
1680 else
1681 ssw_sheet_axis_jump_start_with_offset (axis, start_cell, loc);
1682 }
1683
1684
1685 /*
1686 Find the row/column at POS.
1687 This is linear in the number of visible items.
1688 Returns -1 id there is no row/column at POS.
1689 */
1690 gint
ssw_sheet_axis_find_cell(SswSheetAxis * axis,gdouble pos,gint * location,gint * size)1691 ssw_sheet_axis_find_cell (SswSheetAxis *axis, gdouble pos, gint *location, gint *size)
1692 {
1693 gint x = ssw_sheet_axis_rtl (axis) ? axis->first_cell - 1: axis->last_cell;
1694 gint prev = G_MAXINT;
1695
1696 if (axis->cell_limits->len == 0)
1697 return -1;
1698
1699 int i;
1700 const int step = ssw_sheet_axis_rtl (axis) ? -1 : +1;
1701 for (i = axis->cell_limits->len - 1; i >= 0; --i)
1702 {
1703 const SswGeometry *geom = g_ptr_array_index (axis->cell_limits,
1704 ssw_sheet_axis_rtl (axis)? axis->cell_limits->len - 1 - i: i);
1705 const gint end = geom->position;
1706
1707 /* This list should be monotonically decreasing.
1708 If it isn't, then something has gone wrong */
1709 g_return_val_if_fail (end < prev, -1);
1710 x -= step;
1711 if (pos >= end)
1712 {
1713 if (location)
1714 *location = end;
1715 if (size)
1716 *size = geom->size;
1717 break;
1718 }
1719 prev = end;
1720 }
1721
1722 return x;
1723 }
1724
1725
1726 /*
1727 Find the dimensions of the row/column indexed by POS and
1728 store the results in LOCATION and SIZE.
1729
1730 Returns zero on success. If POS is outside the visible
1731 range, then returns -1 if it is above/left-of the visible
1732 range or +1 if it is below/right-of it.
1733 */
1734 gint
ssw_sheet_axis_find_boundary(SswSheetAxis * axis,gint pos,gint * location,gint * size)1735 ssw_sheet_axis_find_boundary (SswSheetAxis *axis, gint pos,
1736 gint *location, gint *size)
1737 {
1738 if (pos >= axis->last_cell)
1739 return +1;
1740
1741 if (pos < axis->first_cell)
1742 return -1;
1743
1744 const gint i = pos - axis->first_cell;
1745 const SswGeometry *geom = g_ptr_array_index (axis->cell_limits, i);
1746 const gint end = geom->position;
1747
1748 if (location)
1749 *location = end;
1750 if (size)
1751 *size = geom->size;
1752
1753 return 0;
1754 }
1755
1756
1757
1758
1759
1760 /* ssw_sheet_axis_get_size and ssw_sheet_axis_get_extent return similar
1761 things.
1762
1763 ssw_sheet_axis_get_size returns the number of items in the axis.
1764
1765 ssw_sheet_axis_get_extent returns the number of items in the axis PLUS
1766 the number of items needed to fill 90% of the screen beyond the last item.
1767
1768 This allows for the arrangement where, when the sheet is scrolled to its
1769 upper extreme, the real estate beyond the last cell gets filled with
1770 "inactive" rows/columns.
1771 */
1772
1773 gint
ssw_sheet_axis_get_size(SswSheetAxis * axis)1774 ssw_sheet_axis_get_size (SswSheetAxis *axis)
1775 {
1776 GListModel *model = ssw_sheet_axis_get_model (axis);
1777 g_return_val_if_fail (model, 0);
1778 return g_list_model_get_n_items (model);
1779 }
1780
1781 gint
ssw_sheet_axis_get_extent(SswSheetAxis * axis)1782 ssw_sheet_axis_get_extent (SswSheetAxis *axis)
1783 {
1784 PRIV_DECL (axis);
1785
1786 gdouble avg_widget_size = estimated_widget_size (axis);
1787 if (avg_widget_size == 0)
1788 avg_widget_size = 28;
1789
1790 gint n_items = ssw_sheet_axis_get_size (axis) ;
1791
1792 gint overshoot = priv->get_allocated_p_size (GTK_WIDGET (axis));
1793
1794 /* If the sheet is empty, then increase the overall size
1795 by 1, so that an ugly empty space at the end of the axis is not visible */
1796 if (n_items == 0)
1797 n_items += 1;
1798 else
1799 overshoot *= 0.9;
1800
1801 return n_items + overshoot / avg_widget_size;
1802 }
1803
1804
1805 /* Return the number of the first cell which is *fully* visible */
1806 gint
ssw_sheet_axis_get_first(SswSheetAxis * axis)1807 ssw_sheet_axis_get_first (SswSheetAxis *axis)
1808 {
1809 PRIV_DECL (axis);
1810 gint widget_size = priv->get_allocated_p_size (GTK_WIDGET (axis));
1811 const gint extremity = ssw_sheet_axis_rtl (axis) ? widget_size : 0;
1812 gint position, size;
1813 gint cell = ssw_sheet_axis_find_cell (axis, extremity, &position, &size);
1814
1815 if (cell == -1)
1816 return 0;
1817
1818 if (ssw_sheet_axis_rtl (axis))
1819 {
1820 if (position + size > widget_size)
1821 cell++;
1822 }
1823 else
1824 {
1825 if (position < 0)
1826 cell++;
1827 }
1828
1829 return cell;
1830 }
1831
1832 gint
ssw_sheet_axis_get_last(SswSheetAxis * axis)1833 ssw_sheet_axis_get_last (SswSheetAxis *axis)
1834 {
1835 PRIV_DECL (axis);
1836 gint widget_size = priv->get_allocated_p_size (GTK_WIDGET (axis));
1837 const gint extremity = ssw_sheet_axis_rtl (axis) ? 0 : widget_size;
1838 gint position, size;
1839 gint cell = ssw_sheet_axis_find_cell (axis, extremity, &position, &size);
1840
1841 if (cell == -1)
1842 return ssw_sheet_axis_get_size (axis);
1843
1844 if (ssw_sheet_axis_rtl (axis))
1845 {
1846 if (position < 0)
1847 cell--;
1848 }
1849 else
1850 {
1851 if (position + size > widget_size)
1852 cell--;
1853 }
1854
1855 return cell;
1856 }
1857
1858 gint
ssw_sheet_axis_get_visible_size(SswSheetAxis * axis)1859 ssw_sheet_axis_get_visible_size (SswSheetAxis *axis)
1860 {
1861 return ssw_sheet_axis_get_last (axis) - ssw_sheet_axis_get_first (axis) + 1;
1862 }
1863
1864 void
ssw_sheet_axis_info(SswSheetAxis * axis)1865 ssw_sheet_axis_info (SswSheetAxis *axis)
1866 {
1867 PRIV_DECL (axis);
1868 Foreach_Item
1869 g_print ("Size %d\n", item_size (axis, item));
1870 EndFor;
1871 }
1872
1873 static gint
adj_start(gint widget_size,gint location,gint extent)1874 adj_start (gint widget_size, gint location, gint extent)
1875 {
1876 return -location;
1877 }
1878
1879 static gint
adj_end(gint widget_size,gint location,gint extent)1880 adj_end (gint widget_size, gint location, gint extent)
1881 {
1882 return widget_size - (location + extent);
1883 }
1884
1885 /*
1886 Adjusts the scroll position to point to WHERETO.
1887 INIT_LOCATION and INIT_EXTENT are the initial
1888 values of WHERETO's geometry.
1889 */
1890 static void
fine_adjust(SswSheetAxis * axis,gint whereto,gint init_location,gint init_extent,gint (* adj)(gint,gint,gint),gint offs)1891 fine_adjust (SswSheetAxis *axis, gint whereto, gint init_location,
1892 gint init_extent, gint (*adj) (gint, gint, gint), gint offs)
1893 {
1894 PRIV_DECL (axis);
1895 gint widget_size = priv->get_allocated_p_size (GTK_WIDGET (axis));
1896
1897 gint delta = adj (widget_size, init_location, init_extent) + offs;
1898 gint old_delta = delta + 1;
1899
1900 gdouble old_v = -1;
1901 while (abs(delta) > 0)
1902 {
1903 gint location, extent;
1904 gdouble v = __axis_get_value (axis);
1905 if (ssw_sheet_axis_rtl (axis))
1906 delta = - delta;
1907 __axis_set_value (axis, v - delta);
1908 ssw_sheet_axis_find_boundary (axis, whereto, &location, &extent);
1909 delta = adj (widget_size, location, extent) + offs;
1910 if (delta == old_delta && old_v == v)
1911 {
1912 /* Should never happen ... */
1913 g_warning ("%s %p: Cannot seek to item %d.\n",
1914 G_OBJECT_TYPE_NAME (axis), axis, whereto);
1915 /* ... but avoid an infinite loop if something goes wrong */
1916 break;
1917 }
1918 old_delta = delta;
1919 old_v = v;
1920 }
1921 priv->bin_start_diff += delta;
1922 ensure_visible_widgets (axis, FALSE);
1923 }
1924
1925 /* Scrolls the axis, such that the item WHERETO is approximately
1926 in the middle of the screen.
1927 As a side effect, the LOCATION and EXTENT are populated
1928 with the location and extent of the WHERETO.
1929 This function is bounded by O(log n) in the number of items.
1930 */
1931 static void
course_adjust(SswSheetAxis * axis,gint whereto,gint * location,gint * extent)1932 course_adjust (SswSheetAxis *axis, gint whereto, gint *location, gint *extent)
1933 {
1934 PRIV_DECL (axis);
1935 gint direction ;
1936 gdouble k =
1937 gtk_adjustment_get_upper (priv->adjustment) /
1938 gtk_adjustment_get_page_size (priv->adjustment);
1939 gint old_direction = 0;
1940 do
1941 {
1942 direction = ssw_sheet_axis_find_boundary (axis, whereto,
1943 location, extent);
1944 gdouble v = __axis_get_value (axis);
1945 gdouble ps = gtk_adjustment_get_page_size (priv->adjustment);
1946 gdouble u = gtk_adjustment_get_upper (priv->adjustment);
1947 if (v + ps * k * direction > u - ps)
1948 gtk_adjustment_set_upper (priv->adjustment, u + ps);
1949
1950 __axis_set_value (axis, v + ps * k * direction);
1951 if (old_direction == -direction)
1952 k /= 2.0;
1953 old_direction = direction;
1954 }
1955 while (direction != 0);
1956 }
1957
1958
1959 /* Scroll the axis such that WHERETO is at the end */
1960 void
ssw_sheet_axis_jump_end_with_offset(SswSheetAxis * axis,gint whereto,gint offs)1961 ssw_sheet_axis_jump_end_with_offset (SswSheetAxis *axis, gint whereto, gint offs)
1962 {
1963 PRIV_DECL (axis);
1964 g_return_if_fail (whereto <= ssw_sheet_axis_get_size (axis));
1965
1966 gdouble upper = gtk_adjustment_get_upper (priv->adjustment);
1967 gdouble page_size = gtk_adjustment_get_page_size (priv->adjustment);
1968
1969 __axis_set_value (axis, (upper - page_size) * (whereto + 1)
1970 / (gdouble) ssw_sheet_axis_get_extent (axis));
1971
1972 gint location, extent;
1973 course_adjust (axis, whereto, &location, &extent);
1974 fine_adjust (axis, whereto, location, extent,
1975 ssw_sheet_axis_rtl (axis) ? adj_start : adj_end, offs);
1976 }
1977
1978
1979 /* Scroll the axis such that WHERETO is at the start */
1980 static void
ssw_sheet_axis_jump_start_with_offset(SswSheetAxis * axis,gint whereto,gint offs)1981 ssw_sheet_axis_jump_start_with_offset (SswSheetAxis *axis, gint whereto, gint offs)
1982 {
1983 PRIV_DECL (axis);
1984 g_return_if_fail (whereto < ssw_sheet_axis_get_size (axis));
1985
1986 gdouble upper = gtk_adjustment_get_upper (priv->adjustment);
1987 gdouble page_size = gtk_adjustment_get_page_size (priv->adjustment);
1988
1989 __axis_set_value (axis, (upper - page_size) * (whereto)
1990 / (gdouble) ssw_sheet_axis_get_extent (axis));
1991
1992 gint location, extent;
1993 course_adjust (axis, whereto, &location, &extent);
1994 fine_adjust (axis, whereto, location, extent,
1995 ssw_sheet_axis_rtl (axis) ? adj_end : adj_start, offs);
1996 }
1997
1998
1999 /* Scroll the axis such that WHERETO's start is (approxiamately) in the
2000 middle */
2001 void
ssw_sheet_axis_jump_center(SswSheetAxis * axis,gint whereto)2002 ssw_sheet_axis_jump_center (SswSheetAxis *axis, gint whereto)
2003 {
2004 PRIV_DECL (axis);
2005 g_return_if_fail (whereto < ssw_sheet_axis_get_size (axis));
2006
2007 gdouble upper = gtk_adjustment_get_upper (priv->adjustment);
2008 gdouble page_size = gtk_adjustment_get_page_size (priv->adjustment);
2009
2010 __axis_set_value (axis, (upper - page_size) * (whereto)
2011 / (gdouble) ssw_sheet_axis_get_extent (axis));
2012
2013 gint location, extent;
2014 course_adjust (axis, whereto, &location, &extent);
2015 }
2016
2017
2018 /* Scroll the axis such that WHERETO is at the start */
2019 void
ssw_sheet_axis_jump_start(SswSheetAxis * axis,gint whereto)2020 ssw_sheet_axis_jump_start (SswSheetAxis *axis, gint whereto)
2021 {
2022 ssw_sheet_axis_jump_start_with_offset (axis, whereto, 0);
2023 }
2024
2025
2026 /* Scroll the axis such that WHERETO is at the end */
2027 void
ssw_sheet_axis_jump_end(SswSheetAxis * axis,gint whereto)2028 ssw_sheet_axis_jump_end (SswSheetAxis *axis, gint whereto)
2029 {
2030 ssw_sheet_axis_jump_end_with_offset (axis, whereto, 0);
2031 }
2032