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 "ssw-sheet.h"
21 
22 #include "ssw-sheet-single.h"
23 #include "ssw-sheet-axis.h"
24 #include "ssw-axis-model.h"
25 #include "ssw-sheet-body.h"
26 #include "ssw-marshaller.h"
27 #include "ssw-xpaned.h"
28 #include "ssw-paste.h"
29 
30 #define P_(X) (X)
31 
32 G_DEFINE_TYPE (SswSheet, ssw_sheet, GTK_TYPE_BIN)
33 
34 #define DIM 2
35 
36   enum  {ROW_HEADER_CLICKED,
37          ROW_HEADER_DOUBLE_CLICKED,
38          COLUMN_HEADER_CLICKED,
39          COLUMN_HEADER_DOUBLE_CLICKED,
40          ROW_HEADER_PRESSED,
41          ROW_HEADER_RELEASED,
42          COLUMN_HEADER_PRESSED,
43          COLUMN_HEADER_RELEASED,
44          SELECTION_CHANGED,
45          VALUE_CHANGED,
46          ROW_MOVED,
47          COLUMN_MOVED,
48          n_SIGNALS};
49 
50 static guint signals [n_SIGNALS];
51 
52 GtkWidget *
ssw_sheet_new(void)53 ssw_sheet_new (void)
54 {
55   GObject *object = g_object_new (SSW_TYPE_SHEET, NULL);
56 
57   return GTK_WIDGET (object);
58 }
59 
60 GtkWidget *
ssw_sheet_get_button(SswSheet * s)61 ssw_sheet_get_button (SswSheet *s)
62 {
63   return SSW_SHEET_SINGLE (s->sheet[0])->button;
64 }
65 
66 static void
__realize(GtkWidget * w)67 __realize (GtkWidget *w)
68 {
69   SswSheet *sheet = SSW_SHEET (w);
70   GTK_WIDGET_CLASS (ssw_sheet_parent_class)->realize (w);
71   GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (w));
72   GdkDisplay *display = gdk_window_get_display (win);
73   sheet->wait_cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
74 }
75 
76 static void
__unrealize(GtkWidget * w)77 __unrealize (GtkWidget *w)
78 {
79   SswSheet *sheet = SSW_SHEET (w);
80   g_object_unref (sheet->wait_cursor);
81   GTK_WIDGET_CLASS (ssw_sheet_parent_class)->unrealize (w);
82 }
83 
84 enum
85   {
86    PROP_0,
87    PROP_SELECTION,
88    PROP_SPLITTER,
89    PROP_VMODEL,
90    PROP_HMODEL,
91    PROP_DATA_MODEL,
92    PROP_SPLIT,
93    PROP_GRIDLINES,
94    PROP_EDITABLE,
95    PROP_HORIZONTAL_DRAGGABLE,
96    PROP_VERTICAL_DRAGGABLE,
97    PROP_RENDERER_FUNC,
98    PROP_RENDERER_FUNC_DATUM,
99    PROP_CONVERT_FWD_FUNC,
100    PROP_CONVERT_REV_FUNC
101   };
102 
103 static void
on_header_button_pressed(SswSheetAxis * axis,gint id,guint button,guint state,gpointer user_data)104 on_header_button_pressed (SswSheetAxis *axis, gint id, guint button, guint state,
105                           gpointer user_data)
106 {
107   SswSheet *sheet = SSW_SHEET (user_data);
108 
109   GtkOrientation o = gtk_orientable_get_orientation (GTK_ORIENTABLE (axis));
110 
111   if (o == GTK_ORIENTATION_HORIZONTAL)
112     g_signal_emit (sheet, signals [COLUMN_HEADER_PRESSED], 0, id, button, state);
113   else
114     g_signal_emit (sheet, signals [ROW_HEADER_PRESSED], 0, id, button, state);
115 }
116 
117 
118 static void
on_header_button_released(SswSheetAxis * axis,gint id,guint button,guint state,gpointer user_data)119 on_header_button_released (SswSheetAxis *axis, gint id, guint button, guint state,
120                            gpointer user_data)
121 {
122   SswSheet *sheet = SSW_SHEET (user_data);
123 
124   GtkOrientation o = gtk_orientable_get_orientation (GTK_ORIENTABLE (axis));
125 
126   if (o == GTK_ORIENTATION_HORIZONTAL)
127     g_signal_emit (sheet, signals [COLUMN_HEADER_RELEASED], 0, id, button, state);
128   else
129     g_signal_emit (sheet, signals [ROW_HEADER_RELEASED], 0, id, button, state);
130 }
131 
132 
133 static void
on_header_clicked(SswSheetAxis * axis,gint which,guint state,gpointer user_data)134 on_header_clicked (SswSheetAxis *axis, gint which, guint state, gpointer user_data)
135 {
136   SswSheet *sheet = SSW_SHEET (user_data);
137 
138   GtkOrientation o = gtk_orientable_get_orientation (GTK_ORIENTABLE (axis));
139 
140   if (o == GTK_ORIENTATION_HORIZONTAL)
141     g_signal_emit (sheet, signals [COLUMN_HEADER_CLICKED], 0, which);
142   else
143     g_signal_emit (sheet, signals [ROW_HEADER_CLICKED], 0, which);
144 }
145 
146 static void
on_header_double_clicked(SswSheetAxis * axis,gint which,guint state,gpointer user_data)147 on_header_double_clicked (SswSheetAxis *axis, gint which, guint state, gpointer user_data)
148 {
149   SswSheet *sheet = SSW_SHEET (user_data);
150 
151   GtkOrientation o = gtk_orientable_get_orientation (GTK_ORIENTABLE (axis));
152 
153   if (o == GTK_ORIENTATION_HORIZONTAL)
154     g_signal_emit (sheet, signals [COLUMN_HEADER_DOUBLE_CLICKED], 0, which);
155   else
156     g_signal_emit (sheet, signals [ROW_HEADER_DOUBLE_CLICKED], 0, which);
157 }
158 
159 
160 static void
arrange(SswSheet * sheet)161 arrange (SswSheet *sheet)
162 {
163   gint i;
164 
165   for (i = 0; i < DIM; ++i)
166     {
167       if (sheet->vmodel)
168         ssw_sheet_axis_set_model (SSW_SHEET_AXIS (sheet->vertical_axis[i]),
169                                   sheet->vmodel);
170 
171       if (sheet->hmodel)
172         ssw_sheet_axis_set_model (SSW_SHEET_AXIS (sheet->horizontal_axis[i]),
173                                   sheet->hmodel);
174     }
175 }
176 
177 static void
__finalize(GObject * obj)178 __finalize (GObject *obj)
179 {
180   SswSheet *sheet = SSW_SHEET (obj);
181 
182   g_free (sheet->selection);
183 
184   G_OBJECT_CLASS (ssw_sheet_parent_class)->finalize (obj);
185 }
186 
187 static void
__dispose(GObject * object)188 __dispose (GObject *object)
189 {
190   SswSheet *sheet = SSW_SHEET (object);
191 
192   if (sheet->dispose_has_run)
193     return;
194 
195   if (sheet->vmodel)
196     g_object_unref (sheet->vmodel);
197 
198   if (sheet->hmodel)
199     g_object_unref (sheet->hmodel);
200 
201   sheet->dispose_has_run = TRUE;
202 
203   G_OBJECT_CLASS (ssw_sheet_parent_class)->dispose (object);
204 }
205 
206 
207 static void
resize_vmodel(GtkTreeModel * tm,guint posn,guint rm,guint add,GListModel * vmodel)208 resize_vmodel (GtkTreeModel *tm, guint posn, guint rm, guint add, GListModel *vmodel)
209 {
210   gint rows = gtk_tree_model_iter_n_children (tm, NULL);
211 
212   g_object_set (vmodel, "size", rows, NULL);
213 }
214 
215 static void
resize_hmodel(GtkTreeModel * tm,guint posn,guint rm,guint add,GListModel * hmodel)216 resize_hmodel (GtkTreeModel *tm, guint posn, guint rm, guint add, GListModel *hmodel)
217 {
218   gint cols = gtk_tree_model_get_n_columns (tm);
219 
220   g_object_set (hmodel, "size", cols, NULL);
221 }
222 
223 
224 
225 static void
__set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)226 __set_property (GObject *object,
227                 guint prop_id, const GValue *value, GParamSpec *pspec)
228 {
229   gint i;
230   SswSheet *sheet = SSW_SHEET (object);
231 
232   switch (prop_id)
233     {
234     case PROP_SPLITTER:
235       {
236         GType t = g_value_get_gtype (value);
237 
238         if (t == GTK_TYPE_CONTAINER)
239           t = SSW_TYPE_XPANED;
240 
241         GtkWidget *splitter = g_object_new (t, NULL);
242         gtk_container_add (GTK_CONTAINER (sheet), splitter);
243         gtk_widget_show (splitter);
244 
245         for (i = 0; i < DIM * DIM ; ++i)
246           {
247             gtk_container_add_with_properties (GTK_CONTAINER (splitter),
248                                                sheet->sw[i],
249                                                "left-attach", i%DIM,
250                                                "top-attach", i/DIM,
251                                                NULL);
252           }
253       }
254       break;
255     case PROP_GRIDLINES:
256       {
257         gboolean lines = g_value_get_boolean (value);
258 
259         for (i = 0; i < DIM * DIM ; ++i)
260           {
261             g_object_set (SSW_SHEET_SINGLE (sheet->sheet[i])->body, "gridlines", lines, NULL);
262           }
263         sheet->gridlines = lines;
264       }
265       break;
266     case PROP_EDITABLE:
267       {
268         gboolean editable = g_value_get_boolean (value);
269 
270         for (i = 0; i < DIM * DIM ; ++i)
271           {
272             g_object_set (SSW_SHEET_SINGLE (sheet->sheet[i])->body,
273                           "editable", editable,
274                           NULL);
275           }
276         sheet->editable = editable;
277       }
278       break;
279 
280     case PROP_HORIZONTAL_DRAGGABLE:
281       for (i = 0; i < DIM; ++i)
282         g_object_set (SSW_SHEET_AXIS (sheet->horizontal_axis[i]),
283                       "draggable",  g_value_get_boolean (value), NULL);
284       break;
285 
286     case PROP_VERTICAL_DRAGGABLE:
287       for (i = 0; i < DIM; ++i)
288         g_object_set (SSW_SHEET_AXIS (sheet->vertical_axis[i]),
289                       "draggable",  g_value_get_boolean (value), NULL);
290       break;
291 
292     case PROP_SELECTION:
293       {
294         gpointer p = g_value_get_pointer (value);
295         if (p)
296           {
297             SswRange *r = p;
298             *sheet->selection = *r;
299 
300             for (i = 0; i < DIM * DIM ; ++i)
301               gtk_widget_queue_draw (SSW_SHEET_SINGLE (sheet->sheet[i])->body);
302 
303             g_signal_emit (sheet, signals [SELECTION_CHANGED], 0, sheet->selection);
304           }
305       }
306       break;
307 
308     case PROP_CONVERT_FWD_FUNC:
309       {
310         gpointer p = g_value_get_pointer (value);
311 
312         if (p)
313           for (i = 0; i < DIM * DIM ; ++i)
314             {
315               g_object_set (SSW_SHEET_SINGLE (sheet->sheet[i])->body,
316                             "forward-conversion", p,
317                             NULL);
318             }
319       }
320       break;
321 
322     case PROP_CONVERT_REV_FUNC:
323       {
324         gpointer p = g_value_get_pointer (value);
325 
326         if (p)
327           for (i = 0; i < DIM * DIM ; ++i)
328             {
329               g_object_set (SSW_SHEET_SINGLE (sheet->sheet[i])->body,
330                             "reverse-conversion", p,
331                             NULL);
332             }
333       }
334       break;
335 
336     case PROP_RENDERER_FUNC:
337       {
338         gpointer select_renderer_func = g_value_get_pointer (value);
339 
340         for (i = 0; i < DIM * DIM ; ++i)
341           {
342             g_object_set (SSW_SHEET_SINGLE (sheet->sheet[i])->body,
343                           "select-renderer-func", select_renderer_func,
344                           NULL);
345           }
346         //    sheet->select_renderer_func = select_renderer_func;
347       }
348       break;
349 
350     case PROP_RENDERER_FUNC_DATUM:
351       sheet->renderer_func_datum = g_value_get_pointer (value);
352       break;
353 
354     case PROP_SPLIT:
355       {
356         gboolean split = g_value_get_boolean (value);
357 
358         for (i = 0; i < DIM * DIM ; ++i)
359           {
360             g_object_set (sheet->sw[i], "no-show-all", !split, NULL);
361             gtk_widget_set_visible (sheet->sw[i], split);
362           }
363         g_object_set (sheet->sw[0], "no-show-all", FALSE, NULL);
364         gtk_widget_show (sheet->sw[0]);
365 
366         const int dimens = (split ? 2 : 1);
367         for (i = 0; i < dimens * dimens; ++i)
368           {
369             g_object_set (sheet->sw[i],  "vscrollbar-policy",
370                           ((i%dimens) == dimens - 1) ? GTK_POLICY_ALWAYS : GTK_POLICY_NEVER,
371                           NULL);
372 
373             g_object_set (sheet->sw[i], "hscrollbar-policy",
374                           ((i/dimens) == dimens - 1) ? GTK_POLICY_ALWAYS : GTK_POLICY_NEVER,
375                           NULL);
376           }
377 
378         sheet->split = split;
379       }
380       break;
381     case PROP_VMODEL:
382       g_set_object (&sheet->vmodel, g_value_get_object (value));
383       arrange (sheet);
384       break;
385     case PROP_HMODEL:
386       g_set_object (&sheet->hmodel, g_value_get_object (value));
387       arrange (sheet);
388       break;
389     case PROP_DATA_MODEL:
390       sheet->data_model = g_value_get_object (value);
391 
392       GtkTreeModelFlags flags =
393         gtk_tree_model_get_flags (GTK_TREE_MODEL (sheet->data_model));
394 
395       if (!(flags & GTK_TREE_MODEL_LIST_ONLY))
396         g_warning ("SswSheet can interpret list models only. Child nodes will be ignored.");
397 
398       if (sheet->vmodel == NULL)
399         sheet->vmodel = g_object_new (SSW_TYPE_AXIS_MODEL, NULL);
400 
401       if (sheet->hmodel == NULL)
402         sheet->hmodel = g_object_new (SSW_TYPE_AXIS_MODEL,  NULL);
403 
404       if (SSW_IS_AXIS_MODEL (sheet->vmodel))
405         {
406           int n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (sheet->data_model), NULL);
407           g_object_set (sheet->vmodel, "size", n_rows, NULL);
408           g_signal_connect_object (sheet->data_model, "items-changed",
409                                    G_CALLBACK (resize_vmodel), sheet->vmodel, 0);
410         }
411 
412       if (SSW_IS_AXIS_MODEL (sheet->hmodel))
413         {
414           int n_cols = gtk_tree_model_get_n_columns (GTK_TREE_MODEL (sheet->data_model));
415           g_object_set (sheet->hmodel, "size", n_cols, NULL);
416           g_signal_connect_object (sheet->data_model, "items-changed",
417                                    G_CALLBACK (resize_hmodel), sheet->hmodel, 0);
418         }
419 
420       arrange (sheet);
421 
422       for (i = 0; i < DIM * DIM; ++i)
423         {
424           g_object_set (sheet->sheet[i], "data-model", sheet->data_model, NULL);
425         }
426       break;
427     default:
428       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
429       break;
430     }
431 }
432 
433 static void
__get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)434 __get_property (GObject *object,
435                 guint prop_id, GValue *value, GParamSpec *pspec)
436 {
437   switch (prop_id)
438     {
439     case PROP_DATA_MODEL:
440       g_value_set_object (value, SSW_SHEET (object)->data_model);
441       break;
442     case PROP_VMODEL:
443       g_value_set_object (value, SSW_SHEET (object)->vmodel);
444       break;
445     case PROP_HMODEL:
446       g_value_set_object (value, SSW_SHEET (object)->hmodel);
447       break;
448     case PROP_SPLIT:
449       g_value_set_boolean (value, SSW_SHEET (object)->split);
450       break;
451     case PROP_GRIDLINES:
452       g_value_set_boolean (value, SSW_SHEET (object)->gridlines);
453       break;
454     case PROP_SELECTION:
455       g_value_set_pointer (value, SSW_SHEET (object)->selection);
456       break;
457     case PROP_EDITABLE:
458       g_value_set_boolean (value, SSW_SHEET (object)->editable);
459       break;
460     case PROP_RENDERER_FUNC_DATUM:
461       g_value_set_pointer (value, SSW_SHEET (object)->renderer_func_datum);
462       break;
463     default:
464       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
465       break;
466     }
467 }
468 
469 
470 static void
ssw_sheet_class_init(SswSheetClass * class)471 ssw_sheet_class_init (SswSheetClass *class)
472 {
473   GObjectClass *object_class = G_OBJECT_CLASS (class);
474   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
475 
476   GParamSpec *convert_fwd_spec =
477     g_param_spec_pointer ("forward-conversion",
478                           P_("Forward conversion function"),
479                           P_("A function to convert a cell datum to a string"),
480                           G_PARAM_WRITABLE);
481 
482   GParamSpec *convert_rev_spec =
483     g_param_spec_pointer ("reverse-conversion",
484                           P_("Reverse conversion function"),
485                           P_("A function to convert a string to a cell datum"),
486                           G_PARAM_WRITABLE);
487 
488 
489   GParamSpec *selection_spec =
490     g_param_spec_pointer ("selection",
491                           P_("The selection"),
492                           P_("A pointer to the current selection"),
493                           G_PARAM_READWRITE);
494 
495   GParamSpec *splitter_spec =
496     g_param_spec_gtype ("splitter",
497                         P_("Splitter Container Type"),
498                         P_("The type of container widget to handle splits"),
499                         GTK_TYPE_CONTAINER,
500                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
501 
502   GParamSpec *vmodel_spec =
503     g_param_spec_object ("vmodel",
504                          P_("Vertical Model"),
505                          P_("The model describing the rows"),
506                          G_TYPE_LIST_MODEL,
507                          G_PARAM_READWRITE);
508 
509   GParamSpec *hmodel_spec =
510     g_param_spec_object ("hmodel",
511                          P_("Horizontal Model"),
512                          P_("The model describing the columns"),
513                          G_TYPE_LIST_MODEL,
514                          G_PARAM_READWRITE);
515 
516   GParamSpec *data_model_spec =
517     g_param_spec_object ("data-model",
518                          P_("Data Model"),
519                          P_("The model describing the contents of the data"),
520                          GTK_TYPE_TREE_MODEL,
521                          G_PARAM_READWRITE);
522 
523   GParamSpec *split_spec =
524     g_param_spec_boolean ("split",
525                           P_("Split View"),
526                           P_("If TRUE the sheet view is split four ways"),
527                           FALSE,
528                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
529 
530   GParamSpec *horizontal_draggable_spec =
531     g_param_spec_boolean ("horizontal-draggable",
532                           P_("Horizontal_Draggable"),
533                           P_("If TRUE, items in the horizontal axis can be dragged."),
534                           FALSE,
535                           G_PARAM_WRITABLE | G_PARAM_CONSTRUCT);
536 
537   GParamSpec *vertical_draggable_spec =
538     g_param_spec_boolean ("vertical-draggable",
539                           P_("Vertical_Draggable"),
540                           P_("If TRUE, items in the vertical axis can be dragged."),
541                           FALSE,
542                           G_PARAM_WRITABLE | G_PARAM_CONSTRUCT);
543 
544   GParamSpec *gridlines_spec =
545     g_param_spec_boolean ("gridlines",
546                           P_("Show Gridlines"),
547                           P_("True if gridlines should be shown"),
548                           TRUE,
549                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
550 
551   GParamSpec *editable_spec =
552     g_param_spec_boolean ("editable",
553                           P_("Editable"),
554                           P_("True if the sheet is editable"),
555                           FALSE,
556                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
557 
558   GParamSpec *renderer_func_spec =
559     g_param_spec_pointer ("select-renderer-func",
560                           P_("Select Renderer Function"),
561                           P_("Function returning the renderer to use for a cell"),
562                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
563 
564   GParamSpec *renderer_func_datum_spec =
565     g_param_spec_pointer ("select-renderer-datum",
566                           P_("Select Renderer Function Datum"),
567                           P_("The Datum to be passed to the \"select-renderer-func\" property"),
568                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
569 
570   object_class->set_property = __set_property;
571   object_class->get_property = __get_property;
572   object_class->dispose = __dispose;
573   object_class->finalize = __finalize;
574   widget_class->realize = __realize;
575   widget_class->unrealize = __unrealize;
576 
577   g_object_class_install_property (object_class,
578                                    PROP_CONVERT_FWD_FUNC,
579                                    convert_fwd_spec);
580 
581   g_object_class_install_property (object_class,
582                                    PROP_CONVERT_REV_FUNC,
583                                    convert_rev_spec);
584 
585   g_object_class_install_property (object_class,
586                                    PROP_RENDERER_FUNC,
587                                    renderer_func_spec);
588 
589   g_object_class_install_property (object_class,
590                                    PROP_RENDERER_FUNC_DATUM,
591                                    renderer_func_datum_spec);
592 
593   g_object_class_install_property (object_class,
594                                    PROP_SPLITTER,
595                                    splitter_spec);
596 
597   g_object_class_install_property (object_class,
598                                    PROP_SELECTION,
599                                    selection_spec);
600 
601   g_object_class_install_property (object_class,
602                                    PROP_VMODEL,
603                                    vmodel_spec);
604 
605   g_object_class_install_property (object_class,
606                                    PROP_HMODEL,
607                                    hmodel_spec);
608 
609   g_object_class_install_property (object_class,
610                                    PROP_DATA_MODEL,
611                                    data_model_spec);
612 
613   g_object_class_install_property (object_class,
614                                    PROP_SPLIT,
615                                    split_spec);
616 
617   g_object_class_install_property (object_class,
618                                    PROP_GRIDLINES,
619                                    gridlines_spec);
620 
621   g_object_class_install_property (object_class,
622                                    PROP_EDITABLE,
623                                    editable_spec);
624 
625   g_object_class_install_property (object_class,
626                                    PROP_HORIZONTAL_DRAGGABLE,
627                                    horizontal_draggable_spec);
628 
629   g_object_class_install_property (object_class,
630                                    PROP_VERTICAL_DRAGGABLE,
631                                    vertical_draggable_spec);
632 
633   signals [ROW_HEADER_CLICKED] =
634     g_signal_new ("row-header-clicked",
635                   G_TYPE_FROM_CLASS (class),
636                   G_SIGNAL_RUN_FIRST,
637                   0,
638                   NULL, NULL,
639                   g_cclosure_marshal_VOID__INT,
640                   G_TYPE_NONE,
641                   1,
642                   G_TYPE_INT);
643 
644   signals [ROW_HEADER_DOUBLE_CLICKED] =
645     g_signal_new ("row-header-double-clicked",
646                   G_TYPE_FROM_CLASS (class),
647                   G_SIGNAL_RUN_FIRST,
648                   0,
649                   NULL, NULL,
650                   g_cclosure_marshal_VOID__INT,
651                   G_TYPE_NONE,
652                   1,
653                   G_TYPE_INT);
654 
655   signals [COLUMN_HEADER_CLICKED] =
656     g_signal_new ("column-header-clicked",
657                   G_TYPE_FROM_CLASS (class),
658                   G_SIGNAL_RUN_FIRST,
659                   0,
660                   NULL, NULL,
661                   g_cclosure_marshal_VOID__INT,
662                   G_TYPE_NONE,
663                   1,
664                   G_TYPE_INT);
665 
666   signals [COLUMN_HEADER_DOUBLE_CLICKED] =
667     g_signal_new ("column-header-double-clicked",
668                   G_TYPE_FROM_CLASS (class),
669                   G_SIGNAL_RUN_FIRST,
670                   0,
671                   NULL, NULL,
672                   g_cclosure_marshal_VOID__INT,
673                   G_TYPE_NONE,
674                   1,
675                   G_TYPE_INT);
676 
677 
678   signals [ROW_HEADER_PRESSED] =
679     g_signal_new ("row-header-pressed",
680                   G_TYPE_FROM_CLASS (class),
681                   G_SIGNAL_RUN_FIRST,
682                   0,
683                   NULL, NULL,
684                   ssw_cclosure_marshal_VOID__INT_UINT_UINT,
685                   G_TYPE_NONE,
686                   3,
687                   G_TYPE_INT,
688                   G_TYPE_UINT,
689                   G_TYPE_UINT);
690 
691   signals [ROW_HEADER_RELEASED] =
692     g_signal_new ("row-header-released",
693                   G_TYPE_FROM_CLASS (class),
694                   G_SIGNAL_RUN_FIRST,
695                   0,
696                   NULL, NULL,
697                   ssw_cclosure_marshal_VOID__INT_UINT_UINT,
698                   G_TYPE_NONE,
699                   3,
700                   G_TYPE_INT,
701                   G_TYPE_UINT,
702                   G_TYPE_UINT);
703 
704   signals [COLUMN_HEADER_PRESSED] =
705     g_signal_new ("column-header-pressed",
706                   G_TYPE_FROM_CLASS (class),
707                   G_SIGNAL_RUN_FIRST,
708                   0,
709                   NULL, NULL,
710                   ssw_cclosure_marshal_VOID__INT_UINT_UINT,
711                   G_TYPE_NONE,
712                   3,
713                   G_TYPE_INT,
714                   G_TYPE_UINT,
715                   G_TYPE_UINT);
716 
717   signals [COLUMN_HEADER_RELEASED] =
718     g_signal_new ("column-header-released",
719                   G_TYPE_FROM_CLASS (class),
720                   G_SIGNAL_RUN_FIRST,
721                   0,
722                   NULL, NULL,
723                   ssw_cclosure_marshal_VOID__INT_UINT_UINT,
724                   G_TYPE_NONE,
725                   3,
726                   G_TYPE_INT,
727                   G_TYPE_UINT,
728                   G_TYPE_UINT);
729 
730 
731   signals [SELECTION_CHANGED] =
732     g_signal_new ("selection-changed",
733                   G_TYPE_FROM_CLASS (class),
734                   G_SIGNAL_RUN_FIRST,
735                   0,
736                   NULL, NULL,
737                   g_cclosure_marshal_VOID__POINTER,
738                   G_TYPE_NONE,
739                   1,
740                   G_TYPE_POINTER);
741 
742 
743   signals [VALUE_CHANGED] =
744     g_signal_new ("value-changed",
745                   G_TYPE_FROM_CLASS (class),
746                   G_SIGNAL_RUN_FIRST,
747                   0,
748                   NULL, NULL,
749                   ssw_cclosure_marshal_VOID__INT_INT_POINTER,
750                   G_TYPE_NONE,
751                   3,
752                   G_TYPE_INT,
753                   G_TYPE_INT,
754                   G_TYPE_POINTER);
755 
756   signals [ROW_MOVED] =
757     g_signal_new ("row-moved",
758                   G_TYPE_FROM_CLASS (class),
759                   G_SIGNAL_RUN_FIRST,
760                   0,
761                   NULL, NULL,
762                   ssw_cclosure_marshal_VOID__INT_INT,
763                   G_TYPE_NONE,
764                   2,
765                   G_TYPE_INT,
766                   G_TYPE_INT);
767 
768   signals [COLUMN_MOVED] =
769     g_signal_new ("column-moved",
770                   G_TYPE_FROM_CLASS (class),
771                   G_SIGNAL_RUN_FIRST,
772                   0,
773                   NULL, NULL,
774                   ssw_cclosure_marshal_VOID__INT_INT,
775                   G_TYPE_NONE,
776                   2,
777                   G_TYPE_INT,
778                   G_TYPE_INT);
779 }
780 
781 static void
forward_signal(SswSheet * sheet,...)782 forward_signal (SswSheet *sheet, ...)
783 {
784   va_list ap;
785   va_start (ap, sheet);
786   g_signal_emit_valist (sheet, signals [VALUE_CHANGED], 0, ap);
787   va_end (ap);
788 }
789 
790 static void
forward_selection_signal(SswSheet * sheet,SswRange * sel,gpointer ud)791 forward_selection_signal (SswSheet *sheet, SswRange *sel, gpointer ud)
792 {
793   gint i;
794 
795   for (i = 0; i < DIM * DIM ; ++i)
796     {
797       GtkWidget *body = SSW_SHEET_SINGLE (sheet->sheet[i])->body;
798       if (body == ud)
799         continue;
800 
801       gtk_widget_queue_draw (body);
802     }
803   g_signal_emit (sheet, signals [SELECTION_CHANGED], 0, sel);
804 }
805 
806 static void
on_drag_n_drop(SswSheet * sheet,gint from,gint to,GtkOrientable * axis)807 on_drag_n_drop (SswSheet *sheet, gint from, gint to, GtkOrientable *axis)
808 {
809   if (to - from == 1 || to == from) /* This is a null move */
810     return;
811 
812   if (gtk_orientable_get_orientation (axis) == GTK_ORIENTATION_HORIZONTAL)
813     g_signal_emit (sheet, signals [COLUMN_MOVED], 0, from, to);
814   else
815     g_signal_emit (sheet, signals [ROW_MOVED], 0, from, to);
816 }
817 
818 static void
ssw_sheet_init(SswSheet * sheet)819 ssw_sheet_init (SswSheet *sheet)
820 {
821   gtk_widget_set_has_window (GTK_WIDGET (sheet), FALSE);
822   sheet->vmodel = g_object_new (SSW_TYPE_AXIS_MODEL, NULL);
823   sheet->hmodel = g_object_new (SSW_TYPE_AXIS_MODEL, NULL);
824 
825   gint i;
826   for (i = 0; i < DIM; ++i)
827     {
828       sheet->vadj[i] = gtk_adjustment_new (0, 0, 0, 0, 0, 0);
829       sheet->hadj[i] = gtk_adjustment_new (0, 0, 0, 0, 0, 0);
830 
831       sheet->horizontal_axis[i] =
832         ssw_sheet_axis_new (GTK_ORIENTATION_HORIZONTAL);
833 
834 
835       g_signal_connect (sheet->horizontal_axis[i], "header-clicked",
836                         G_CALLBACK (on_header_clicked), sheet);
837 
838       g_signal_connect (sheet->horizontal_axis[i], "header-double-clicked",
839                         G_CALLBACK (on_header_double_clicked), sheet);
840 
841       g_signal_connect (sheet->horizontal_axis[i], "header-button-pressed",
842                         G_CALLBACK (on_header_button_pressed), sheet);
843 
844       g_signal_connect (sheet->horizontal_axis[i], "header-button-released",
845                         G_CALLBACK (on_header_button_released), sheet);
846 
847       sheet->vertical_axis[i] =
848         ssw_sheet_axis_new (GTK_ORIENTATION_VERTICAL);
849 
850       g_signal_connect (sheet->vertical_axis[i], "header-clicked",
851                         G_CALLBACK (on_header_clicked), sheet);
852 
853       g_signal_connect (sheet->vertical_axis[i], "header-double-clicked",
854                         G_CALLBACK (on_header_double_clicked), sheet);
855 
856       g_signal_connect (sheet->vertical_axis[i], "header-button-pressed",
857                         G_CALLBACK (on_header_button_pressed), sheet);
858 
859       g_signal_connect (sheet->vertical_axis[i], "header-button-released",
860                         G_CALLBACK (on_header_button_released), sheet);
861     }
862 
863   sheet->selection = g_malloc (sizeof *sheet->selection);
864   sheet->selection->start_x = -1;
865   sheet->selection->start_y = -1;
866   sheet->selection->end_x = -1;
867   sheet->selection->end_y = -1;
868 
869   for (i = 0; i < DIM ; ++i)
870     {
871       g_signal_connect_swapped (sheet->horizontal_axis[i], "drag-n-dropped",
872                                 G_CALLBACK (on_drag_n_drop), sheet);
873 
874       g_signal_connect_swapped (sheet->vertical_axis[i], "drag-n-dropped",
875                                 G_CALLBACK (on_drag_n_drop), sheet);
876     }
877 
878   for (i = 0; i < DIM * DIM ; ++i)
879     {
880       sheet->sw[i] = gtk_scrolled_window_new (sheet->hadj[i%DIM],
881                                               sheet->vadj[i/DIM]);
882 
883       g_object_set (sheet->sw[i], "shadow-type", GTK_SHADOW_IN, NULL);
884 
885       sheet->sheet[i] =
886         ssw_sheet_single_new (sheet,
887                               SSW_SHEET_AXIS (sheet->horizontal_axis[i%DIM]),
888                               SSW_SHEET_AXIS (sheet->vertical_axis[i/DIM]),
889                               sheet->selection);
890 
891       gtk_widget_show_all (sheet->sheet[i]);
892 
893       gtk_container_add (GTK_CONTAINER (sheet->sw[i]), sheet->sheet[i]);
894 
895       g_signal_connect_swapped (SSW_SHEET_SINGLE (sheet->sheet[i])->body,
896                                 "selection-changed", G_CALLBACK (forward_selection_signal), sheet);
897 
898       g_signal_connect_swapped (SSW_SHEET_SINGLE (sheet->sheet[i])->body,
899                                 "value-changed", G_CALLBACK (forward_signal), sheet);
900     }
901 
902   sheet->renderer_func_datum = NULL;
903   sheet->dispose_has_run = FALSE;
904   sheet->selected_body = SSW_SHEET_SINGLE (sheet->sheet[0])->body;
905 
906   arrange (sheet);
907 
908   sheet->cursor_stack = NULL;
909 }
910 
911 void
ssw_sheet_set_clip(SswSheet * sheet,GtkClipboard * clip)912 ssw_sheet_set_clip (SswSheet *sheet, GtkClipboard *clip)
913 {
914   if (!sheet->data_model)
915     return;
916 
917   ssw_sheet_body_set_clip (SSW_SHEET_BODY (sheet->selected_body), clip);
918 }
919 
920 
921 void
ssw_sheet_scroll_to(SswSheet * sheet,gint hpos,gint vpos)922 ssw_sheet_scroll_to (SswSheet *sheet, gint hpos, gint vpos)
923 {
924   if (hpos >= 0)
925     ssw_sheet_axis_jump_center (SSW_SHEET_AXIS (sheet->horizontal_axis[0]), hpos);
926 
927   if (vpos >= 0)
928     ssw_sheet_axis_jump_center (SSW_SHEET_AXIS (sheet->vertical_axis[0]), vpos);
929 }
930 
931 
932 void
ssw_sheet_set_active_cell(SswSheet * sheet,gint col,gint row,GdkEvent * e)933 ssw_sheet_set_active_cell (SswSheet *sheet,
934                            gint col, gint row, GdkEvent *e)
935 {
936   ssw_sheet_body_set_active_cell (SSW_SHEET_BODY (sheet->selected_body),
937                                   col, row, e);
938 }
939 
940 
941 gboolean
ssw_sheet_get_active_cell(SswSheet * sheet,gint * col,gint * row)942 ssw_sheet_get_active_cell (SswSheet *sheet,
943                            gint *col, gint *row)
944 {
945   return ssw_sheet_body_get_active_cell (SSW_SHEET_BODY (sheet->selected_body),
946                                          col, row);
947 }
948 
949 
950 
951 /* A callback function which populates the cell indicated by PS with the value
952    indicated by STR and the current reverse conversion function.  */
953 void
ssw_sheet_paste_insert_datum(const gchar * str,size_t len,const struct paste_state * ps)954 ssw_sheet_paste_insert_datum (const gchar *str, size_t len, const struct paste_state *ps)
955 {
956   SswSheet *sheet = ps->sheet;
957 
958   GValue value = G_VALUE_INIT;
959 
960   gint col = ps->col + ps->col0;
961   gint row = ps->row + ps->row0;
962 
963   ssw_sheet_reverse_conversion_func rcf;
964 
965   g_object_get (SSW_SHEET_SINGLE (sheet->sheet[0])->body,
966                 "reverse-conversion", &rcf,
967                 NULL);
968 
969   if (rcf (sheet->data_model, col, row, str, &value))
970     ps->set_cell (sheet->data_model, col, row, &value);
971   g_value_unset (&value);
972 }
973 
974 static void
paste_datum(const gchar * x,size_t len,struct paste_state * t)975 paste_datum (const gchar *x, size_t len, struct paste_state *t)
976 {
977   ssw_sheet_paste_insert_datum (x, len, t);
978   t->col ++;
979 }
980 
981 static void
end_of_row(struct paste_state * t)982 end_of_row (struct paste_state *t)
983 {
984   t->row++;
985   t->col = 0;
986 }
987 
988 static void
end_of_paste(struct paste_state * t)989 end_of_paste (struct paste_state *t)
990 {
991   g_free (t);
992 }
993 
994 
995 static void
parse_delimited_data(const gchar * data,int len,const char * delim,void (* payload)(const gchar *,size_t,struct paste_state *),void (* endload)(struct paste_state *),struct paste_state * dw)996 parse_delimited_data (const gchar *data, int len, const char *delim,
997                       void (*payload)(const gchar *, size_t, struct paste_state *),
998                       void (*endload)(struct paste_state *),
999                       struct paste_state * dw)
1000 {
1001   while (len > 0)
1002     {
1003       const gchar *x = g_strstr_len (data, len, delim);
1004       if (x == NULL)
1005         {
1006           char *f = g_strndup (data, len);
1007           payload (f, len, dw);
1008           g_free (f);
1009           break;
1010         }
1011       len -= x - data + 1;
1012 
1013       gchar *line = g_strndup (data, x - data);
1014       payload (line, x - data, dw);
1015       data = x + 1;
1016       g_free (line);
1017     }
1018   if (endload)
1019     endload (dw);
1020 }
1021 
1022 static void
parseit(const gchar * x,size_t len,struct paste_state * dw)1023 parseit (const gchar *x, size_t len, struct paste_state *dw)
1024 {
1025   parse_delimited_data (x, len, "\t", paste_datum, end_of_row, dw);
1026 }
1027 
1028 static void
utf8_tab_delimited_parse(GtkClipboard * clip,GtkSelectionData * sd,gpointer user_data)1029 utf8_tab_delimited_parse (GtkClipboard *clip, GtkSelectionData *sd,
1030                           gpointer user_data)
1031 {
1032   struct paste_state *ps = user_data;
1033   SswSheet *sheet = SSW_SHEET (ps->sheet);
1034   const gchar *data = (const gchar *) gtk_selection_data_get_data (sd);
1035   gint len = gtk_selection_data_get_length (sd);
1036 
1037   if (len < 0)
1038     {
1039       g_free (ps);
1040       return;
1041     }
1042 
1043   ps->row = 0;
1044   ps->col = 0;
1045 
1046   parse_delimited_data (data, len, "\n", parseit, end_of_paste, ps);
1047   gtk_widget_queue_draw (GTK_WIDGET (sheet));
1048 }
1049 
1050 static void
target_marshaller(GtkClipboard * clip,GdkAtom * atoms,gint n_atoms,gpointer user_data)1051 target_marshaller (GtkClipboard *clip, GdkAtom *atoms, gint n_atoms,
1052                    gpointer user_data)
1053 {
1054   int i;
1055   struct paste_state *ps = user_data;
1056 
1057   if (atoms == NULL)
1058     {
1059       g_free (ps);
1060       return;
1061     }
1062 
1063   for (i = 0; i < n_atoms; ++i)
1064     {
1065       if (atoms[i] == gdk_atom_intern_static_string ("text/html"))
1066         {
1067           gtk_clipboard_request_contents (clip, atoms[i], ssw_html_parse, ps);
1068           break;
1069         }
1070       else if (atoms[i] == gdk_atom_intern_static_string ("UTF8_STRING"))
1071         {
1072           gtk_clipboard_request_contents (clip, atoms[i], utf8_tab_delimited_parse, ps);
1073           break;
1074         }
1075     }
1076 
1077   if (i == n_atoms)
1078     g_free (ps);
1079 }
1080 
1081 void
ssw_sheet_paste(SswSheet * sheet,GtkClipboard * clip,ssw_sheet_set_cell sc)1082 ssw_sheet_paste (SswSheet *sheet, GtkClipboard *clip, ssw_sheet_set_cell sc)
1083 {
1084   g_return_if_fail (sheet);
1085 
1086   gint col, row;
1087 
1088   if (ssw_sheet_body_paste_editable (SSW_SHEET_BODY (sheet->selected_body)))
1089     return;
1090 
1091   if (ssw_sheet_get_active_cell (sheet, &col, &row))
1092     {
1093       struct paste_state *ps = g_malloc (sizeof *ps);
1094       ps->sheet = sheet;
1095       ps->set_cell = sc;
1096 
1097       ps->col0 = col;
1098       ps->row0 = row;
1099 
1100       gtk_clipboard_request_targets (clip, target_marshaller, ps);
1101     }
1102 }
1103 
1104 gboolean
ssw_sheet_try_cut(SswSheet * sheet)1105 ssw_sheet_try_cut (SswSheet *sheet)
1106 {
1107   g_return_val_if_fail (sheet, FALSE);
1108 
1109   return ssw_sheet_body_cut_editable (SSW_SHEET_BODY (sheet->selected_body));
1110 }
1111 
1112 
1113 
1114 void
ssw_sheet_wait_push(SswSheet * sheet)1115 ssw_sheet_wait_push (SswSheet *sheet)
1116 {
1117   GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (sheet));
1118   if (win == NULL)
1119     return;
1120 
1121   GdkCursor *cursor = gdk_window_get_cursor (win);
1122   sheet->cursor_stack = g_slist_prepend (sheet->cursor_stack, cursor);
1123   gdk_window_set_cursor (win, sheet->wait_cursor);
1124 }
1125 
1126 
1127 void
ssw_sheet_wait_pop(SswSheet * sheet)1128 ssw_sheet_wait_pop (SswSheet *sheet)
1129 {
1130   GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (sheet));
1131   if (win == NULL)
1132     return;
1133 
1134   GSList *cursor = sheet->cursor_stack;
1135   gdk_window_set_cursor (win, cursor->data);
1136   sheet->cursor_stack = sheet->cursor_stack->next;
1137 }
1138