1 /*
2   A widget to display and manipulate tabular data
3   Copyright (C) 2016, 2017, 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 #include <config.h>
20 #include <math.h>
21 #include <string.h>
22 #include "ssw-sheet.h"
23 #include "ssw-sheet-body.h"
24 #include "ssw-constraint.h"
25 #include "ssw-marshaller.h"
26 
27 #define P_(X) (X)
28 
29 typedef GtkCellRenderer * (* fetch_renderer_func) (SswSheet *sheet, gint col, gint row, GType type, gpointer ud);
30 
31 static const int linewidth = 1;
32 
33 struct _SswSheetBodyPrivate
34 {
35   SswSheetAxis *vaxis;
36   SswSheetAxis *haxis;
37 
38   /* The widget which is editing the active cell,
39      or null if no cell is being edited */
40   GtkWidget *editor;
41   GtkWidget *active_cell_holder;
42 
43   /* A string of the form "%d:%d" describing the current cell */
44   gchar path[512];
45 
46   gboolean show_gridlines;
47   gboolean editable;
48 
49   GtkTreeModel *data_model;
50 
51   /* Cursors used when resizing columns and rows respectively */
52   GdkCursor *vc;
53   GdkCursor *hc;
54 
55   /* Cursor used when extending/defining a cell selection */
56   GdkCursor *xc;
57 
58   /* Gestures used when selecting / resizing */
59   GtkGesture *selection_gesture;
60   GtkGesture *horizontal_resize_gesture;
61   GtkGesture *vertical_resize_gesture;
62 
63   gboolean dispose_has_run;
64 
65   fetch_renderer_func renderer_func;
66 
67   GtkCellRenderer *default_renderer;
68 
69   SswRange *selection;
70 
71   /* The sheet to which this body belongs */
72   SswSheet *sheet;
73 
74   ssw_sheet_forward_conversion_func cf;
75   ssw_sheet_reverse_conversion_func revf;
76 };
77 
78 typedef struct _SswSheetBodyPrivate SswSheetBodyPrivate;
79 
80 G_DEFINE_TYPE_WITH_CODE (SswSheetBody, ssw_sheet_body,
81                          GTK_TYPE_LAYOUT,
82                          G_ADD_PRIVATE (SswSheetBody));
83 
84 #define PRIV_DECL(l) SswSheetBodyPrivate *priv = ((SswSheetBodyPrivate *)ssw_sheet_body_get_instance_private ((SswSheetBody *)l))
85 #define PRIV(l) ((SswSheetBodyPrivate *)ssw_sheet_body_get_instance_private ((SswSheetBody *)l))
86 
87 
88 static void start_editing (SswSheetBody *body, GdkEvent *e);
89 
90 static void
limit_selection(SswSheetBody * body)91 limit_selection (SswSheetBody *body)
92 {
93   PRIV_DECL (body);
94 
95   if (priv->selection->end_x < 0)
96     priv->selection->end_x = 0;
97 
98   if (priv->selection->end_y < 0)
99     priv->selection->end_y = 0;
100 
101   if (priv->selection->end_x >= ssw_sheet_axis_get_size (priv->haxis))
102     priv->selection->end_x = ssw_sheet_axis_get_size (priv->haxis) - 1;
103 
104   if (priv->selection->end_y >= ssw_sheet_axis_get_size (priv->vaxis))
105     priv->selection->end_y = ssw_sheet_axis_get_size (priv->vaxis) - 1;
106 }
107 
108 enum  {SELECTION_CHANGED,
109        VALUE_CHANGED,
110        n_SIGNALS};
111 
112 static guint signals [n_SIGNALS];
113 
114 static inline gboolean
get_active_cell(SswSheetBody * body,gint * col,gint * row)115 get_active_cell (SswSheetBody *body, gint *col, gint *row)
116 {
117   PRIV_DECL (body);
118 
119   SswSheetBody *active_body = NULL;
120 
121   sscanf (priv->path, "r%dc%ds%p", row, col, &active_body);
122 
123   if (active_body == NULL) /* There is no active cell! */
124     return FALSE;
125 
126   if (active_body != body) /* This body is not active */
127     return FALSE;
128 
129   if (*col < 0 || *row < 0)
130     return FALSE;
131 
132   return TRUE;
133 }
134 
135 static inline
set_active_cell(SswSheetBody * body,gint col,gint row)136 void set_active_cell (SswSheetBody *body, gint col, gint row)
137 {
138   PRIV_DECL (body);
139 
140   gint old_start_x = priv->selection->start_x;
141   gint old_start_y = priv->selection->start_y;
142 
143   priv->selection->start_x = col;
144   priv->selection->start_y = row;
145 
146   if (old_start_y < row)
147     priv->selection->end_y = row;
148 
149   if (old_start_x < col)
150     priv->selection->end_x = col;
151 
152   snprintf (priv->path, 512, "r%dc%ds%p", row, col, body);
153 }
154 
155 static void
scroll_vertically(SswSheetBody * body)156 scroll_vertically (SswSheetBody *body)
157 {
158   PRIV_DECL (body);
159 
160   if (!gtk_widget_is_visible (GTK_WIDGET (body)))
161     return;
162 
163   gint row = -1, col = -1;
164   get_active_cell (body, &col, &row);
165 
166   if (row >= 0 && col >= 0)
167     {
168       gint vlocation, hlocation;
169       gint visible = ssw_sheet_axis_find_boundary (priv->vaxis, row, &vlocation, NULL);
170 
171       gtk_container_child_get (GTK_CONTAINER (body),
172                                priv->active_cell_holder, "x", &hlocation, NULL);
173 
174       gtk_layout_move (GTK_LAYOUT (body), priv->active_cell_holder,
175                        hlocation, vlocation + 1);
176 
177       gtk_widget_set_visible (priv->active_cell_holder, (visible == 0));
178     }
179 
180   gtk_widget_queue_draw (GTK_WIDGET (body));
181 }
182 
183 static void
scroll_horizontally(SswSheetBody * body)184 scroll_horizontally (SswSheetBody *body)
185 {
186   PRIV_DECL (body);
187 
188   if (!gtk_widget_is_visible (GTK_WIDGET (body)))
189     return;
190 
191   gint row = -1, col = -1;
192   get_active_cell (body, &col, &row);
193 
194   if (row >= 0 && col >= 0)
195     {
196       gint vlocation, hlocation;
197       gint visible = ssw_sheet_axis_find_boundary (priv->haxis, col, &hlocation, NULL);
198 
199       gtk_container_child_get (GTK_CONTAINER (body), priv->active_cell_holder, "y", &vlocation, NULL);
200 
201       gtk_layout_move (GTK_LAYOUT (body), priv->active_cell_holder, hlocation + 1, vlocation);
202       gtk_widget_set_visible (priv->active_cell_holder, visible == 0);
203     }
204 
205   gtk_widget_queue_draw (GTK_WIDGET (body));
206 }
207 
208 
209 
210 
211 static void
normalise_selection(const SswRange * in,SswRange * out)212 normalise_selection (const SswRange *in, SswRange *out)
213 {
214   if (in->start_y < in->end_y)
215     {
216       out->end_y = in->end_y;
217       out->start_y = in->start_y;
218     }
219   else
220     {
221       out->start_y = in->end_y;
222       out->end_y = in->start_y;
223     }
224 
225   if (in->start_x < in->end_x)
226     {
227       out->end_x = in->end_x;
228       out->start_x = in->start_x;
229     }
230   else
231     {
232       out->start_x = in->end_x;
233       out->end_x = in->start_x;
234     }
235 }
236 
237 static void
draw_selection(SswSheetBody * body,cairo_t * cr)238 draw_selection (SswSheetBody *body, cairo_t *cr)
239 {
240   GtkWidget *w = GTK_WIDGET (body);
241   guint width = gtk_widget_get_allocated_width (w);
242   guint height = gtk_widget_get_allocated_height (w);
243   PRIV_DECL (body);
244 
245   GdkRGBA *color;
246   GtkStyleContext *sc = gtk_widget_get_style_context (w);
247   gtk_style_context_get (sc,
248                          GTK_STATE_FLAG_SELECTED,
249                          "background-color",
250                          &color, NULL);
251 
252   GtkCssProvider *cp = gtk_css_provider_new ();
253   gchar *css = g_strdup_printf ("* {background-color: rgba(%d, %d, %d, 0.25);}",
254                                 (int) (100.0 * color->red),
255                                 (int) (100.0 * color->green),
256                                 (int) (100.0 * color->blue));
257 
258   gtk_css_provider_load_from_data (cp, css, strlen (css), 0);
259 
260   gtk_style_context_add_provider (sc, GTK_STYLE_PROVIDER (cp),
261                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
262 
263   /* Draw the selection */
264   if ((priv->selection->start_x != priv->selection->end_x)
265       ||
266       (priv->selection->start_y != priv->selection->end_y))
267     {
268       SswRange mySelect;
269       normalise_selection (priv->selection, &mySelect);
270       gint xpos_start = 0;
271       gint ypos_start = 0;
272       if (0 < ssw_sheet_axis_find_boundary (priv->haxis, mySelect.start_x,
273                                             &xpos_start,  NULL))
274         goto done;
275 
276       if (0 < ssw_sheet_axis_find_boundary (priv->vaxis, mySelect.start_y,
277                                             &ypos_start,  NULL))
278         goto done;
279 
280       gint xpos_end, ypos_end, yextent, xextent;
281       gint xrel = ssw_sheet_axis_find_boundary (priv->haxis,
282                                                 mySelect.end_x, &xpos_end,
283                                                 &xextent);
284       if (xrel < 0)
285         goto done;
286 
287       gint xsize = (xrel == 0) ? xpos_end - xpos_start + xextent : width;
288 
289 
290       gint yrel = ssw_sheet_axis_find_boundary (priv->vaxis,
291                                                 mySelect.end_y, &ypos_end,
292                                                 &yextent);
293       if (yrel < 0)
294         goto done;
295 
296       gint ysize = (yrel == 0) ? ypos_end - ypos_start + yextent : height;
297 
298       gtk_render_background (sc, cr,
299                              xpos_start, ypos_start,
300                              xsize, ysize);
301     }
302 
303  done:
304   gtk_style_context_remove_provider (sc, GTK_STYLE_PROVIDER (cp));
305   g_free (css);
306   g_object_unref (cp);
307   gdk_rgba_free (color);
308 }
309 
310 gboolean
ssw_sheet_default_reverse_conversion(GtkTreeModel * model,gint col,gint row,const gchar * in,GValue * out)311 ssw_sheet_default_reverse_conversion (GtkTreeModel *model, gint col, gint row,
312                                       const gchar *in, GValue *out)
313 {
314   GType src_type = G_TYPE_STRING;
315   GType target_type = gtk_tree_model_get_column_type (model, col);
316 
317   GValue src_value = G_VALUE_INIT;
318 
319   if (! g_value_type_transformable (src_type, target_type))
320     {
321       g_critical ("Value of type %s cannot be transformed to type %s",
322                   g_type_name (src_type),
323                   g_type_name (target_type));
324       return FALSE;
325     }
326 
327   g_value_init (&src_value, src_type);
328   g_value_set_string (&src_value, in);
329 
330   g_value_init (out, target_type);
331 
332   g_value_transform (&src_value, out);
333   g_value_unset (&src_value);
334   return TRUE;
335 }
336 
337 
338 gchar *
ssw_sheet_default_forward_conversion(SswSheet * sheet,GtkTreeModel * m,gint col,gint row,const GValue * value)339 ssw_sheet_default_forward_conversion (SswSheet *sheet, GtkTreeModel *m,
340                                       gint col, gint row, const GValue *value)
341 {
342   GValue target_value = G_VALUE_INIT;
343 
344   g_value_init (&target_value, G_TYPE_STRING);
345 
346   if (!g_value_transform (value, &target_value))
347     {
348       if (G_VALUE_HOLDS_VARIANT (value))
349         {
350           GVariant *vt = g_value_get_variant (value);
351           gchar *s = g_variant_print (vt, FALSE);
352           g_value_unset (&target_value);
353           return s;
354         }
355       else
356         {
357           g_warning ("Failed to transform type \"%s\" to type \"%s\"\n",
358                      G_VALUE_TYPE_NAME (&value),
359                      g_type_name (G_TYPE_STRING));
360         }
361     }
362 
363   gchar *cell_text = g_value_dup_string (&target_value);
364 
365   g_value_unset (&target_value);
366 
367   return cell_text;
368 }
369 
370 
371 static GtkCellRenderer * choose_renderer (SswSheetBody *body, gint col, gint row);
372 
373 static const gchar *unfocused_border = "* {\n"
374   " border-width: 2px;\n"
375   " border-radius: 2px;\n"
376   " border-color: black;\n"
377   " border-style: dotted;\n"
378   "}";
379 
380 
381 static const gchar *focused_border = "* {\n"
382   " border-width: 2px;\n"
383   " border-radius: 2px;\n"
384   " border-color: black;\n"
385   " border-style: solid;\n"
386   "}";
387 
388 static gboolean
__draw(GtkWidget * widget,cairo_t * cr)389 __draw (GtkWidget *widget, cairo_t *cr)
390 {
391   SswSheetBody *body = SSW_SHEET_BODY (widget);
392   PRIV_DECL (body);
393 
394   if (!priv->haxis || !priv->vaxis)
395     return TRUE;
396 
397   gint active_row = -1, active_col = -1;
398   get_active_cell (body, &active_col, &active_row);
399 
400   guint width = gtk_widget_get_allocated_width (widget);
401   guint height = gtk_widget_get_allocated_height (widget);
402 
403   GtkStyleContext *sc = gtk_widget_get_style_context (widget);
404 
405   GtkCssProvider *cp = gtk_css_provider_new ();
406 
407   GtkBorder border;
408   if (priv->editable)
409     {
410       gint yy = ssw_sheet_axis_find_boundary (priv->vaxis, active_row, NULL, NULL);
411       gint xx = ssw_sheet_axis_find_boundary (priv->haxis, active_col, NULL, NULL);
412 
413       if (yy == 0 && xx == 0 && priv->editor == NULL)
414         start_editing (body, NULL);
415 
416       if ((gtk_widget_is_focus (widget) ||
417            (priv->editor && gtk_widget_is_focus (priv->editor))))
418         gtk_css_provider_load_from_data (cp, focused_border, strlen (focused_border), 0);
419       else
420         gtk_css_provider_load_from_data (cp, unfocused_border,
421                                          strlen (unfocused_border), 0);
422 
423       gtk_style_context_add_provider (sc, GTK_STYLE_PROVIDER (cp),
424                                       GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
425 
426       gtk_style_context_get_border (sc,
427                                     gtk_widget_get_state_flags (widget),
428                                     &border);
429     }
430 
431   int row = priv->vaxis->last_cell;
432   gint y;
433   for (y = priv->vaxis->cell_limits->len - 1;
434        y >= 0;
435        --y)
436     {
437       const SswGeometry *vgeom = g_ptr_array_index (priv->vaxis->cell_limits, y);
438 
439 
440       if (priv->show_gridlines)
441         {
442           gint ypos = vgeom->position;
443           ypos += vgeom->size;
444 
445           /* Draw horizontal grid lines */
446           gtk_render_line (sc, cr, 0, ypos, width, ypos);
447         }
448 
449       --row;
450       GtkTreeIter iter;
451       if (priv->data_model)
452         gtk_tree_model_iter_nth_child (priv->data_model, &iter, NULL, row);
453       int col = priv->haxis->last_cell;
454       gint x;
455       for (x = priv->haxis->cell_limits->len - 1;
456            x >= 0;
457            --x)
458         {
459           const SswGeometry *hgeom = g_ptr_array_index (priv->haxis->cell_limits, x);
460 
461           GdkRectangle rect;
462           rect.x = hgeom->position;
463           rect.y = vgeom->position;
464           rect.width = hgeom->size;
465           rect.height = vgeom->size;
466 
467           --col;
468 
469           if (priv->editable && col == active_col && row == active_row)
470             {
471               /* Draw frame */
472               gtk_render_frame (sc, cr,
473                                 rect.x - border.left,
474                                 rect.y - border.top,
475                                 rect.width + border.left + border.right + 1,
476                                 rect.height + border.top + border.bottom + 1);
477             }
478           if (priv->data_model
479               && row < gtk_tree_model_iter_n_children (priv->data_model, NULL)
480               && col < gtk_tree_model_get_n_columns (priv->data_model))
481             {
482               GtkCellRenderer *renderer = choose_renderer (body, col, row);
483 
484               if (GTK_IS_CELL_RENDERER_TEXT (renderer))
485                 {
486                   char *cell_text = NULL;
487 
488                   if (col != active_col || row != active_row ||
489                       ! priv->editable ||
490                       (priv->sheet->selected_body != GTK_WIDGET (body)))
491                     {
492                       /* Don't render the active cell.
493                          It is already rendered by the cell_editable widget
494                          and rendering twice looks unaesthetic */
495                       GValue value = G_VALUE_INIT;
496                       gtk_tree_model_get_value (priv->data_model, &iter, col, &value);
497                       cell_text = priv->cf (priv->sheet, priv->data_model, col, row, &value);
498                       g_value_unset (&value);
499                     }
500 
501                   g_object_set (renderer,
502                                 "text", cell_text,
503                                 NULL);
504 
505                   g_free (cell_text);
506                 }
507 
508 
509               gtk_cell_renderer_render (renderer, cr, widget,
510                                         &rect, &rect, 0);
511             }
512 
513           if (priv->show_gridlines)
514             {
515               /* Draw vertical grid lines */
516               gtk_render_line (sc, cr,
517                                rect.x + rect.width, 0,
518                                rect.x + rect.width, height);
519             }
520         }
521     }
522 
523   if (gtk_gesture_is_active (priv->horizontal_resize_gesture))
524     {
525       gdouble start_x, start_y;
526       gdouble offsetx, offsety;
527       gtk_gesture_drag_get_start_point (GTK_GESTURE_DRAG (priv->horizontal_resize_gesture), &start_x, &start_y);
528       gtk_gesture_drag_get_offset (GTK_GESTURE_DRAG (priv->horizontal_resize_gesture), &offsetx, &offsety);
529 
530       gtk_render_line (sc, cr, start_x + offsetx, 0, start_x + offsetx, height);
531     }
532 
533   if (gtk_gesture_is_active (priv->vertical_resize_gesture))
534     {
535       gdouble start_x, start_y;
536       gdouble offsetx, offsety;
537       gtk_gesture_drag_get_start_point (GTK_GESTURE_DRAG (priv->vertical_resize_gesture), &start_x, &start_y);
538       gtk_gesture_drag_get_offset (GTK_GESTURE_DRAG (priv->vertical_resize_gesture), &offsetx, &offsety);
539 
540       gtk_render_line (sc, cr, 0, start_y + offsety, width, start_y + offsety);
541     }
542 
543   draw_selection (body, cr);
544 
545   gtk_style_context_remove_provider (sc, GTK_STYLE_PROVIDER (cp));
546 
547   g_object_unref (cp);
548 
549   return GTK_WIDGET_CLASS (ssw_sheet_body_parent_class)->draw (widget, cr);
550 }
551 
552 
553 static void
__realize(GtkWidget * w)554 __realize (GtkWidget *w)
555 {
556   GTK_WIDGET_CLASS (ssw_sheet_body_parent_class)->realize (w);
557 
558   GdkWindow *win = gtk_widget_get_window (w);
559   gdk_window_set_events (win, GDK_KEY_PRESS_MASK |
560                          GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
561 }
562 
563 static void
__dispose(GObject * object)564 __dispose (GObject *object)
565 {
566   SswSheetBody *body = SSW_SHEET_BODY (object);
567   PRIV_DECL (body);
568 
569   if (priv->dispose_has_run)
570     return;
571 
572   if (priv->data_model)
573     g_object_unref (priv->data_model);
574 
575   priv->dispose_has_run = TRUE;
576 
577   G_OBJECT_CLASS (ssw_sheet_body_parent_class)->dispose (object);
578 }
579 
580 
581 static void
__finalize(GObject * obj)582 __finalize (GObject *obj)
583 {
584   SswSheetBody *body = SSW_SHEET_BODY (obj);
585   PRIV_DECL (body);
586 
587   g_object_unref (priv->vc);
588   g_object_unref (priv->hc);
589   g_object_unref (priv->xc);
590 
591   g_object_unref (priv->horizontal_resize_gesture);
592   g_object_unref (priv->vertical_resize_gesture);
593   g_object_unref (priv->selection_gesture);
594   g_object_unref (priv->default_renderer);
595 
596   G_OBJECT_CLASS (ssw_sheet_body_parent_class)->finalize (obj);
597 }
598 
599 
600 
601 static void
announce_selection(SswSheetBody * body)602 announce_selection (SswSheetBody *body)
603 {
604   gtk_widget_queue_draw (GTK_WIDGET (body));
605 
606   PRIV_DECL (body);
607 
608   g_signal_emit (body, signals [SELECTION_CHANGED], 0, priv->selection);
609 
610   GtkClipboard *primary =
611     gtk_clipboard_get_for_display (gtk_widget_get_display (GTK_WIDGET (body)),
612                                    GDK_SELECTION_PRIMARY);
613 
614   ssw_sheet_body_set_clip (body, primary);
615 }
616 
617 static void
set_selection(SswSheetBody * body,gint start_x,gint start_y,gint end_x,gint end_y)618 set_selection (SswSheetBody *body, gint start_x, gint start_y, gint end_x, gint end_y)
619 {
620   PRIV_DECL (body);
621 
622   priv->selection->start_x = start_x;
623   priv->selection->start_y = start_y;
624 
625   priv->selection->end_x = end_x;
626   priv->selection->end_y = end_y;
627 
628   announce_selection (body);
629 }
630 
631 /* Forces COL and ROW to be within the range of the currently loaded
632    model.   Returns TRUE if COL or ROW was changed.
633 */
634 static gboolean
trim_to_model_limits(SswSheetBody * body,gint old_col,gint old_row,gint * col,gint * row)635 trim_to_model_limits (SswSheetBody *body, gint old_col, gint old_row, gint *col, gint *row)
636 {
637   gboolean clipped = FALSE;
638   PRIV_DECL (body);
639 
640   /* Allow editing of a new cell */
641   if (priv->editable)
642     {
643       if (old_row == ssw_sheet_axis_get_size (priv->vaxis) && old_col == 0)
644         return FALSE;
645 
646       if (old_col == ssw_sheet_axis_get_size (priv->haxis) && old_row == 0)
647         return FALSE;
648 
649       if (*row == ssw_sheet_axis_get_size (priv->vaxis) && *col == 0)
650         return FALSE;
651 
652       if (*col == ssw_sheet_axis_get_size (priv->haxis) && *row == 0)
653         return FALSE;
654     }
655 
656   if (*col >= ssw_sheet_axis_get_size (priv->haxis))
657     {
658       *col = ssw_sheet_axis_get_size (priv->haxis) - 1;
659       clipped = TRUE;
660     }
661 
662   if (*row >= ssw_sheet_axis_get_size (priv->vaxis))
663     {
664       *row = ssw_sheet_axis_get_size (priv->vaxis) - 1;
665       clipped = TRUE;
666     }
667 
668   if (*col < 0)
669     {
670       *col = 0;
671       clipped = TRUE;
672     }
673 
674   if (*row < 0)
675     {
676       *row = 0;
677       clipped = TRUE;
678     }
679 
680   return clipped;
681 }
682 
683 static gboolean
__button_release_event(GtkWidget * w,GdkEventButton * e)684 __button_release_event (GtkWidget *w, GdkEventButton *e)
685 {
686   return GTK_WIDGET_CLASS (ssw_sheet_body_parent_class)->button_release_event (w, e);
687 }
688 
689 
690 
691 static void
destroy_editor_widget(SswSheetBody * body)692 destroy_editor_widget (SswSheetBody *body)
693 {
694   PRIV_DECL (body);
695   gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (priv->editor));
696 }
697 
698 static void
finish_editing(GtkSpinButton * s,gpointer user_data)699 finish_editing (GtkSpinButton *s, gpointer user_data)
700 {
701   GtkCellEditable *ce = GTK_CELL_EDITABLE (s);
702   g_object_set (ce, "editing-canceled", FALSE, NULL);
703   gtk_cell_editable_editing_done (ce);
704 }
705 
706 static void
start_editing(SswSheetBody * body,GdkEvent * e)707 start_editing (SswSheetBody *body, GdkEvent *e)
708 {
709   PRIV_DECL (body);
710 
711   gint row = -1, col = -1;
712   get_active_cell (body, &col, &row);
713 
714   /* There seems to be a bug in GtkCellRendererSpinButton, where
715      its GtkAdjustment spins at wierd moments.  Blocking the handler
716      here seems to work around the problem. */
717   if (priv->editor && GTK_IS_SPIN_BUTTON (priv->editor))
718     g_signal_handlers_block_by_func (priv->editor, finish_editing, NULL);
719 
720   if (GTK_WIDGET (body) == priv->sheet->selected_body)
721     {
722       GtkCellRenderer *renderer = choose_renderer (body, col, row);
723       GtkCellEditable *ce =
724         gtk_cell_renderer_start_editing (renderer, e, GTK_WIDGET (body),
725                                          priv->path, NULL, NULL,
726                                          GTK_CELL_RENDERER_SELECTED);
727 
728       /* We use this property with a slightly different nuance:
729          It is rather a "not-started" flag than a canceled flag. That is
730          to say, it is TRUE  by default and becomes FALSE, once an
731          edit has commenced */
732       g_object_set (ce, "editing-canceled", TRUE, NULL);
733     }
734 }
735 
736 static void on_editing_done (GtkCellEditable *e, SswSheetBody *body);
737 
738 
739 static gboolean
__button_press_event(GtkWidget * w,GdkEventButton * e)740 __button_press_event (GtkWidget *w, GdkEventButton *e)
741 {
742   SswSheetBody *body = SSW_SHEET_BODY (w);
743   PRIV_DECL (body);
744 
745   if (e->type != GDK_BUTTON_PRESS)
746     return FALSE;
747 
748   GdkEventButton *eb = (GdkEventButton *) e;
749 
750   gint old_row = -1, old_col = -1;
751   get_active_cell (body, &old_col, &old_row);
752 
753   gint col  = ssw_sheet_axis_find_cell (priv->haxis, eb->x, NULL, NULL);
754   gint row  = ssw_sheet_axis_find_cell (priv->vaxis, eb->y, NULL, NULL);
755 
756   if (trim_to_model_limits (body, old_col, old_row, &col, &row))
757     return FALSE;
758 
759   if (priv->editor)
760     on_editing_done (GTK_CELL_EDITABLE (priv->editor), body);
761 
762   if (priv->sheet->selected_body != GTK_WIDGET (body))
763     {
764       destroy_editor_widget (SSW_SHEET_BODY (priv->sheet->selected_body));
765     }
766 
767   GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (body));
768 
769   if (gdk_window_get_cursor (win) != priv->hc
770       && priv->editable
771       && gdk_window_get_cursor (win) != priv->vc)
772     set_active_cell (body, col, row);
773 
774   priv->sheet->selected_body = GTK_WIDGET (body);
775   gtk_widget_grab_focus (w);
776 
777   if (priv->editable)
778     start_editing (body, (GdkEvent *) e);
779 
780   return GTK_WIDGET_CLASS (ssw_sheet_body_parent_class)->button_press_event (w, e);
781 }
782 
783 static gboolean
is_printable_key(gint keyval)784 is_printable_key (gint keyval)
785 {
786   switch (keyval)
787     {
788     case GDK_KEY_Return:
789     case GDK_KEY_ISO_Left_Tab:
790     case GDK_KEY_Tab:
791       return FALSE;
792       break;
793     }
794 
795   return (0 != gdk_keyval_to_unicode (keyval));
796 }
797 
798 static gboolean
__key_press_event_shifted(GtkWidget * w,GdkEventKey * e)799 __key_press_event_shifted (GtkWidget *w, GdkEventKey *e)
800 {
801   SswSheetBody *body = SSW_SHEET_BODY (w);
802   PRIV_DECL (body);
803 
804   switch (e->keyval)
805     {
806     case GDK_KEY_Left:
807       if (e->state & GDK_CONTROL_MASK)
808         priv->selection->end_x = 0;
809       else
810         priv->selection->end_x--;
811       break;
812     case GDK_KEY_Right:
813       if (e->state & GDK_CONTROL_MASK)
814         priv->selection->end_x = ssw_sheet_axis_get_size (priv->haxis) - 1;
815       else
816         priv->selection->end_x++;
817       break;
818     case GDK_KEY_Up:
819       if (e->state & GDK_CONTROL_MASK)
820         priv->selection->end_y = 0;
821       else
822         priv->selection->end_y--;
823       break;
824     case GDK_KEY_Down:
825       if (e->state & GDK_CONTROL_MASK)
826         priv->selection->end_y = ssw_sheet_axis_get_size (priv->vaxis) - 1;
827       else
828         priv->selection->end_y++;
829       break;
830     default:
831       return FALSE;
832       break;
833     }
834 
835   limit_selection (body);
836 
837   announce_selection (body);
838 
839   return FALSE;
840 }
841 
842 gboolean
ssw_sheet_body_get_active_cell(SswSheetBody * body,gint * col,gint * row)843 ssw_sheet_body_get_active_cell (SswSheetBody *body,
844                                 gint *col, gint *row)
845 {
846   return get_active_cell (body, col, row);
847 }
848 
849 void
ssw_sheet_body_set_active_cell(SswSheetBody * body,gint col,gint row,GdkEvent * e)850 ssw_sheet_body_set_active_cell (SswSheetBody *body,
851                                 gint col, gint row, GdkEvent *e)
852 {
853   PRIV_DECL (body);
854 
855   if (!priv->editable)
856     return;
857 
858   if (priv->editor && (GTK_WIDGET (body) == priv->sheet->selected_body))
859     gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (priv->editor));
860 
861   gint oldrow = -1, oldcol = -1;
862   get_active_cell (body, &oldcol, &oldrow);
863 
864   if (row == -1)
865     row = oldrow;
866 
867   if (col == -1)
868     col = oldcol;
869 
870   if (row == -1)
871     row = 0;
872 
873   if (col == -1)
874     col = 0;
875 
876   set_active_cell (body, col, row);
877 
878   start_editing (body, e);
879 
880   /* Do not announce the selection if it hasn't changed.
881      Otherwise infinite loops can occur :( */
882   if (oldrow != row || oldcol != col)
883     {
884       set_selection (body, col, row, col, row);
885     }
886 }
887 
888 static gboolean
__key_press_event(GtkWidget * w,GdkEventKey * e)889 __key_press_event (GtkWidget *w, GdkEventKey *e)
890 {
891   SswSheetBody *body = SSW_SHEET_BODY (w);
892   PRIV_DECL (body);
893   /* If send_event is true, then this event has been generated by on_entry_activate*/
894   if ( !e->send_event &&
895        GTK_WIDGET_CLASS (ssw_sheet_body_parent_class)->key_press_event (w, e))
896     return TRUE;
897 
898   if (is_printable_key (e->keyval))
899     {
900       if (priv->editor && priv->editable &&
901           (priv->sheet->selected_body == GTK_WIDGET (body)))
902         {
903           gtk_widget_grab_focus (priv->editor);
904           if (GTK_IS_ENTRY (priv->editor))
905             {
906               gchar out[7] = {0,0,0,0,0,0,0};
907               guint32 code = gdk_keyval_to_unicode (e->keyval);
908               g_unichar_to_utf8 (code, out);
909               gtk_entry_set_text (GTK_ENTRY(priv->editor), out);
910               gtk_editable_set_position (GTK_EDITABLE (priv->editor), -1);
911             }
912         }
913       return FALSE;
914     }
915 
916   if (e->state & GDK_SHIFT_MASK &&
917       e->keyval != GDK_KEY_ISO_Left_Tab)
918     return __key_press_event_shifted (w, e);
919 
920   const gint vlimit = ssw_sheet_axis_get_size (priv->vaxis) - 1;
921   gint row = -1, col = -1;
922   get_active_cell (body, &col, &row);
923 
924   gint page_length = ssw_sheet_axis_get_visible_size (priv->vaxis) - 1;
925 
926   gint hstep ;
927   gint h_end_limit ;
928   gint h_start_limit ;
929 
930   if (ssw_sheet_axis_rtl (priv->haxis))
931     {
932       hstep = -1;
933       h_end_limit = 0;
934       h_start_limit = ssw_sheet_axis_get_size (priv->haxis) - 1;
935     }
936   else
937     {
938       hstep = +1;
939       h_end_limit = ssw_sheet_axis_get_size (priv->haxis) - 1;
940       h_start_limit = 0;
941     }
942 
943   const gint old_row = row;
944   const gint old_col = col;
945 
946   switch (e->keyval)
947     {
948     case GDK_KEY_Home:
949       col = 0;
950       row = 0;
951       break;
952     case GDK_KEY_Left:
953       if (e->state & GDK_CONTROL_MASK)
954         col = h_start_limit;
955       else
956         col -= hstep;
957       break;
958     case GDK_KEY_Right:
959       if (e->state & GDK_CONTROL_MASK)
960         col = h_end_limit;
961       else
962         col += hstep;
963       break;
964     case GDK_KEY_Page_Up:
965       row -= page_length;
966       break;
967     case GDK_KEY_Page_Down:
968       row += page_length;
969       break;
970     case GDK_KEY_Up:
971       if (e->state & GDK_CONTROL_MASK)
972         row = 0;
973       else
974         row--;
975       break;
976     case GDK_KEY_Down:
977       if (e->state & GDK_CONTROL_MASK)
978         row = G_MAXINT;
979       else
980         row++;
981       break;
982     case GDK_KEY_Return:
983       row++;
984       break;
985     case GDK_KEY_ISO_Left_Tab:
986       {
987         col --;
988         if (col < 0 && row > 0)
989           {
990             col = ssw_sheet_axis_get_size (priv->haxis) - 1;
991             row --;
992           }
993       }
994       break;
995     case GDK_KEY_Tab:
996       {
997         col ++;
998         if (col >= ssw_sheet_axis_get_size (priv->haxis)  && row < vlimit)
999           {
1000             col = 0;
1001             row ++;
1002           }
1003       }
1004       break;
1005     default:
1006       return FALSE;
1007       break;
1008     }
1009 
1010   trim_to_model_limits (body, old_col, old_row, &col, &row);
1011 
1012   if (col > ssw_sheet_axis_get_last (priv->haxis))
1013     ssw_sheet_axis_jump_end (priv->haxis, col);
1014 
1015   if (col < ssw_sheet_axis_get_first (priv->haxis))
1016     ssw_sheet_axis_jump_start (priv->haxis, col);
1017 
1018   if (row > ssw_sheet_axis_get_last (priv->vaxis))
1019     ssw_sheet_axis_jump_end (priv->vaxis, row);
1020 
1021   if (row < ssw_sheet_axis_get_first (priv->vaxis))
1022     ssw_sheet_axis_jump_start (priv->vaxis, row);
1023 
1024   ssw_sheet_body_set_active_cell (body, col, row, (GdkEvent *) e);
1025 
1026   if (old_row != row || old_col != col)
1027     set_selection (body, col, row, col, row);
1028 
1029   /* Return true, to stop keys unfocusing the sheet */
1030   return TRUE;
1031 }
1032 
1033 
1034 
1035 static gboolean
__motion_notify_event(GtkWidget * w,GdkEventMotion * e)1036 __motion_notify_event (GtkWidget *w, GdkEventMotion *e)
1037 {
1038   SswSheetBody *body = SSW_SHEET_BODY (w);
1039   PRIV_DECL (body);
1040 
1041   if (GTK_WIDGET_CLASS (ssw_sheet_body_parent_class)->motion_notify_event (w, e))
1042     return TRUE;
1043 
1044   if (gtk_gesture_is_active (priv->horizontal_resize_gesture) || gtk_gesture_is_active (priv->selection_gesture))
1045     return FALSE;
1046 
1047   const int threshold = 5;
1048 
1049   gint xpos;
1050   gint xsize;
1051   gint ypos;
1052   gint ysize;
1053 
1054   GdkWindow *win = gtk_widget_get_window (w);
1055 
1056   gdouble xm = e->x;
1057   gdouble ym = e->y;
1058   if (win != e->window)
1059     {
1060       gdk_window_coords_to_parent (e->window, e->x, e->y, &xm, &ym);
1061     }
1062 
1063   gint x = ssw_sheet_axis_find_cell (priv->haxis, xm, &xpos, &xsize);
1064   gint y = ssw_sheet_axis_find_cell (priv->vaxis, ym, &ypos, &ysize);
1065 
1066   if (x < 0 || y < 0)
1067     {
1068       gdk_window_set_cursor (win, NULL);
1069       return FALSE;
1070     }
1071 
1072   gint nearest_x =
1073     (fabs (xpos - xm) < fabs (xpos + xsize - xm)) ? xpos : xpos + xsize;
1074   gint nearest_y =
1075     (fabs (ypos - ym) < fabs (ypos + ysize - ym)) ? ypos : ypos + ysize;
1076 
1077   int xdiff = fabs (nearest_x - xm);
1078   int ydiff = fabs (nearest_y - ym);
1079 
1080   x = (fabs (xpos - xm) < fabs (xpos + xsize - xm)) ? x : x + 1;
1081   y = (fabs (ypos - ym) < fabs (ypos + ysize - ym)) ? y : y + 1;
1082 
1083   gint row = -1, col = -1;
1084   get_active_cell (body, &col, &row);
1085 
1086   if ((xdiff < threshold) && (ydiff < threshold) &&
1087       (row == y - 1 && col == x - 1))
1088     {
1089       gdk_window_set_cursor (win, priv->xc);
1090     }
1091   else if (xdiff < threshold && x > 0)
1092     {
1093       gdk_window_set_cursor (win, priv->hc);
1094     }
1095   else if (ydiff < threshold && y > 0)
1096     {
1097       gdk_window_set_cursor (win, priv->vc);
1098     }
1099   else
1100     {
1101       gdk_window_set_cursor (win, NULL);
1102     }
1103 
1104   return FALSE;
1105 }
1106 
1107 
1108 GtkWidget *
1109 
ssw_sheet_body_new(SswSheet * sheet)1110 ssw_sheet_body_new (SswSheet *sheet)
1111 {
1112   return GTK_WIDGET (g_object_new (SSW_TYPE_SHEET_BODY,
1113                                    "sheet", sheet,
1114                                    NULL));
1115 }
1116 
1117 
1118 enum
1119   {
1120    PROP_0,
1121    PROP_VAXIS,
1122    PROP_HAXIS,
1123    PROP_DATA_MODEL,
1124    PROP_GRIDLINES,
1125    PROP_EDITABLE,
1126    PROP_SELECTION,
1127    PROP_RENDERER_FUNC,
1128    PROP_CONVERT_FWD_FUNC,
1129    PROP_CONVERT_REV_FUNC,
1130    PROP_SHEET
1131   };
1132 
1133 static void
select_entire_row(SswSheetBody * body,gint i,guint state,gpointer ud)1134 select_entire_row (SswSheetBody *body, gint i, guint state, gpointer ud)
1135 {
1136   PRIV_DECL (body);
1137 
1138   GdkDisplay *disp = gtk_widget_get_display (GTK_WIDGET (body));
1139   GdkKeymap *km = gdk_keymap_get_for_display (disp);
1140   GdkModifierType extend_mask =
1141     gdk_keymap_get_modifier_mask (km, GDK_MODIFIER_INTENT_EXTEND_SELECTION);
1142 
1143   gint start_y = (extend_mask & state) ? priv->selection->start_y : i;
1144 
1145   set_selection (body,
1146                  0, start_y,
1147                  ssw_sheet_axis_get_size (priv->haxis) - 1, i);
1148   start_editing (body, NULL);
1149 }
1150 
1151 static void
select_entire_column(SswSheetBody * body,gint i,guint state,gpointer ud)1152 select_entire_column (SswSheetBody *body, gint i, guint state, gpointer ud)
1153 {
1154   PRIV_DECL (body);
1155   GdkDisplay *disp = gtk_widget_get_display (GTK_WIDGET (body));
1156   GdkKeymap *km = gdk_keymap_get_for_display (disp);
1157   GdkModifierType extend_mask =
1158     gdk_keymap_get_modifier_mask (km, GDK_MODIFIER_INTENT_EXTEND_SELECTION);
1159 
1160   gint start_x = (extend_mask & state) ? priv->selection->start_x : i;
1161 
1162   set_selection (body,
1163                  start_x, 0,
1164                  i, ssw_sheet_axis_get_size (priv->vaxis) - 1);
1165   start_editing (body, NULL);
1166 }
1167 
1168 
1169 static void
set_editor_widget_value(SswSheetBody * body,GValue * value,GtkEditable * editable)1170 set_editor_widget_value (SswSheetBody *body, GValue *value, GtkEditable *editable)
1171 {
1172   PRIV_DECL (body);
1173 
1174   if (editable == NULL)
1175     return;
1176 
1177   gint row = -1, col = -1;
1178   get_active_cell (body, &col, &row);
1179 
1180   /* Allow the datum to be changed only if the "editable" property is set */
1181   gtk_editable_set_editable (editable, priv->editable);
1182 
1183   if (GTK_IS_SPIN_BUTTON (editable))
1184     {
1185       if (G_IS_VALUE (value))
1186         {
1187           GValue dvalue = G_VALUE_INIT;
1188           g_value_init (&dvalue, G_TYPE_DOUBLE);
1189           if (g_value_transform (value, &dvalue))
1190             {
1191               gtk_spin_button_set_value (GTK_SPIN_BUTTON (editable), g_value_get_double (&dvalue));
1192             }
1193           g_value_unset (&dvalue);
1194         }
1195     }
1196   else if (GTK_IS_ENTRY (editable))
1197     {
1198       gchar *s = NULL;
1199       if (G_IS_VALUE (value))
1200         s = priv->cf (priv->sheet, priv->data_model, col, row, value);
1201       gtk_entry_set_text (GTK_ENTRY (editable), s ? s : "");
1202       g_free (s);
1203     }
1204   else if (GTK_IS_COMBO_BOX (editable))
1205     {
1206       gint n = -1;
1207       if (G_IS_VALUE (value))
1208         n = g_value_get_enum (value);
1209       gtk_combo_box_set_active (GTK_COMBO_BOX (editable), n);
1210     }
1211   else
1212     {
1213       gchar *x = NULL;
1214       if (G_IS_VALUE (value))
1215         x = g_strdup_value_contents (value);
1216       g_warning ("Unhandled edit widget %s, when dealing with %s\n",
1217                  G_OBJECT_TYPE_NAME (editable), x);
1218       g_free (x);
1219     }
1220 }
1221 
1222 static void
update_editable(SswSheetBody * body)1223 update_editable (SswSheetBody *body)
1224 {
1225   PRIV_DECL (body);
1226 
1227   gint row = -1, col = -1;
1228   if (! get_active_cell (body, &col, &row))
1229     return;
1230 
1231   GtkTreeIter iter;
1232   if (gtk_tree_model_iter_nth_child (priv->data_model, &iter, NULL, row))
1233     {
1234       GValue value = G_VALUE_INIT;
1235       gtk_tree_model_get_value (priv->data_model, &iter, col, &value);
1236 
1237       set_editor_widget_value (body, &value, GTK_EDITABLE (priv->editor));
1238       g_value_unset (&value);
1239     }
1240 }
1241 
1242 static void
on_data_change(GtkTreeModel * tm,guint posn,guint rm,guint add,gpointer p)1243 on_data_change (GtkTreeModel *tm, guint posn, guint rm, guint add, gpointer p)
1244 {
1245   /* Set the "editor widget" (typically a GtkEntry) to reflect the new value */
1246   SswSheetBody *body = SSW_SHEET_BODY (p);
1247   gtk_widget_queue_draw (GTK_WIDGET (body));
1248   update_editable (body);
1249 }
1250 
1251 static void
__set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1252 __set_property (GObject *object,
1253                 guint prop_id, const GValue *value, GParamSpec *pspec)
1254 {
1255   SswSheetBody *body = SSW_SHEET_BODY (object);
1256   PRIV_DECL (body);
1257 
1258   switch (prop_id)
1259     {
1260     case PROP_SHEET:
1261       priv->sheet = g_value_get_object (value);
1262       break;
1263     case PROP_VAXIS:
1264       priv->vaxis = g_value_get_object (value);
1265       g_signal_connect_swapped (priv->vaxis, "changed",
1266                                 G_CALLBACK (scroll_vertically), object);
1267       g_signal_connect_swapped (priv->vaxis, "header-clicked",
1268                                 G_CALLBACK (select_entire_row), object);
1269       break;
1270     case PROP_HAXIS:
1271       priv->haxis = g_value_get_object (value);
1272       g_signal_connect_swapped (priv->haxis, "changed",
1273                                 G_CALLBACK (scroll_horizontally), object);
1274       g_signal_connect_swapped (priv->haxis, "header-clicked",
1275                                 G_CALLBACK (select_entire_column), object);
1276       break;
1277     case PROP_GRIDLINES:
1278       priv->show_gridlines = g_value_get_boolean (value);
1279       gtk_widget_queue_draw (GTK_WIDGET (object));
1280       break;
1281     case PROP_EDITABLE:
1282       priv->editable = g_value_get_boolean (value);
1283       break;
1284     case PROP_RENDERER_FUNC:
1285       priv->renderer_func = g_value_get_pointer (value);
1286       break;
1287     case PROP_CONVERT_FWD_FUNC:
1288       priv->cf = g_value_get_pointer (value);
1289       gtk_widget_queue_draw (GTK_WIDGET (body));
1290       update_editable (body);
1291       break;
1292     case PROP_CONVERT_REV_FUNC:
1293       priv->revf = g_value_get_pointer (value);
1294       break;
1295     case PROP_SELECTION:
1296       priv->selection = g_value_get_pointer (value);
1297       break;
1298     case PROP_DATA_MODEL:
1299       g_set_object (&priv->data_model, g_value_get_object (value));
1300       g_signal_connect_object (priv->data_model, "items-changed",
1301                                G_CALLBACK (on_data_change), body, 0);
1302       break;
1303     default:
1304       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1305       break;
1306     }
1307 }
1308 
1309 static void
__get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1310 __get_property (GObject * object,
1311                 guint prop_id, GValue * value, GParamSpec * pspec)
1312 {
1313   SswSheetBody *body = SSW_SHEET_BODY (object);
1314   PRIV_DECL (body);
1315 
1316   switch (prop_id)
1317     {
1318     case PROP_SHEET:
1319       g_value_set_object (value, priv->sheet);
1320       break;
1321     case PROP_VAXIS:
1322       g_value_set_object (value, priv->vaxis);
1323       break;
1324     case PROP_HAXIS:
1325       g_value_set_object (value, priv->haxis);
1326       break;
1327     case PROP_RENDERER_FUNC:
1328       g_value_set_pointer (value, priv->renderer_func);
1329       break;
1330     case PROP_CONVERT_FWD_FUNC:
1331       g_value_set_pointer (value, priv->cf);
1332       break;
1333     case PROP_CONVERT_REV_FUNC:
1334       g_value_set_pointer (value, priv->revf);
1335       break;
1336     default:
1337       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1338       break;
1339     }
1340 }
1341 
1342 static void
ssw_sheet_body_class_init(SswSheetBodyClass * class)1343 ssw_sheet_body_class_init (SswSheetBodyClass * class)
1344 {
1345   GObjectClass *object_class = G_OBJECT_CLASS (class);
1346   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
1347 
1348   GParamSpec *sheet_spec =
1349     g_param_spec_object ("sheet",
1350                          P_("Sheet"),
1351                          P_("The SswSheet to which this body belongs"),
1352                          SSW_TYPE_SHEET,
1353                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
1354 
1355   GParamSpec *haxis_spec =
1356     g_param_spec_object ("horizontal-axis",
1357                          P_("Horizontal Axis"),
1358                          P_("The Horizontal Axis"),
1359                          SSW_TYPE_SHEET_AXIS,
1360                          G_PARAM_READWRITE);
1361 
1362   GParamSpec *vaxis_spec =
1363     g_param_spec_object ("vertical-axis",
1364                          P_("Vertical Axis"),
1365                          P_("The Vertical Axis"),
1366                          SSW_TYPE_SHEET_AXIS,
1367                          G_PARAM_READWRITE);
1368 
1369   GParamSpec *data_model_spec =
1370     g_param_spec_object ("data-model",
1371                          P_("Data Model"),
1372                          P_("The model describing the contents of the data"),
1373                          GTK_TYPE_TREE_MODEL,
1374                          G_PARAM_READWRITE);
1375 
1376   GParamSpec *gridlines_spec =
1377     g_param_spec_boolean ("gridlines",
1378                           P_("Show Gridlines"),
1379                           P_("True if gridlines should be shown"),
1380                           TRUE,
1381                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
1382 
1383   GParamSpec *editable_spec =
1384     g_param_spec_boolean ("editable",
1385                           P_("Editable"),
1386                           P_("True if the data may be edited"),
1387                           TRUE,
1388                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
1389 
1390   GParamSpec *renderer_func_spec =
1391     g_param_spec_pointer ("select-renderer-func",
1392                           P_("Select Renderer Function"),
1393                           P_("Function returning the renderer to use for a cell"),
1394                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
1395 
1396   GParamSpec *convert_fwd_spec =
1397     g_param_spec_pointer ("forward-conversion",
1398                           P_("Forward conversion function"),
1399                           P_("A function to convert a cell datum to a string"),
1400                           G_PARAM_READWRITE);
1401 
1402   GParamSpec *convert_rev_spec =
1403     g_param_spec_pointer ("reverse-conversion",
1404                           P_("Reverse conversion function"),
1405                           P_("A function to convert a string to a cell datum"),
1406                           G_PARAM_READWRITE);
1407 
1408   GParamSpec *selection_spec =
1409     g_param_spec_pointer ("selection",
1410                           P_("The selection"),
1411                           P_("A pointer to the current selection"),
1412                           G_PARAM_READWRITE);
1413 
1414   object_class->set_property = __set_property;
1415   object_class->get_property = __get_property;
1416 
1417 
1418   g_object_class_install_property (object_class,
1419                                    PROP_SHEET,
1420                                    sheet_spec);
1421 
1422   g_object_class_install_property (object_class,
1423                                    PROP_VAXIS,
1424                                    vaxis_spec);
1425 
1426   g_object_class_install_property (object_class,
1427                                    PROP_HAXIS,
1428                                    haxis_spec);
1429 
1430   g_object_class_install_property (object_class,
1431                                    PROP_GRIDLINES,
1432                                    gridlines_spec);
1433 
1434   g_object_class_install_property (object_class,
1435                                    PROP_EDITABLE,
1436                                    editable_spec);
1437 
1438   g_object_class_install_property (object_class,
1439                                    PROP_DATA_MODEL,
1440                                    data_model_spec);
1441 
1442   g_object_class_install_property (object_class,
1443                                    PROP_RENDERER_FUNC,
1444                                    renderer_func_spec);
1445 
1446   g_object_class_install_property (object_class,
1447                                    PROP_CONVERT_FWD_FUNC,
1448                                    convert_fwd_spec);
1449 
1450   g_object_class_install_property (object_class,
1451                                    PROP_CONVERT_REV_FUNC,
1452                                    convert_rev_spec);
1453 
1454   g_object_class_install_property (object_class,
1455                                    PROP_SELECTION,
1456                                    selection_spec);
1457 
1458   object_class->dispose = __dispose;
1459   object_class->finalize = __finalize;
1460 
1461   widget_class->draw = __draw;
1462   widget_class->realize = __realize;
1463   widget_class->button_press_event = __button_press_event;
1464   widget_class->button_release_event = __button_release_event;
1465   widget_class->motion_notify_event = __motion_notify_event;
1466   widget_class->key_press_event = __key_press_event;
1467 
1468   signals [SELECTION_CHANGED] =
1469     g_signal_new ("selection-changed",
1470                   G_TYPE_FROM_CLASS (class),
1471                   G_SIGNAL_RUN_FIRST,
1472                   0,
1473                   NULL, NULL,
1474                   g_cclosure_marshal_VOID__POINTER,
1475                   G_TYPE_NONE,
1476                   1,
1477                   G_TYPE_POINTER);
1478 
1479 
1480   signals [VALUE_CHANGED] =
1481     g_signal_new ("value-changed",
1482                   G_TYPE_FROM_CLASS (class),
1483                   G_SIGNAL_RUN_FIRST,
1484                   0,
1485                   NULL, NULL,
1486                   ssw_cclosure_marshal_VOID__INT_INT_POINTER,
1487                   G_TYPE_NONE,
1488                   3,
1489                   G_TYPE_INT,
1490                   G_TYPE_INT,
1491                   G_TYPE_POINTER);
1492 }
1493 
1494 
1495 static void
selection_drag_begin(GtkGesture * gesture,gdouble start_x,gdouble start_y,gpointer user_data)1496 selection_drag_begin (GtkGesture *gesture,
1497                       gdouble         start_x,
1498                       gdouble         start_y,
1499                       gpointer        user_data)
1500 {
1501   SswSheetBody *body = SSW_SHEET_BODY (user_data);
1502   PRIV_DECL (body);
1503 
1504   GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (body));
1505 
1506   if (gdk_window_get_cursor (win) == priv->hc || gdk_window_get_cursor (win) == priv->vc)
1507     {
1508       gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
1509       return;
1510     }
1511 
1512   gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
1513 
1514   ssw_sheet_body_unset_selection (body);
1515 
1516   priv->selection->start_x = ssw_sheet_axis_find_cell (priv->haxis, start_x, NULL, NULL);
1517   priv->selection->start_y = ssw_sheet_axis_find_cell (priv->vaxis, start_y, NULL, NULL);
1518 
1519   priv->selection->end_x = priv->selection->start_x;
1520   priv->selection->end_y = priv->selection->start_y;
1521 }
1522 
1523 
1524 
1525 static void
selection_drag_update(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,gpointer user_data)1526 selection_drag_update (GtkGestureDrag *gesture,
1527                        gdouble         offset_x,
1528                        gdouble         offset_y,
1529                        gpointer        user_data)
1530 {
1531   SswSheetBody *body = SSW_SHEET_BODY (user_data);
1532   PRIV_DECL (body);
1533 
1534   gdouble start_x, start_y;
1535   gtk_gesture_drag_get_start_point (GTK_GESTURE_DRAG (gesture), &start_x, &start_y);
1536 
1537   gint xsize, ysize;
1538   gint xpos, ypos;
1539 
1540   gint x = ssw_sheet_axis_find_cell (priv->haxis, start_x + offset_x, &xpos, &xsize);
1541   gint y = ssw_sheet_axis_find_cell (priv->vaxis, start_y + offset_y, &ypos, &ysize);
1542 
1543   if (x < 0 || y < 0)
1544     return ;
1545 
1546   priv->selection->end_x = x - 1;
1547   priv->selection->end_y = y - 1;
1548 
1549   if (start_x + offset_x > (gdouble) xpos - (gdouble) xsize/2.0)
1550     priv->selection->end_x++;
1551 
1552   if (start_y + offset_y > (gdouble) ypos - (gdouble) ysize/2.0)
1553     priv->selection->end_y++;
1554 
1555   limit_selection (body);
1556 
1557 
1558   gtk_widget_queue_draw (GTK_WIDGET (body));
1559 }
1560 
1561 static void
selection_drag_end(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,gpointer user_data)1562 selection_drag_end (GtkGestureDrag *gesture,
1563                     gdouble         offset_x,
1564                     gdouble         offset_y,
1565                     gpointer        user_data)
1566 {
1567   SswSheetBody *body = SSW_SHEET_BODY (user_data);
1568 
1569   GdkEventSequence *seq =
1570     gtk_gesture_get_last_updated_sequence (GTK_GESTURE(gesture));
1571 
1572   GtkEventSequenceState state =
1573     gtk_gesture_get_sequence_state (GTK_GESTURE (gesture), seq);
1574 
1575   if (state == GTK_EVENT_SEQUENCE_DENIED)
1576     return;
1577 
1578   announce_selection (body);
1579 }
1580 
1581 
1582 
1583 /* The following targets are emitted by Libreoffice:
1584 
1585    text/plain;charset=utf-8
1586    text/plain;charset=UTF-8
1587    UTF-8
1588    UTF8_STRING
1589    COMPOUND_TEXT
1590    STRING
1591    application/x-openoffice-embed-source-xml;windows_formatname="Star Embed Source (XML)"
1592    application/x-openoffice-objectdescriptor-xml;windows_formatname="Star Object Descriptor (XML)";classname="47BBB4CB-CE4C-4E80-a591-42d9ae74950f";typename="calc8";viewaspect="1";width="4517";height="3162";posx="0";posy="0"
1593    application/x-openoffice-gdimetafile;windows_formatname="GDIMetaFile"
1594    application/x-openoffice-emf;windows_formatname="Image EMF"
1595    application/x-openoffice-wmf;windows_formatname="Image WMF"
1596    image/png
1597    application/x-openoffice-bitmap;windows_formatname="Bitmap"
1598    PIXMAP
1599    image/bmp
1600    text/html
1601    application/x-openoffice-sylk;windows_formatname="Sylk"
1602    application/x-openoffice-link;windows_formatname="Link"
1603    application/x-openoffice-dif;windows_formatname="DIF"
1604    text/richtext
1605    MULTIPLE
1606 
1607    And Gnumeric generates the following targets:
1608 
1609    TIMESTAMP
1610    TARGETS
1611    MULTIPLE
1612    SAVE_TARGETS
1613    application/x-gnumeric
1614    text/html
1615    UTF8_STRING
1616    COMPOUND_TEXT
1617    STRING
1618 
1619 */
1620 
1621 
1622 enum target_info
1623   {
1624    TARGET_TEXT_TSV,
1625    TARGET_TEXT_CSV,
1626    TARGET_HTML,
1627    TARGET_TEXT_PLAIN,
1628    TARGET_UTF8_STRING,
1629    TARGET_STRING,
1630    N_TARGETS
1631   };
1632 
1633 static const GtkTargetEntry targets[] =
1634   {
1635    {
1636     "text/tab-separated-values",
1637     GTK_TARGET_OTHER_APP,
1638     TARGET_TEXT_TSV
1639    },
1640    {
1641     "text/csv",
1642     GTK_TARGET_OTHER_APP,
1643     TARGET_TEXT_CSV
1644    },
1645    {
1646     "text/html",
1647     GTK_TARGET_OTHER_APP,
1648     TARGET_HTML
1649    },
1650    {
1651     "text/plain",
1652     GTK_TARGET_OTHER_APP,
1653     TARGET_TEXT_PLAIN
1654    },
1655    {
1656     "UTF8_STRING",
1657     GTK_TARGET_OTHER_APP,
1658     TARGET_UTF8_STRING
1659    },
1660    {
1661     "STRING",
1662     GTK_TARGET_OTHER_APP,
1663     TARGET_STRING
1664    },
1665   };
1666 
1667 
1668 /*
1669   Append a string representation of the value contained in MODEL,ITER,COL
1670   to the string OUTPUT.
1671 */
1672 static void
append_value_to_string(SswSheetBody * body,GtkTreeIter * iter,gint col,gint row,GString * output)1673 append_value_to_string (SswSheetBody *body,
1674                         GtkTreeIter *iter, gint col, gint row,
1675                         GString *output)
1676 {
1677   PRIV_DECL (body);
1678   GtkTreeModel *model = priv->data_model;
1679   GValue value = G_VALUE_INIT;
1680   GValue target_value = G_VALUE_INIT;
1681   g_value_init (&target_value, G_TYPE_STRING);
1682   gtk_tree_model_get_value (model, iter, col, &value);
1683 
1684   if (priv->cf)
1685     {
1686       gchar *x = priv->cf (priv->sheet, model, col, row, &value);
1687       g_string_append (output, x);
1688       g_free (x);
1689     }
1690   else if (g_value_transform (&value, &target_value))
1691     {
1692       g_string_append (output, g_value_get_string (&target_value));
1693     }
1694   else
1695     {
1696       g_warning ("Pasting from SswSheet failed.  "
1697                  "You must register a transform function for source "
1698                  "type \"%s\" to dest type \"%s\"\n",
1699                  G_VALUE_TYPE_NAME (&value),
1700                  g_type_name (G_TYPE_STRING));
1701     }
1702 
1703   g_value_unset (&value);
1704   g_value_unset (&target_value);
1705 }
1706 
1707 
1708 
1709 static void
clipit_html(SswSheetBody * body,GString * output,SswRange * source_range)1710 clipit_html (SswSheetBody *body, GString *output, SswRange *source_range)
1711 {
1712   PRIV_DECL (body);
1713   if (!priv->haxis || !priv->vaxis || !priv->data_model)
1714     return;
1715 
1716   g_string_append (output, "<body>\n");
1717   g_string_append (output, "<table>\n");
1718 
1719   gint row;
1720   gint col;
1721   for (row = source_range->start_y ; row <= source_range->end_y; ++row)
1722     {
1723       GtkTreeIter iter;
1724       gtk_tree_model_iter_nth_child (priv->data_model, &iter, NULL, row);
1725       g_string_append (output, "<tr>\n");
1726       for (col = source_range->start_x; col <= source_range->end_x; ++col)
1727         {
1728           if (row < gtk_tree_model_iter_n_children (priv->data_model, NULL)
1729               && col < gtk_tree_model_get_n_columns (priv->data_model))
1730             {
1731               g_string_append (output, "<td>");
1732               append_value_to_string (body, &iter, col, row, output);
1733               g_string_append (output, "</td>\n");
1734             }
1735         }
1736       g_string_append (output, "</tr>\n");
1737     }
1738 
1739   g_string_append (output, "</table>\n");
1740   g_string_append (output, "</body>\n");
1741 }
1742 
1743 
1744 static void
clipit_ascii(SswSheetBody * body,GString * output,SswRange * source_range)1745 clipit_ascii (SswSheetBody *body, GString *output, SswRange *source_range)
1746 {
1747   PRIV_DECL (body);
1748   if (!priv->haxis || !priv->vaxis || !priv->data_model)
1749     return;
1750 
1751   gint row;
1752   gint col;
1753   for (row = source_range->start_y ; row <= source_range->end_y; ++row)
1754     {
1755       GtkTreeIter iter;
1756       gtk_tree_model_iter_nth_child (priv->data_model, &iter, NULL, row);
1757 
1758       for (col = source_range->start_x; col <= source_range->end_x; ++col)
1759         {
1760           if (priv->data_model
1761               && row < gtk_tree_model_iter_n_children (priv->data_model, NULL)
1762               && col < gtk_tree_model_get_n_columns (priv->data_model))
1763             {
1764               append_value_to_string (body, &iter, col, row, output);
1765 
1766               if (col < source_range->end_x)
1767                 g_string_append (output, "\t");
1768             }
1769         }
1770       if (row < source_range->end_y)
1771         g_string_append (output, "\n");
1772     }
1773 }
1774 
1775 static void
clipit_utf8(SswSheetBody * body,GString * output,SswRange * source_range)1776 clipit_utf8 (SswSheetBody *body, GString *output, SswRange *source_range)
1777 {
1778   PRIV_DECL (body);
1779   if (!priv->haxis || !priv->vaxis || !priv->data_model)
1780     return;
1781 
1782   gint row;
1783   gint col;
1784   for (row = source_range->start_y ; row <= source_range->end_y; ++row)
1785     {
1786       GtkTreeIter iter;
1787       gtk_tree_model_iter_nth_child (priv->data_model, &iter, NULL, row);
1788 
1789       for (col = source_range->start_x; col <= source_range->end_x; ++col)
1790         {
1791           if (row < gtk_tree_model_iter_n_children (priv->data_model, NULL)
1792               && col < gtk_tree_model_get_n_columns (priv->data_model))
1793             {
1794               append_value_to_string (body, &iter, col, row, output);
1795 
1796               if (col < source_range->end_x)
1797                 g_string_append_c (output, '\t');
1798             }
1799         }
1800       if (row < source_range->end_y)
1801         g_string_append (output, "\n");
1802     }
1803 }
1804 
1805 
1806 static void
get_func(GtkClipboard * clipboard,GtkSelectionData * selection_data,guint info,gpointer owner)1807 get_func (GtkClipboard *clipboard,
1808           GtkSelectionData *selection_data,
1809           guint info,
1810           gpointer owner)
1811 {
1812   SswSheetBody *body = SSW_SHEET_BODY (owner);
1813 
1814   if (body == NULL)
1815     {
1816       gtk_clipboard_clear (clipboard);
1817       return;
1818     }
1819 
1820   SswRange *source_range = g_object_get_data (G_OBJECT (clipboard), "source-range");
1821   g_return_if_fail (source_range);
1822 
1823   GString *stuff = g_string_new ("");
1824   switch (info)
1825     {
1826     case TARGET_STRING:
1827       clipit_ascii (body, stuff, source_range);
1828       break;
1829     case TARGET_UTF8_STRING:
1830       clipit_utf8 (body, stuff, source_range);
1831       break;
1832     case TARGET_HTML:
1833       clipit_html (body, stuff, source_range);
1834       break;
1835     default:
1836       g_warning ("Request for unknown target %d\n", info);
1837       return ;
1838       break;
1839     }
1840 
1841   gtk_selection_data_set (selection_data,
1842                           gdk_atom_intern_static_string (targets[info].target),
1843                           CHAR_BIT,
1844                           (gpointer) stuff->str, stuff->len);
1845 
1846   g_string_free (stuff, TRUE);
1847 }
1848 
1849 
1850 static void
clear_func(GtkClipboard * clipboard,gpointer user_data_or_owner)1851 clear_func (GtkClipboard *clipboard,
1852             gpointer user_data_or_owner)
1853 {
1854 }
1855 
1856 void
ssw_sheet_body_set_clip(SswSheetBody * body,GtkClipboard * clip)1857 ssw_sheet_body_set_clip (SswSheetBody *body, GtkClipboard *clip)
1858 {
1859   PRIV_DECL (body);
1860   if (body == NULL)
1861     return;
1862 
1863   if (priv->editor &&
1864       GTK_IS_EDITABLE (priv->editor) &&
1865       gtk_widget_is_focus (priv->editor))
1866     {
1867       gtk_editable_copy_clipboard (GTK_EDITABLE(priv->editor));
1868       return;
1869     }
1870 
1871   SswRange *source_range = g_object_get_data (G_OBJECT (clip), "source-range");
1872   g_free (source_range);
1873   source_range = g_malloc (sizeof (*source_range));
1874   g_object_set_data (G_OBJECT (clip), "source-range", source_range);
1875   normalise_selection (priv->selection, source_range);
1876 
1877   if (!gtk_clipboard_set_with_owner (clip, targets, N_TARGETS,
1878                                      get_func, clear_func, G_OBJECT (body)))
1879     {
1880       g_warning ("Clip failed\n");
1881     }
1882 }
1883 
1884 static void
drag_begin_resize_horizontal(GtkGesture * gesture,gdouble start_x,gdouble start_y,gpointer user_data)1885 drag_begin_resize_horizontal (GtkGesture *gesture,
1886                               gdouble         start_x,
1887                               gdouble         start_y,
1888                               gpointer        user_data)
1889 {
1890   SswSheetBody *body = SSW_SHEET_BODY (user_data);
1891   PRIV_DECL (body);
1892   GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (body));
1893 
1894   if (gdk_window_get_cursor (win) == priv->hc)
1895     {
1896       gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
1897     }
1898   else
1899     {
1900       gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
1901     }
1902 }
1903 
1904 static void
drag_begin_resize_vertical(GtkGesture * gesture,gdouble start_x,gdouble start_y,gpointer user_data)1905 drag_begin_resize_vertical (GtkGesture *gesture,
1906                             gdouble         start_x,
1907                             gdouble         start_y,
1908                             gpointer        user_data)
1909 {
1910   SswSheetBody *body = SSW_SHEET_BODY (user_data);
1911   PRIV_DECL (body);
1912   GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (body));
1913 
1914   if (gdk_window_get_cursor (win) == priv->vc)
1915     {
1916       gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
1917     }
1918   else
1919     {
1920       gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
1921     }
1922 }
1923 
1924 static void
drag_update_resize(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,gpointer user_data)1925 drag_update_resize (GtkGestureDrag *gesture,
1926                     gdouble         offset_x,
1927                     gdouble         offset_y,
1928                     gpointer        user_data)
1929 {
1930   SswSheetBody *body = SSW_SHEET_BODY (user_data);
1931 
1932   gtk_widget_queue_draw (GTK_WIDGET (body));
1933 }
1934 
1935 
1936 
1937 static void
drag_end_resize_horizontal(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,gpointer user_data)1938 drag_end_resize_horizontal (GtkGestureDrag *gesture,
1939                             gdouble         offset_x,
1940                             gdouble         offset_y,
1941                             gpointer        user_data)
1942 {
1943   gdouble start_x, start_y;
1944   SswSheetBody *body = SSW_SHEET_BODY (user_data);
1945   PRIV_DECL (body);
1946   gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
1947 
1948   GdkEventSequence *seq =
1949     gtk_gesture_get_last_updated_sequence (GTK_GESTURE(gesture));
1950 
1951   GtkEventSequenceState state =
1952     gtk_gesture_get_sequence_state (GTK_GESTURE (gesture), seq);
1953 
1954   if (state == GTK_EVENT_SEQUENCE_DENIED)
1955     return;
1956 
1957   gint nearest = -1;
1958   gint pos, size;
1959   gint item = ssw_sheet_axis_find_cell (priv->haxis, start_x, &pos, &size);
1960   if (ssw_sheet_axis_rtl (priv->haxis))
1961     {
1962       nearest =
1963         (start_x - pos) < (pos + size - start_x) ? item: item - 1 ;
1964     }
1965   else
1966     {
1967       nearest =
1968         (start_x - pos) < (pos + size - start_x) ? item - 1: item ;
1969     }
1970 
1971   gint old_size;
1972   ssw_sheet_axis_find_boundary (priv->haxis, nearest, NULL, &old_size);
1973   const gint new_size = ssw_sheet_axis_rtl (priv->haxis) ?
1974     old_size - offset_x : old_size + offset_x;
1975 
1976   gint row = -1, col = -1;
1977   get_active_cell (body, &col, &row);
1978 
1979   if (nearest == col)
1980     {
1981       gint vsize;
1982 
1983       ssw_sheet_axis_find_boundary (priv->vaxis, row, NULL, &vsize);
1984 
1985 
1986       gtk_widget_set_size_request (GTK_WIDGET (priv->editor),
1987                                    new_size - linewidth, vsize);
1988 
1989       g_object_set (priv->active_cell_holder,
1990                     "hconstraint", new_size - linewidth,
1991                     NULL);
1992     }
1993 
1994   ssw_sheet_axis_override_size (priv->haxis, nearest, new_size);
1995 }
1996 
1997 
1998 static void
drag_end_resize_vertical(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,gpointer user_data)1999 drag_end_resize_vertical (GtkGestureDrag *gesture,
2000                           gdouble         offset_x,
2001                           gdouble         offset_y,
2002                           gpointer        user_data)
2003 {
2004   gdouble start_x, start_y;
2005   SswSheetBody *body = SSW_SHEET_BODY (user_data);
2006   PRIV_DECL (body);
2007   gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
2008 
2009   GdkEventSequence *seq =
2010     gtk_gesture_get_last_updated_sequence (GTK_GESTURE(gesture));
2011 
2012   GtkEventSequenceState state =
2013     gtk_gesture_get_sequence_state (GTK_GESTURE (gesture), seq);
2014 
2015   if (state == GTK_EVENT_SEQUENCE_DENIED)
2016     return;
2017 
2018   gint pos, size;
2019   gint item = ssw_sheet_axis_find_cell (priv->vaxis, start_y, &pos, &size);
2020   gint nearest = (start_y - pos) < (pos + size - start_y) ? item - 1: item ;
2021 
2022 
2023   gint old_size;
2024   ssw_sheet_axis_find_boundary (priv->vaxis, nearest, NULL, &old_size);
2025   const gint new_size = old_size + offset_y;
2026 
2027   gint row = -1, col = -1;
2028   get_active_cell (body, &col, &row);
2029 
2030   if (nearest == row)
2031     {
2032       gint hsize;
2033       ssw_sheet_axis_find_boundary (priv->haxis, nearest, &hsize, NULL);
2034 
2035       gtk_widget_set_size_request (GTK_WIDGET (priv->editor),
2036                                    hsize,
2037                                    new_size - linewidth);
2038 
2039       g_object_set (priv->active_cell_holder,
2040                     "vconstraint", new_size - linewidth,
2041                     NULL);
2042     }
2043 
2044   ssw_sheet_axis_override_size (priv->vaxis, nearest, new_size);
2045 }
2046 
2047 static void
ssw_sheet_body_init(SswSheetBody * body)2048 ssw_sheet_body_init (SswSheetBody *body)
2049 {
2050   PRIV_DECL (body);
2051 
2052   /* This is derived from GtkLayout.  So it has its own window.  */
2053   gtk_widget_set_has_window (GTK_WIDGET (body), TRUE);
2054   GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (body));
2055   GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (body));
2056 
2057   gtk_style_context_add_class (context, "cell");
2058 
2059   priv->sheet = NULL;
2060   priv->path[0] = '\0';
2061   priv->dispose_has_run = FALSE;
2062 
2063   priv->default_renderer = gtk_cell_renderer_text_new ();
2064 
2065   priv->vc = gdk_cursor_new_for_display (display, GDK_SB_V_DOUBLE_ARROW);
2066   priv->hc = gdk_cursor_new_for_display (display, GDK_SB_H_DOUBLE_ARROW);
2067   priv->xc = gdk_cursor_new_for_display (display, GDK_SIZING);
2068 
2069   priv->selection_gesture = gtk_gesture_drag_new (GTK_WIDGET (body));
2070 
2071 
2072   g_signal_connect (priv->selection_gesture, "drag-begin",
2073                     G_CALLBACK (selection_drag_begin), body);
2074 
2075   g_signal_connect (priv->selection_gesture, "drag-update",
2076                     G_CALLBACK (selection_drag_update), body);
2077 
2078   g_signal_connect (priv->selection_gesture, "drag-end",
2079                     G_CALLBACK (selection_drag_end), body);
2080 
2081   priv->horizontal_resize_gesture =
2082     gtk_gesture_drag_new (GTK_WIDGET (body));
2083 
2084   g_signal_connect (priv->horizontal_resize_gesture, "drag-begin",
2085                     G_CALLBACK (drag_begin_resize_horizontal), body);
2086 
2087   g_signal_connect (priv->horizontal_resize_gesture, "drag-update",
2088                     G_CALLBACK (drag_update_resize), body);
2089 
2090   g_signal_connect (priv->horizontal_resize_gesture, "drag-end",
2091                     G_CALLBACK (drag_end_resize_horizontal), body);
2092 
2093   priv->vertical_resize_gesture =
2094     gtk_gesture_drag_new (GTK_WIDGET (body));
2095 
2096   g_signal_connect (priv->vertical_resize_gesture, "drag-begin",
2097                     G_CALLBACK (drag_begin_resize_vertical), body);
2098 
2099   g_signal_connect (priv->vertical_resize_gesture, "drag-update",
2100                     G_CALLBACK (drag_update_resize), body);
2101 
2102   g_signal_connect (priv->vertical_resize_gesture, "drag-end",
2103                     G_CALLBACK (drag_end_resize_vertical), body);
2104 
2105 
2106   priv->data_model = NULL;
2107   priv->editor = NULL;
2108   priv->cf = ssw_sheet_default_forward_conversion;
2109   priv->revf = ssw_sheet_default_reverse_conversion;
2110 
2111   priv->active_cell_holder = ssw_constraint_new ();
2112   /* Keep the constraint widget well out of the visible range */
2113   gtk_layout_put (GTK_LAYOUT (body), priv->active_cell_holder,  -99, -99);
2114 }
2115 
2116 void
ssw_sheet_body_unset_selection(SswSheetBody * body)2117 ssw_sheet_body_unset_selection (SswSheetBody *body)
2118 {
2119   PRIV_DECL (body);
2120   priv->selection->start_x = priv->selection->start_y = -1;
2121   priv->selection->end_x = priv->selection->end_y = -1;
2122 
2123   gtk_widget_queue_draw (GTK_WIDGET (body));
2124 }
2125 
2126 
2127 void
ssw_sheet_body_value_to_string(SswSheetBody * body,gint col,gint row,GString * output)2128 ssw_sheet_body_value_to_string (SswSheetBody *body, gint col, gint row,
2129                                 GString *output)
2130 {
2131   GtkTreeIter iter;
2132   PRIV_DECL (body);
2133 
2134   gtk_tree_model_iter_nth_child (priv->data_model, &iter, NULL, row);
2135 
2136   append_value_to_string (body, &iter, col, row, output);
2137 }
2138 
2139 
2140 
2141 
2142 static void text_editing_started (GtkCellRenderer *cell,
2143                                   GtkCellEditable *editable,
2144                                   const gchar     *path,
2145                                   gpointer         data);
2146 
2147 
2148 static GtkCellRenderer *
choose_renderer(SswSheetBody * body,gint col,gint row)2149 choose_renderer (SswSheetBody *body, gint col, gint row)
2150 {
2151   PRIV_DECL (body);
2152 
2153   GtkCellRenderer *r = NULL;
2154 
2155   if (priv->renderer_func)
2156     {
2157       GType t = gtk_tree_model_get_column_type (priv->data_model, col);
2158       r = priv->renderer_func (priv->sheet, col, row, t,
2159                                priv->sheet->renderer_func_datum);
2160     }
2161 
2162   if (r == NULL)
2163     r = priv->default_renderer;
2164 
2165   g_object_set (r,
2166                 "mode", GTK_CELL_RENDERER_MODE_EDITABLE,
2167                 "editable", TRUE,
2168                 NULL);
2169 
2170   if (!g_object_get_data (G_OBJECT (r), "ess"))
2171     {
2172       g_signal_connect (r,
2173                         "editing-started",
2174                         G_CALLBACK (text_editing_started),
2175                         NULL);
2176 
2177       g_object_set_data (G_OBJECT (r), "ess", GINT_TO_POINTER (TRUE));
2178     }
2179 
2180   return r;
2181 }
2182 
2183 static void
on_editing_done(GtkCellEditable * e,SswSheetBody * body)2184 on_editing_done (GtkCellEditable *e, SswSheetBody *body)
2185 {
2186   PRIV_DECL (body);
2187 
2188   gint row = -1, col = -1;
2189   get_active_cell (body, &col, &row);
2190 
2191   gboolean cell_value_unchanged = -1;
2192   g_object_get (e, "editing-canceled", &cell_value_unchanged, NULL);
2193 
2194   /* Grab the focus back from the CellEditable */
2195   gtk_widget_grab_focus (GTK_WIDGET (body));
2196 
2197   if (cell_value_unchanged)
2198     return;
2199 
2200   GValue value = G_VALUE_INIT;
2201   if (GTK_IS_SPIN_BUTTON (e))
2202     {
2203       GType t = gtk_tree_model_get_column_type (priv->data_model, col);
2204       g_value_init (&value, t);
2205 
2206       gdouble v = gtk_spin_button_get_value (GTK_SPIN_BUTTON (e));
2207       GValue dbl_value = G_VALUE_INIT;
2208       g_value_init (&dbl_value, G_TYPE_DOUBLE);
2209       g_value_set_double (&dbl_value, v);
2210       g_value_transform (&dbl_value, &value);
2211       g_value_unset (&dbl_value);
2212     }
2213   else if (GTK_IS_ENTRY (e))
2214     {
2215       const char *s = gtk_entry_get_text (GTK_ENTRY (e));
2216       priv->revf (priv->data_model, col, row, s, &value);
2217     }
2218   else if (GTK_IS_COMBO_BOX (e))
2219     {
2220       g_value_init (&value, G_TYPE_INT);
2221       gint n = gtk_combo_box_get_active (GTK_COMBO_BOX (e));
2222       g_value_set_int (&value, n);
2223     }
2224 
2225   g_signal_emit (body, signals[VALUE_CHANGED], 0, col, row, &value);
2226 
2227   g_value_unset (&value);
2228 }
2229 
2230 static void
on_entry_activate(GtkEntry * e,gpointer ud)2231 on_entry_activate (GtkEntry *e, gpointer ud)
2232 {
2233   SswSheetBody *body = SSW_SHEET_BODY (ud);
2234   GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (body));
2235   g_return_if_fail (GDK_IS_WINDOW (win));
2236   GdkEvent *ev = gdk_event_new (GDK_KEY_PRESS);
2237   GdkEventKey *event = (GdkEventKey *) ev;
2238   event->window = g_object_ref(win);
2239   event->keyval = GDK_KEY_Return;
2240   event->send_event = TRUE;
2241   gboolean ret;
2242   g_signal_emit_by_name (body, "key-press-event", event, &ret);
2243   gdk_event_free (ev);
2244 }
2245 
2246 static void
done_editing(GtkCellRendererCombo * combo,gchar * path_string,GtkTreeIter * new_iter,gpointer user_data)2247 done_editing (GtkCellRendererCombo *combo,
2248               gchar                *path_string,
2249               GtkTreeIter          *new_iter,
2250               gpointer              user_data)
2251 {
2252   GtkCellEditable *ce = user_data;
2253   g_object_set (ce, "editing-canceled", FALSE, NULL);
2254   gtk_cell_editable_editing_done (ce);
2255 }
2256 
2257 
2258 static void
on_remove_widget(GtkCellEditable * e,gpointer ud)2259 on_remove_widget (GtkCellEditable *e, gpointer ud)
2260 {
2261   SswSheetBody *body = ud;
2262   PRIV_DECL (body);
2263 
2264   gtk_widget_destroy (GTK_WIDGET (e));
2265   priv->editor = NULL;
2266   gtk_widget_hide (priv->active_cell_holder);
2267 }
2268 
2269 static void
on_changed(GtkEditable * editable,gpointer user_data)2270 on_changed (GtkEditable *editable, gpointer user_data)
2271 {
2272   g_object_set (editable, "editing-canceled", FALSE, NULL);
2273 }
2274 
2275 static gboolean
on_focus_out(GtkWidget * w,GdkEvent * e,gpointer ud)2276 on_focus_out (GtkWidget *w, GdkEvent *e, gpointer ud)
2277 {
2278   /* It seems that GtkEntry by default emits the "widget-remove" signal,
2279      in response to focus-out.  We don't want that to happen, so disable
2280      it here. */
2281   return TRUE;
2282 }
2283 
2284 static void
text_editing_started(GtkCellRenderer * cell,GtkCellEditable * editable,const gchar * path,gpointer data)2285 text_editing_started (GtkCellRenderer *cell,
2286                       GtkCellEditable *editable,
2287                       const gchar     *path,
2288                       gpointer         data)
2289 {
2290   SswSheetBody *body = NULL;
2291   gint row = -1, col = -1;
2292   sscanf (path, "r%*dc%*ds%p", &body);
2293 
2294   if (body == NULL) /* There is no active cell! */
2295     return;
2296 
2297   get_active_cell (body, &col, &row);
2298   PRIV_DECL (body);
2299 
2300   if (col < 0 || row < 0)
2301     {
2302       gtk_widget_destroy (GTK_WIDGET (editable));
2303       return;
2304     }
2305 
2306   if (! gtk_widget_is_visible (GTK_WIDGET (body)))
2307     return;
2308 
2309   if (priv->editor)
2310     {
2311       gtk_cell_renderer_stop_editing (GTK_CELL_RENDERER (cell),
2312                                       TRUE);
2313 
2314       g_signal_emit_by_name (priv->editor, "remove-widget");
2315     }
2316 
2317 #if DEBUGGING
2318   GdkRGBA color = {1.0, 0, 0, 1};
2319   gtk_widget_override_background_color (GTK_WIDGET (editable),
2320                                         GTK_STATE_NORMAL,
2321                                         &color);
2322 #endif
2323 
2324   priv->editor = NULL;
2325 
2326   gint vlocation, vsize;
2327   gint hlocation, hsize;
2328   if (0 != ssw_sheet_axis_find_boundary (priv->vaxis, row, &vlocation, &vsize))
2329     return;
2330 
2331   if (0 != ssw_sheet_axis_find_boundary (priv->haxis, col, &hlocation, &hsize))
2332     return;
2333 
2334   priv->editor = GTK_WIDGET (editable);
2335 
2336 #if GTK_CHECK_VERSION (3, 22, 20)
2337   /* Don't show the stupid "Insert Emoji" in the popup menu.  */
2338   if (GTK_IS_ENTRY (priv->editor))
2339     g_object_set (priv->editor,
2340                   "input-hints", GTK_INPUT_HINT_NO_EMOJI,
2341                   NULL);
2342 #endif
2343 
2344   g_signal_connect (editable, "remove-widget",
2345                     G_CALLBACK (on_remove_widget), body);
2346 
2347 
2348   /* Avoid the gridlines */
2349   hsize -= linewidth;
2350   hlocation += linewidth;
2351   vsize -= linewidth;
2352   vlocation += linewidth;
2353 
2354   gtk_widget_set_size_request (GTK_WIDGET (editable),  hsize, vsize);
2355 
2356   g_object_set (priv->active_cell_holder,
2357                 "hconstraint", hsize,
2358                 "vconstraint", vsize,
2359                 NULL);
2360 
2361   gtk_container_add (GTK_CONTAINER (priv->active_cell_holder),
2362                      GTK_WIDGET (editable));
2363 
2364   gtk_layout_move (GTK_LAYOUT (body), priv->active_cell_holder,
2365                    hlocation, vlocation);
2366 
2367   GtkTreeIter iter;
2368   GValue value = G_VALUE_INIT;
2369   if (gtk_tree_model_iter_nth_child (priv->data_model,
2370                                      &iter, NULL, row))
2371     {
2372       gtk_tree_model_get_value (priv->data_model, &iter,
2373                                 col, &value);
2374     }
2375 
2376   if (GTK_IS_ENTRY (editable))
2377     {
2378       g_signal_connect (editable, "focus-out-event", G_CALLBACK (on_focus_out), NULL);
2379       gtk_entry_set_text (GTK_ENTRY (editable), "");
2380       g_signal_connect (editable, "changed", G_CALLBACK (on_changed), NULL);
2381     }
2382   else if (GTK_IS_COMBO_BOX (editable))
2383     {
2384       gtk_combo_box_set_active (GTK_COMBO_BOX (editable), 0);
2385     }
2386 
2387   g_signal_connect (editable, "editing-done",
2388                     G_CALLBACK (on_editing_done), body);
2389 
2390 
2391   set_editor_widget_value (body, &value, GTK_EDITABLE (editable));
2392   g_value_unset (&value);
2393 
2394   if (GTK_IS_SPIN_BUTTON (editable))
2395     {
2396       g_signal_connect (editable, "value-changed", G_CALLBACK (finish_editing), NULL);
2397     }
2398   else if (GTK_IS_ENTRY (editable))
2399     {
2400       /* "activate" means when the Enter key is pressed */
2401       g_signal_connect (editable, "activate", G_CALLBACK (on_entry_activate), body);
2402     }
2403   else if (GTK_IS_COMBO_BOX (editable))
2404     {
2405       g_signal_connect_object (cell, "changed", G_CALLBACK (done_editing),
2406                                editable, 0);
2407     }
2408 
2409   gtk_widget_show_all (priv->active_cell_holder);
2410 }
2411 
2412 gboolean
ssw_sheet_body_paste_editable(SswSheetBody * body)2413 ssw_sheet_body_paste_editable (SswSheetBody *body)
2414 {
2415   PRIV_DECL (body);
2416   if (body &&
2417       priv->editor &&
2418       GTK_IS_EDITABLE (priv->editor) &&
2419       gtk_widget_is_focus (priv->editor))
2420     {
2421       gtk_editable_paste_clipboard (GTK_EDITABLE (priv->editor));
2422       return TRUE;
2423     }
2424   return FALSE;
2425 }
2426 
2427 gboolean
ssw_sheet_body_cut_editable(SswSheetBody * body)2428 ssw_sheet_body_cut_editable (SswSheetBody *body)
2429 {
2430   PRIV_DECL (body);
2431   if (body &&
2432       priv->editor &&
2433       GTK_IS_ENTRY (priv->editor) &&
2434       gtk_widget_is_focus (priv->editor))
2435     {
2436       gtk_editable_cut_clipboard ( GTK_EDITABLE (priv->editor));
2437       return TRUE;
2438     }
2439   return FALSE;
2440 }
2441