9febcfabd5ba9cf8166d44276207754a27a6fde7
[pspp] / src / output / output-item.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2011 Free Software Foundation, Inc.
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 "output/output-item.h"
20
21 #include <assert.h>
22 #include <stdlib.h>
23
24 #include "libpspp/assertion.h"
25 #include "libpspp/cast.h"
26 #include "libpspp/message.h"
27 #include "libpspp/str.h"
28 #include "output/chart.h"
29 #include "output/driver.h"
30 #include "output/page-setup.h"
31 #include "output/pivot-table.h"
32
33 #include "gl/xalloc.h"
34
35 #include "gettext.h"
36 #define _(msgid) gettext (msgid)
37 #define N_(msgid) msgid
38 \f
39 #define OUTPUT_ITEM_INITIALIZER(TYPE) .type = TYPE, .ref_cnt = 1
40
41 /* Increases ITEM's reference count, indicating that it has an additional
42    owner.  An output item that is shared among multiple owners must not be
43    modified. */
44 struct output_item *
45 output_item_ref (const struct output_item *item_)
46 {
47   struct output_item *item = CONST_CAST (struct output_item *, item_);
48   item->ref_cnt++;
49   return item;
50 }
51
52 /* Decreases ITEM's reference count, indicating that it has one fewer owner.
53    If ITEM no longer has any owners, it is freed. */
54 void
55 output_item_unref (struct output_item *item)
56 {
57   if (item != NULL)
58     {
59       assert (item->ref_cnt > 0);
60       if (--item->ref_cnt == 0)
61         {
62           switch (item->type)
63             {
64             case OUTPUT_ITEM_CHART:
65               chart_unref (item->chart);
66               break;
67
68             case OUTPUT_ITEM_GROUP_OPEN:
69               break;
70
71             case OUTPUT_ITEM_GROUP_CLOSE:
72               break;
73
74             case OUTPUT_ITEM_IMAGE:
75               cairo_surface_destroy (item->image);
76               break;
77
78             case OUTPUT_ITEM_MESSAGE:
79               msg_destroy (item->message);
80               break;
81
82             case OUTPUT_ITEM_PAGE_BREAK:
83               break;
84
85             case OUTPUT_ITEM_PAGE_SETUP:
86               page_setup_destroy (item->page_setup);
87               break;
88
89             case OUTPUT_ITEM_TABLE:
90               pivot_table_unref (item->table);
91               break;
92
93             case OUTPUT_ITEM_TEXT:
94               pivot_value_destroy (item->text.content);
95               break;
96             }
97
98           free (item->label);
99           free (item->command_name);
100           free (item->cached_label);
101           free (item);
102         }
103     }
104 }
105
106 /* Returns true if ITEM has more than one owner.  An output item that is shared
107    among multiple owners must not be modified. */
108 bool
109 output_item_is_shared (const struct output_item *item)
110 {
111   return item->ref_cnt > 1;
112 }
113
114 struct output_item *
115 output_item_unshare (struct output_item *old)
116 {
117   assert (old->ref_cnt > 0);
118   if (!output_item_is_shared (old))
119     return old;
120   output_item_unref (old);
121
122   struct output_item *new = xmalloc (sizeof *new);
123   *new = (struct output_item) {
124     .ref_cnt = 1,
125     .label = xstrdup_if_nonnull (old->label),
126     .command_name = xstrdup_if_nonnull (old->command_name),
127     .type = old->type,
128   };
129   switch (old->type)
130     {
131     case OUTPUT_ITEM_CHART:
132       new->chart = chart_ref (old->chart);
133       break;
134
135     case OUTPUT_ITEM_GROUP_OPEN:
136       break;
137
138     case OUTPUT_ITEM_GROUP_CLOSE:
139       break;
140
141     case OUTPUT_ITEM_IMAGE:
142       new->image = cairo_surface_reference (old->image);
143       break;
144
145     case OUTPUT_ITEM_MESSAGE:
146       new->message = msg_dup (old->message);
147       break;
148
149     case OUTPUT_ITEM_PAGE_BREAK:
150       break;
151
152     case OUTPUT_ITEM_PAGE_SETUP:
153       new->page_setup = page_setup_clone (old->page_setup);
154       break;
155
156     case OUTPUT_ITEM_TABLE:
157       new->table = pivot_table_ref (old->table);
158       break;
159
160     case OUTPUT_ITEM_TEXT:
161       new->text.subtype = old->text.subtype;
162       new->text.content = pivot_value_clone (old->text.content);
163       break;
164     }
165   return new;
166 }
167
168 void
169 output_item_submit (struct output_item *item)
170 {
171   output_submit (item);
172 }
173
174 /* Returns the label for ITEM, which the caller must not modify or free. */
175 const char *
176 output_item_get_label (const struct output_item *item)
177 {
178   if (item->label)
179     return item->label;
180
181   switch (item->type)
182     {
183     case OUTPUT_ITEM_CHART:
184       return item->chart->title ? item->chart->title : _("Chart");
185
186     case OUTPUT_ITEM_GROUP_OPEN:
187       return item->command_name ? item->command_name : _("Group");
188
189     case OUTPUT_ITEM_GROUP_CLOSE:
190       /* Not marked for translation: user should never see it. */
191       return "Group Close";
192
193     case OUTPUT_ITEM_IMAGE:
194       return "Image";
195
196     case OUTPUT_ITEM_MESSAGE:
197       return (item->message->severity == MSG_S_ERROR ? _("Error")
198               : item->message->severity == MSG_S_WARNING ? _("Warning")
199               : _("Note"));
200
201     case OUTPUT_ITEM_PAGE_BREAK:
202       return _("Page Break");
203
204     case OUTPUT_ITEM_PAGE_SETUP:
205       /* Not marked for translation: user should never see it. */
206       return "Page Setup";
207
208     case OUTPUT_ITEM_TABLE:
209       if (!item->cached_label)
210         {
211           if (!item->table->title)
212             return _("Table");
213
214           struct output_item *item_rw = CONST_CAST (struct output_item *, item);
215           item_rw->cached_label = pivot_value_to_string (item->table->title,
216                                                          item->table);
217         }
218       return item->cached_label;
219
220     case OUTPUT_ITEM_TEXT:
221       return text_item_subtype_to_string (item->text.subtype);
222     }
223
224   NOT_REACHED ();
225 }
226
227 /* Sets the label for ITEM to LABEL.  The caller retains ownership of LABEL.
228    If LABEL is nonnull, it overrides any previously set label and the default
229    label.  If LABEL is null, ITEM will now use its default label.
230
231    ITEM must not be shared. */
232 void
233 output_item_set_label (struct output_item *item, const char *label)
234 {
235   output_item_set_label_nocopy (item, xstrdup_if_nonnull (label));
236 }
237
238 /* Sets the label for ITEM to LABEL, transferring ownership of LABEL to ITEM.
239    If LABEL is nonnull, it overrides any previously set label and the default
240    label.  If LABEL is null, ITEM will now use its default label.
241
242    ITEM must not be shared. */
243 void
244 output_item_set_label_nocopy (struct output_item *item, char *label)
245 {
246   assert (!output_item_is_shared (item));
247   free (item->label);
248   item->label = label;
249 }
250 \f
251 struct output_item *
252 chart_item_create (struct chart *chart)
253 {
254   struct output_item *item = xmalloc (sizeof *item);
255   *item = (struct output_item) {
256     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_CHART),
257     .chart = chart,
258   };
259   return item;
260 }
261 \f
262 struct output_item *
263 group_open_item_create (const char *command_name, const char *label)
264 {
265   return group_open_item_create_nocopy (
266     xstrdup_if_nonnull (command_name),
267     xstrdup_if_nonnull (label));
268 }
269
270 struct output_item *
271 group_open_item_create_nocopy (char *command_name, char *label)
272 {
273   struct output_item *item = xmalloc (sizeof *item);
274   *item = (struct output_item) {
275     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_GROUP_OPEN),
276     .label = label,
277     .command_name = command_name,
278   };
279   return item;
280 }
281
282 struct output_item *
283 group_close_item_create (void)
284 {
285   struct output_item *item = xmalloc (sizeof *item);
286   *item = (struct output_item) {
287     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_GROUP_CLOSE),
288   };
289   return item;
290 }
291 \f
292 /* Creates and returns a new output item containing IMAGE.  Takes ownership of
293    IMAGE. */
294 struct output_item *
295 image_item_create (cairo_surface_t *image)
296 {
297   struct output_item *item = xmalloc (sizeof *item);
298   *item = (struct output_item) {
299     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_IMAGE),
300     .image = image,
301   };
302   return item;
303 }
304 \f
305 struct output_item *
306 message_item_create (const struct msg *msg)
307 {
308   struct output_item *item = xmalloc (sizeof *item);
309   *item = (struct output_item) {
310     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_MESSAGE),
311     .message = msg_dup (msg),
312   };
313   return item;
314 }
315
316 const struct msg *
317 message_item_get_msg (const struct output_item *item)
318 {
319   assert (item->type == OUTPUT_ITEM_MESSAGE);
320   return item->message;
321 }
322
323 struct output_item *
324 message_item_to_text_item (struct output_item *message_item)
325 {
326   assert (message_item->type == OUTPUT_ITEM_MESSAGE);
327   struct output_item *text_item = text_item_create_nocopy (
328     TEXT_ITEM_LOG,
329     msg_to_string (message_item->message),
330     xstrdup (output_item_get_label (message_item)));
331   output_item_unref (message_item);
332   return text_item;
333 }
334 \f
335 struct output_item *
336 page_break_item_create (void)
337 {
338   struct output_item *item = xmalloc (sizeof *item);
339   *item = (struct output_item) {
340     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_PAGE_BREAK),
341   };
342   return item;
343 }
344 \f
345 struct output_item *
346 page_setup_item_create (const struct page_setup *ps)
347 {
348   struct output_item *item = xmalloc (sizeof *item);
349   *item = (struct output_item) {
350     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_PAGE_SETUP),
351     .page_setup = page_setup_clone (ps),
352   };
353   return item;
354 }
355 \f
356 /* Returns a new output_item for rendering TABLE.  Takes ownership of
357    TABLE. */
358 struct output_item *
359 table_item_create (struct pivot_table *table)
360 {
361   pivot_table_assign_label_depth (table);
362
363   struct output_item *item = xmalloc (sizeof *item);
364   *item = (struct output_item) {
365     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_TABLE),
366     .command_name = xstrdup_if_nonnull (table->command_c),
367     .table = table,
368   };
369   return item;
370 }
371 \f
372 /* Creates and returns a new text item containing TEXT and the specified
373    SUBTYPE and LABEL.  The new text item takes ownership of TEXT and LABEL.  If
374    LABEL is NULL, uses the default label for SUBTYPE. */
375 struct output_item *
376 text_item_create_nocopy (enum text_item_subtype subtype,
377                          char *text, char *label)
378 {
379   return text_item_create_value (subtype,
380                                  pivot_value_new_user_text_nocopy (text),
381                                  label);
382 }
383
384 /* Creates and returns a new text item containing a copy of TEXT and the
385    specified SUBTYPE and LABEL.  The caller retains ownership of TEXT and
386    LABEL.  If LABEL is null, uses a default label for SUBTYPE. */
387 struct output_item *
388 text_item_create (enum text_item_subtype subtype, const char *text,
389                   const char *label)
390 {
391   return text_item_create_nocopy (subtype, xstrdup (text),
392                                   xstrdup_if_nonnull (label));
393 }
394
395 /* Creates and returns a new text item containing VALUE, SUBTYPE, and LABEL.
396    Takes ownership of VALUE and LABEL.  If LABEL is null, uses a default label
397    for SUBTYPE. */
398 struct output_item *
399 text_item_create_value (enum text_item_subtype subtype,
400                         struct pivot_value *value, char *label)
401 {
402   if (subtype == TEXT_ITEM_SYNTAX || subtype == TEXT_ITEM_LOG)
403     {
404       if (!value->font_style)
405         {
406           value->font_style = xmalloc (sizeof *value->font_style);
407           *value->font_style = (struct font_style) FONT_STYLE_INITIALIZER;
408         }
409
410       free (value->font_style->typeface);
411       value->font_style->typeface = xstrdup ("Monospaced");
412     }
413
414   struct output_item *item = xzalloc (sizeof *item);
415   *item = (struct output_item) {
416     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_TEXT),
417     .command_name = xstrdup_if_nonnull (output_get_command_name ()),
418     .label = label,
419     .text = { .subtype = subtype, .content = value },
420   };
421   return item;
422 }
423
424 /* Returns ITEM's subtype. */
425 enum text_item_subtype
426 text_item_get_subtype (const struct output_item *item)
427 {
428   assert (item->type == OUTPUT_ITEM_TEXT);
429   return item->text.subtype;
430 }
431
432 /* Returns ITEM's text, which the caller must eventually free. */
433 char *
434 text_item_get_plain_text (const struct output_item *item)
435 {
436   assert (item->type == OUTPUT_ITEM_TEXT);
437   return pivot_value_to_string_defaults (item->text.content);
438 }
439
440 static bool
441 nullable_font_style_equal (const struct font_style *a,
442                            const struct font_style *b)
443 {
444   return a && b ? font_style_equal (a, b) : !a && !b;
445 }
446
447 /* Attempts to append the text in SRC to DST.  If successful, returns true,
448    otherwise false.
449
450    Only TEXT_ITEM_SYNTAX and TEXT_ITEM_LOG items can be combined, and not with
451    each other.
452
453    DST must not be shared. */
454 bool
455 text_item_append (struct output_item *dst, const struct output_item *src)
456 {
457   assert (dst->type == OUTPUT_ITEM_TEXT);
458   assert (src->type == OUTPUT_ITEM_TEXT);
459   assert (!output_item_is_shared (dst));
460
461   enum text_item_subtype ds = dst->text.subtype;
462   enum text_item_subtype ss = src->text.subtype;
463
464   struct pivot_value *dc = dst->text.content;
465   const struct pivot_value *sc = src->text.content;
466
467   if (ds != ss
468       || (ds != TEXT_ITEM_SYNTAX && ds != TEXT_ITEM_LOG)
469       || strcmp (output_item_get_label (dst), output_item_get_label (src))
470       || !nullable_font_style_equal (dc->font_style, sc->font_style)
471       || (dc->font_style && dc->font_style->markup)
472       || sc->type != PIVOT_VALUE_TEXT
473       || dc->type != PIVOT_VALUE_TEXT)
474     return false;
475   else
476     {
477       /* Calculate new text. */
478       char *new_text = xasprintf ("%s\n%s", dc->text.local, sc->text.local);
479
480       /* Free the old text. */
481       free (dc->text.local);
482       if (dc->text.c != dc->text.local)
483         free (dc->text.c);
484       if (dc->text.id != dc->text.local && dc->text.id != dc->text.c)
485         free (dc->text.id);
486
487       /* Put in new text. */
488       dc->text.local = new_text;
489       dc->text.c = new_text;
490       dc->text.id = new_text;
491
492       return true;
493     }
494 }
495
496 static const struct pivot_table_look *
497 text_item_table_look (void)
498 {
499   static struct pivot_table_look *look;
500   if (!look)
501     {
502       look = pivot_table_look_new_builtin_default ();
503
504       for (int a = 0; a < PIVOT_N_AREAS; a++)
505         memset (look->areas[a].cell_style.margin, 0,
506                 sizeof look->areas[a].cell_style.margin);
507       for (int b = 0; b < PIVOT_N_BORDERS; b++)
508         look->borders[b].stroke = TABLE_STROKE_NONE;
509     }
510   return look;
511 }
512
513 struct output_item *
514 text_item_to_table_item (struct output_item *text_item)
515 {
516   assert (text_item->type == OUTPUT_ITEM_TEXT);
517
518   /* Create a new table whose contents come from TEXT_ITEM. */
519   struct pivot_table *table = pivot_table_create__ (NULL, "Text");
520   pivot_table_set_look (table, text_item_table_look ());
521
522   struct pivot_dimension *d = pivot_dimension_create (
523     table, PIVOT_AXIS_ROW, N_("Text"));
524   d->hide_all_labels = true;
525   pivot_category_create_leaf (d->root, pivot_value_new_text ("null"));
526
527   pivot_table_put1 (table, 0, pivot_value_clone (text_item->text.content));
528
529   /* Free TEXT_ITEM. */
530   output_item_unref (text_item);
531
532   /* Return a new output item. */
533   return table_item_create (table);
534 }
535
536 const char *
537 text_item_subtype_to_string (enum text_item_subtype subtype)
538 {
539   switch (subtype)
540     {
541     case TEXT_ITEM_PAGE_TITLE:
542       return _("Page Title");
543
544     case TEXT_ITEM_TITLE:
545       return _("Title");
546
547     case TEXT_ITEM_SYNTAX:
548     case TEXT_ITEM_LOG:
549       return _("Log");
550
551     default:
552       return _("Text");
553     }
554 }
555