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