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