b7ed13346d897a9671b903d0fa5572794fd0c84b
[pspp-builds.git] / src / ui / gui / clipboard.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007  Free Software Foundation
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <gtksheet/gtksheet.h>
20 #include "clipboard.h"
21 #include <data/case.h>
22 #include "psppire-data-store.h"
23 #include <data/casereader.h>
24 #include <data/case-map.h>
25 #include <libpspp/alloc.h>
26 #include <data/casewriter.h>
27 #include <data/format.h>
28 #include <data/data-out.h>
29 #include <stdlib.h>
30
31
32 /* A casereader and dictionary holding the data currently in the clip */
33 static struct casereader *clip_datasheet = NULL;
34 struct dictionary *clip_dict = NULL;
35
36
37
38
39 static void data_sheet_update_clipboard (GtkSheet *);
40
41 /* Set the clip according to the currently
42    selected range in the data sheet */
43 void
44 data_sheet_set_clip (GtkSheet *sheet)
45 {
46   int i;
47   struct casewriter *writer ;
48   GtkSheetRange range;
49   PsppireDataStore *ds;
50   struct case_map *map = NULL;
51   casenumber max_rows;
52   size_t max_columns;
53
54   ds = PSPPIRE_DATA_STORE (gtk_sheet_get_model (sheet));
55
56   gtk_sheet_get_selected_range (sheet, &range);
57
58    /* If nothing selected, then use active cell */
59   if ( range.row0 < 0 || range.col0 < 0 )
60     {
61       gint row, col;
62       gtk_sheet_get_active_cell (sheet, &row, &col);
63
64       range.row0 = range.rowi = row;
65       range.col0 = range.coli = col;
66     }
67
68   /* The sheet range can include cells that do not include data.
69      Exclude them from the range. */
70   max_rows = psppire_data_store_get_case_count (ds);
71   if (range.rowi >= max_rows)
72     {
73       if (max_rows == 0)
74         return;
75       range.rowi = max_rows - 1;
76     }
77   max_columns = dict_get_var_cnt (ds->dict->dict);
78   if (range.coli >= max_columns)
79     {
80       if (max_columns == 0)
81         return;
82       range.coli = max_columns - 1;
83     }
84
85   g_return_if_fail (range.rowi >= range.row0);
86   g_return_if_fail (range.row0 >= 0);
87   g_return_if_fail (range.coli >= range.col0);
88   g_return_if_fail (range.col0 >= 0);
89
90   /* Destroy any existing clip */
91   if ( clip_datasheet )
92     {
93       casereader_destroy (clip_datasheet);
94       clip_datasheet = NULL;
95     }
96
97   if ( clip_dict )
98     {
99       dict_destroy (clip_dict);
100       clip_dict = NULL;
101     }
102
103   /* Construct clip dictionary. */
104   clip_dict = dict_create ();
105   for (i = range.col0; i <= range.coli; i++)
106     {
107       const struct variable *old = dict_get_var (ds->dict->dict, i);
108       dict_clone_var_assert (clip_dict, old, var_get_name (old));
109     }
110
111   /* Construct clip data. */
112   map = case_map_by_name (ds->dict->dict, clip_dict);
113   writer = autopaging_writer_create (dict_get_next_value_idx (clip_dict));
114   for (i = range.row0; i <= range.rowi ; ++i )
115     {
116       struct ccase old;
117
118       if (psppire_case_file_get_case (ds->case_file, i, &old))
119         {
120           struct ccase new;
121
122           case_map_execute (map, &old, &new);
123           case_destroy (&old);
124           casewriter_write (writer, &new);
125         }
126       else
127         casewriter_force_error (writer);
128     }
129   case_map_destroy (map);
130
131   clip_datasheet = casewriter_make_reader (writer);
132
133   data_sheet_update_clipboard (sheet);
134 }
135
136 enum {
137   SELECT_FMT_NULL,
138   SELECT_FMT_TEXT,
139   SELECT_FMT_HTML
140 };
141
142
143 /* Perform data_out for case CC, variable V, appending to STRING */
144 static void
145 data_out_g_string (GString *string, const struct variable *v,
146                    const struct ccase *cc)
147 {
148   char *buf ;
149
150   const struct fmt_spec *fs = var_get_print_format (v);
151   const union value *val = case_data (cc, v);
152   buf = xzalloc (fs->w);
153
154   data_out (val, fs, buf);
155
156   g_string_append_len (string, buf, fs->w);
157
158   g_free (buf);
159 }
160
161 static GString *
162 clip_to_text (void)
163 {
164   casenumber r;
165   GString *string;
166
167   const size_t val_cnt = casereader_get_value_cnt (clip_datasheet);
168   const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
169   const size_t var_cnt = dict_get_var_cnt (clip_dict);
170
171   string = g_string_sized_new (10 * val_cnt * case_cnt);
172
173   for (r = 0 ; r < case_cnt ; ++r )
174     {
175       int c;
176       struct ccase cc;
177       if ( !  casereader_peek (clip_datasheet, r, &cc))
178         {
179           g_warning ("Clipboard seems to have inexplicably shrunk");
180           break;
181         }
182
183       for (c = 0 ; c < var_cnt ; ++c)
184         {
185           const struct variable *v = dict_get_var (clip_dict, c);
186           data_out_g_string (string, v, &cc);
187           if ( c < val_cnt - 1 )
188             g_string_append (string, "\t");
189         }
190
191       if ( r < case_cnt)
192         g_string_append (string, "\n");
193
194       case_destroy (&cc);
195     }
196
197   return string;
198 }
199
200
201 static GString *
202 clip_to_html (void)
203 {
204   casenumber r;
205   GString *string;
206
207   const size_t val_cnt = casereader_get_value_cnt (clip_datasheet);
208   const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
209   const size_t var_cnt = dict_get_var_cnt (clip_dict);
210
211
212   /* Guestimate the size needed */
213   string = g_string_sized_new (20 * val_cnt * case_cnt);
214
215   g_string_append (string, "<table>\n");
216   for (r = 0 ; r < case_cnt ; ++r )
217     {
218       int c;
219       struct ccase cc;
220       if ( !  casereader_peek (clip_datasheet, r, &cc))
221         {
222           g_warning ("Clipboard seems to have inexplicably shrunk");
223           break;
224         }
225       g_string_append (string, "<tr>\n");
226
227       for (c = 0 ; c < var_cnt ; ++c)
228         {
229           const struct variable *v = dict_get_var (clip_dict, c);
230           g_string_append (string, "<td>");
231           data_out_g_string (string, v, &cc);
232           g_string_append (string, "</td>\n");
233         }
234
235       g_string_append (string, "</tr>\n");
236
237       case_destroy (&cc);
238     }
239   g_string_append (string, "</table>\n");
240
241   return string;
242 }
243
244
245
246 static void
247 clipboard_get_cb (GtkClipboard     *clipboard,
248                   GtkSelectionData *selection_data,
249                   guint             info,
250                   gpointer          data)
251 {
252   GString *string = NULL;
253
254   switch (info)
255     {
256     case SELECT_FMT_TEXT:
257       string = clip_to_text ();
258       break;
259     case SELECT_FMT_HTML:
260       string = clip_to_html ();
261       break;
262     default:
263       g_assert_not_reached ();
264     }
265
266   gtk_selection_data_set (selection_data, selection_data->target,
267                           8,
268                           (const guchar *) string->str, string->len);
269
270   g_string_free (string, TRUE);
271 }
272
273 static void
274 clipboard_clear_cb (GtkClipboard *clipboard,
275                     gpointer data)
276 {
277   dict_destroy (clip_dict);
278   clip_dict = NULL;
279
280   casereader_destroy (clip_datasheet);
281   clip_datasheet = NULL;
282 }
283
284
285
286 static void
287 data_sheet_update_clipboard (GtkSheet *sheet)
288 {
289   static const GtkTargetEntry targets[] = {
290     { "UTF8_STRING",   0, SELECT_FMT_TEXT },
291     { "STRING",        0, SELECT_FMT_TEXT },
292     { "TEXT",          0, SELECT_FMT_TEXT },
293     { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
294     { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
295     { "text/plain",    0, SELECT_FMT_TEXT },
296     { "text/html",     0, SELECT_FMT_HTML }
297   };
298
299   GtkClipboard *clipboard =
300     gtk_widget_get_clipboard (GTK_WIDGET (sheet),
301                               GDK_SELECTION_CLIPBOARD);
302
303   if (!gtk_clipboard_set_with_owner (clipboard, targets,
304                                      G_N_ELEMENTS (targets),
305                                      clipboard_get_cb, clipboard_clear_cb,
306                                      G_OBJECT (sheet)))
307     clipboard_clear_cb (clipboard, sheet);
308 }
309