oops
[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 <data/casewriter.h>
26 #include <data/format.h>
27 #include <data/data-out.h>
28 #include "helper.h"
29 #include <stdlib.h>
30 #include "data-editor.h"
31
32 #include "xalloc.h"
33
34 /* A casereader and dictionary holding the data currently in the clip */
35 static struct casereader *clip_datasheet = NULL;
36 static struct dictionary *clip_dict = NULL;
37
38
39
40
41 static void data_sheet_update_clipboard (GtkSheet *);
42
43 /* Set the clip according to the currently
44    selected range in the data sheet */
45 void
46 data_sheet_set_clip (GtkSheet *sheet)
47 {
48   int i;
49   struct casewriter *writer ;
50   GtkSheetRange range;
51   PsppireDataStore *ds;
52   struct case_map *map = NULL;
53   casenumber max_rows;
54   size_t max_columns;
55
56   ds = PSPPIRE_DATA_STORE (gtk_sheet_get_model (sheet));
57
58   gtk_sheet_get_selected_range (sheet, &range);
59
60    /* If nothing selected, then use active cell */
61   if ( range.row0 < 0 || range.col0 < 0 )
62     {
63       gint row, col;
64       gtk_sheet_get_active_cell (sheet, &row, &col);
65
66       range.row0 = range.rowi = row;
67       range.col0 = range.coli = col;
68     }
69
70   /* The sheet range can include cells that do not include data.
71      Exclude them from the range. */
72   max_rows = psppire_data_store_get_case_count (ds);
73   if (range.rowi >= max_rows)
74     {
75       if (max_rows == 0)
76         return;
77       range.rowi = max_rows - 1;
78     }
79   max_columns = dict_get_var_cnt (ds->dict->dict);
80   if (range.coli >= max_columns)
81     {
82       if (max_columns == 0)
83         return;
84       range.coli = max_columns - 1;
85     }
86
87   g_return_if_fail (range.rowi >= range.row0);
88   g_return_if_fail (range.row0 >= 0);
89   g_return_if_fail (range.coli >= range.col0);
90   g_return_if_fail (range.col0 >= 0);
91
92   /* Destroy any existing clip */
93   if ( clip_datasheet )
94     {
95       casereader_destroy (clip_datasheet);
96       clip_datasheet = NULL;
97     }
98
99   if ( clip_dict )
100     {
101       dict_destroy (clip_dict);
102       clip_dict = NULL;
103     }
104
105   /* Construct clip dictionary. */
106   clip_dict = dict_create ();
107   for (i = range.col0; i <= range.coli; i++)
108     {
109       const struct variable *old = dict_get_var (ds->dict->dict, i);
110       dict_clone_var_assert (clip_dict, old, var_get_name (old));
111     }
112
113   /* Construct clip data. */
114   map = case_map_by_name (ds->dict->dict, clip_dict);
115   writer = autopaging_writer_create (dict_get_next_value_idx (clip_dict));
116   for (i = range.row0; i <= range.rowi ; ++i )
117     {
118       struct ccase old;
119
120       if (psppire_case_file_get_case (ds->case_file, i, &old))
121         {
122           struct ccase new;
123
124           case_map_execute (map, &old, &new);
125           case_destroy (&old);
126           casewriter_write (writer, &new);
127         }
128       else
129         casewriter_force_error (writer);
130     }
131   case_map_destroy (map);
132
133   clip_datasheet = casewriter_make_reader (writer);
134
135   data_sheet_update_clipboard (sheet);
136 }
137
138 enum {
139   SELECT_FMT_NULL,
140   SELECT_FMT_TEXT,
141   SELECT_FMT_HTML
142 };
143
144
145 /* Perform data_out for case CC, variable V, appending to STRING */
146 static void
147 data_out_g_string (GString *string, const struct variable *v,
148                    const struct ccase *cc)
149 {
150   char *buf ;
151
152   const struct fmt_spec *fs = var_get_print_format (v);
153   const union value *val = case_data (cc, v);
154   buf = xzalloc (fs->w);
155
156   data_out (val, fs, buf);
157
158   g_string_append_len (string, buf, fs->w);
159
160   g_free (buf);
161 }
162
163 static GString *
164 clip_to_text (void)
165 {
166   casenumber r;
167   GString *string;
168
169   const size_t val_cnt = casereader_get_value_cnt (clip_datasheet);
170   const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
171   const size_t var_cnt = dict_get_var_cnt (clip_dict);
172
173   string = g_string_sized_new (10 * val_cnt * case_cnt);
174
175   for (r = 0 ; r < case_cnt ; ++r )
176     {
177       int c;
178       struct ccase cc;
179       if ( !  casereader_peek (clip_datasheet, r, &cc))
180         {
181           g_warning ("Clipboard seems to have inexplicably shrunk");
182           break;
183         }
184
185       for (c = 0 ; c < var_cnt ; ++c)
186         {
187           const struct variable *v = dict_get_var (clip_dict, c);
188           data_out_g_string (string, v, &cc);
189           if ( c < val_cnt - 1 )
190             g_string_append (string, "\t");
191         }
192
193       if ( r < case_cnt)
194         g_string_append (string, "\n");
195
196       case_destroy (&cc);
197     }
198
199   return string;
200 }
201
202
203 static GString *
204 clip_to_html (void)
205 {
206   casenumber r;
207   GString *string;
208
209   const size_t val_cnt = casereader_get_value_cnt (clip_datasheet);
210   const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
211   const size_t var_cnt = dict_get_var_cnt (clip_dict);
212
213
214   /* Guestimate the size needed */
215   string = g_string_sized_new (20 * val_cnt * case_cnt);
216
217   g_string_append (string, "<table>\n");
218   for (r = 0 ; r < case_cnt ; ++r )
219     {
220       int c;
221       struct ccase cc;
222       if ( !  casereader_peek (clip_datasheet, r, &cc))
223         {
224           g_warning ("Clipboard seems to have inexplicably shrunk");
225           break;
226         }
227       g_string_append (string, "<tr>\n");
228
229       for (c = 0 ; c < var_cnt ; ++c)
230         {
231           const struct variable *v = dict_get_var (clip_dict, c);
232           g_string_append (string, "<td>");
233           data_out_g_string (string, v, &cc);
234           g_string_append (string, "</td>\n");
235         }
236
237       g_string_append (string, "</tr>\n");
238
239       case_destroy (&cc);
240     }
241   g_string_append (string, "</table>\n");
242
243   return string;
244 }
245
246
247
248 static void
249 clipboard_get_cb (GtkClipboard     *clipboard,
250                   GtkSelectionData *selection_data,
251                   guint             info,
252                   gpointer          data)
253 {
254   GString *string = NULL;
255
256   switch (info)
257     {
258     case SELECT_FMT_TEXT:
259       string = clip_to_text ();
260       break;
261     case SELECT_FMT_HTML:
262       string = clip_to_html ();
263       break;
264     default:
265       g_assert_not_reached ();
266     }
267
268   gtk_selection_data_set (selection_data, selection_data->target,
269                           8,
270                           (const guchar *) string->str, string->len);
271
272   g_string_free (string, TRUE);
273 }
274
275 static void
276 clipboard_clear_cb (GtkClipboard *clipboard,
277                     gpointer data)
278 {
279   dict_destroy (clip_dict);
280   clip_dict = NULL;
281
282   casereader_destroy (clip_datasheet);
283   clip_datasheet = NULL;
284 }
285
286
287
288 static void
289 data_sheet_update_clipboard (GtkSheet *sheet)
290 {
291   static const GtkTargetEntry targets[] = {
292     { "UTF8_STRING",   0, SELECT_FMT_TEXT },
293     { "STRING",        0, SELECT_FMT_TEXT },
294     { "TEXT",          0, SELECT_FMT_TEXT },
295     { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
296     { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
297     { "text/plain",    0, SELECT_FMT_TEXT },
298     { "text/html",     0, SELECT_FMT_HTML }
299   };
300
301   GtkClipboard *clipboard =
302     gtk_widget_get_clipboard (GTK_WIDGET (sheet),
303                               GDK_SELECTION_CLIPBOARD);
304
305   if (!gtk_clipboard_set_with_owner (clipboard, targets,
306                                      G_N_ELEMENTS (targets),
307                                      clipboard_get_cb, clipboard_clear_cb,
308                                      G_OBJECT (sheet)))
309     clipboard_clear_cb (clipboard, sheet);
310 }
311
312
313
314 /* A callback for when  clipboard contents have been received */
315 void
316 data_sheet_contents_received_callback (GtkClipboard *clipboard,
317                                       GtkSelectionData *sd,
318                                       gpointer data)
319 {
320   struct data_editor *de = data;
321
322   gint count = 0;
323   gint row, column;
324   gint next_row, next_column;
325   gint first_column;
326   char *c;
327   GtkSheet *data_sheet ;
328   PsppireDataStore *data_store;
329
330   if ( sd->length < 0 )
331     return;
332
333   if ( sd->type != gdk_atom_intern ("UTF8_STRING", FALSE))
334     return;
335
336   c = (char *) sd->data;
337
338   /* Paste text to selected position */
339   data_sheet = GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
340   data_store = PSPPIRE_DATA_STORE (gtk_sheet_get_model (data_sheet));
341
342   gtk_sheet_get_active_cell (data_sheet, &row, &column);
343
344   g_return_if_fail (row >= 0);
345   g_return_if_fail (column >= 0);
346
347   first_column = column;
348   next_row = row;
349   next_column = column;
350   while (count < sd->length)
351     {
352       char *s = c;
353
354       row = next_row;
355       column = next_column;
356       while (*c != '\t' && *c != '\n' && count < sd->length)
357         {
358           c++;
359           count++;
360         }
361       if ( *c == '\t')
362         {
363           next_row = row ;
364           next_column = column + 1;
365         }
366       else if ( *c == '\n')
367         {
368           next_row = row + 1;
369           next_column = first_column;
370         }
371       *c++ = '\0';
372       count++;
373
374       /* Append some new cases if pasting beyond the last row */
375       if ( row >= psppire_data_store_get_case_count (data_store))
376         psppire_data_store_insert_new_case (data_store, row);
377
378       gtk_sheet_set_cell_text (data_sheet, row, column, s);
379     }
380 }