1 /* PSPP - computes sample statistics.
2 Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3 Written by Ben Pfaff <blp@gnu.org>.
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License as
7 published by the Free Software Foundation; either version 2 of the
8 License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
28 #include "dictionary.h"
43 #define _(msgid) gettext (msgid)
47 #include "debug-print.h"
51 *variables=varlist("PV_NO_SCRATCH");
52 cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
53 format=numbering:numbered/!unnumbered,
55 weight:weight/!noweight.
60 /* Layout for one output driver. */
63 int type; /* 0=Values and labels fit across the page. */
64 size_t n_vertical; /* Number of labels to list vertically. */
65 size_t header_rows; /* Number of header rows. */
66 char **header; /* The header itself. */
70 static struct cmd_list cmd;
72 /* Current case number. */
76 static char *line_buf;
78 /* TTY-style output functions. */
79 static unsigned n_lines_remaining (struct outp_driver *d);
80 static unsigned n_chars_width (struct outp_driver *d);
81 static void write_line (struct outp_driver *d, char *s);
83 /* Other functions. */
84 static int list_cases (struct ccase *, void *);
85 static void determine_layout (void);
86 static void clean_up (void);
87 static void write_header (struct outp_driver *);
88 static void write_all_headers (void *);
90 /* Returns the number of text lines that can fit on the remainder of
92 static inline unsigned
93 n_lines_remaining (struct outp_driver *d)
97 diff = d->length - d->cp_y;
98 return (diff > 0) ? (diff / d->font_height) : 0;
101 /* Returns the number of fixed-width character that can fit across the
103 static inline unsigned
104 n_chars_width (struct outp_driver *d)
106 return d->width / d->fixed_width;
109 /* Writes the line S at the current position and advances to the next
112 write_line (struct outp_driver *d, char *s)
114 struct outp_text text;
116 assert (d->cp_y + d->font_height <= d->length);
117 text.options = OUTP_T_JUST_LEFT;
118 ls_init (&text.s, s, strlen (s));
121 d->class->text_draw (d, &text);
123 d->cp_y += d->font_height;
126 /* Parses and executes the LIST procedure. */
130 struct variable casenum_var;
132 if (!parse_list (&cmd))
135 /* Fill in defaults. */
136 if (cmd.step == NOT_LONG)
138 if (cmd.first == NOT_LONG)
140 if (cmd.last == NOT_LONG)
142 if (!cmd.sbc_variables)
143 dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
144 (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
145 if (cmd.n_variables == 0)
147 msg (SE, _("No variables specified."));
151 /* Verify arguments. */
152 if (cmd.first > cmd.last)
155 msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
156 "specified. The values will be swapped."), cmd.first, cmd.last);
158 cmd.first = cmd.last;
163 msg (SW, _("The first case (%ld) to list is less than 1. The value is "
164 "being reset to 1."), cmd.first);
169 msg (SW, _("The last case (%ld) to list is less than 1. The value is "
170 "being reset to 1."), cmd.last);
175 msg (SW, _("The step value %ld is less than 1. The value is being "
176 "reset to 1."), cmd.step);
180 /* Weighting variable. */
181 if (cmd.weight == LST_WEIGHT)
183 if (dict_get_weight (default_dict) != NULL)
187 for (i = 0; i < cmd.n_variables; i++)
188 if (cmd.v_variables[i] == dict_get_weight (default_dict))
190 if (i >= cmd.n_variables)
192 /* Add the weight variable to the end of the variable list. */
194 cmd.v_variables = xrealloc (cmd.v_variables,
196 * sizeof *cmd.v_variables));
197 cmd.v_variables[cmd.n_variables - 1]
198 = dict_get_weight (default_dict);
202 msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
206 if (cmd.numbering == LST_NUMBERED)
208 /* Initialize the case-number variable. */
209 strcpy (casenum_var.name, "Case#");
210 casenum_var.type = NUMERIC;
212 casenum_var.print = make_output_format (FMT_F,
213 (cmd.last == LONG_MAX
214 ? 5 : intlog10 (cmd.last)), 0);
216 /* Add the weight variable at the beginning of the variable list. */
218 cmd.v_variables = xrealloc (cmd.v_variables,
219 cmd.n_variables * sizeof *cmd.v_variables);
220 memmove (&cmd.v_variables[1], &cmd.v_variables[0],
221 (cmd.n_variables - 1) * sizeof *cmd.v_variables);
222 cmd.v_variables[0] = &casenum_var;
228 procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
236 /* Writes headers to all devices. This is done at the beginning of
237 each SPLIT FILE group. */
239 write_all_headers (void *aux UNUSED)
241 struct outp_driver *d;
243 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
245 if (!d->class->special)
247 d->cp_y += d->font_height; /* Blank line. */
250 else if (d->class == &html_class)
252 struct html_driver_ext *x = d->ext;
254 assert (d->driver_open);
255 if (x->sequence_no == 0 && !d->class->open_page (d))
257 msg (ME, _("Cannot open first page on HTML device %s."),
262 fputs ("<TABLE BORDER=1>\n <TR>\n", x->file.file);
267 for (i = 0; i < cmd.n_variables; i++)
268 fprintf (x->file.file, " <TH><I><B>%s</B></I></TH>\n",
269 cmd.v_variables[i]->name);
272 fputs (" <TR>\n", x->file.file);
274 else if (d->class == &devind_class)
283 /* Writes the headers. Some of them might be vertical; most are
284 probably horizontal. */
286 write_header (struct outp_driver *d)
288 struct list_ext *prc = d->prc;
290 if (!prc->header_rows)
293 if (n_lines_remaining (d) < prc->header_rows + 1)
296 assert (n_lines_remaining (d) >= prc->header_rows + 1);
299 /* Design the header. */
305 /* Allocate, initialize header. */
306 prc->header = xmalloc (sizeof (char *) * prc->header_rows);
308 int w = n_chars_width (d);
309 for (i = 0; i < prc->header_rows; i++)
311 prc->header[i] = xmalloc (w + 1);
312 memset (prc->header[i], ' ', w);
316 /* Put in vertical names. */
317 for (i = x = 0; i < prc->n_vertical; i++)
319 struct variable *v = cmd.v_variables[i];
322 memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
324 for (j = 0; j < strlen (v->name); j++)
325 prc->header[strlen (v->name) - j - 1][x] = v->name[j];
329 /* Put in horizontal names. */
330 for (; i < cmd.n_variables; i++)
332 struct variable *v = cmd.v_variables[i];
334 memset (&prc->header[prc->header_rows - 1][x], '-',
335 max (v->print.w, (int) strlen (v->name)));
336 if ((int) strlen (v->name) < v->print.w)
337 x += v->print.w - strlen (v->name);
338 memcpy (&prc->header[0][x], v->name, strlen (v->name));
339 x += strlen (v->name) + 1;
342 /* Add null bytes. */
343 for (i = 0; i < prc->header_rows; i++)
345 for (x = n_chars_width (d); x >= 1; x--)
346 if (prc->header[i][x - 1] != ' ')
348 prc->header[i][x] = 0;
355 /* Write out the header, in back-to-front order except for the last line. */
356 if (prc->header_rows >= 2)
360 for (i = prc->header_rows - 1; i-- != 0; )
361 write_line (d, prc->header[i]);
363 write_line (d, prc->header[prc->header_rows - 1]);
367 /* Frees up all the memory we've allocated. */
371 struct outp_driver *d;
373 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
374 if (d->class->special == 0)
376 struct list_ext *prc = d->prc;
381 for (i = 0; i < prc->header_rows; i++)
382 free (prc->header[i]);
387 d->class->text_set_font_by_name (d, "PROP");
389 else if (d->class == &html_class)
391 if (d->driver_open && d->page_open)
393 struct html_driver_ext *x = d->ext;
395 fputs ("</TABLE>\n", x->file.file);
398 else if (d->class == &devind_class)
405 free (cmd.v_variables);
408 /* Writes string STRING at the current position. If the text would
409 fall off the side of the page, then advance to the next line,
410 indenting by amount INDENT. */
412 write_varname (struct outp_driver *d, char *string, int indent)
414 struct outp_text text;
416 text.options = OUTP_T_JUST_LEFT;
417 ls_init (&text.s, string, strlen (string));
418 d->class->text_metrics (d, &text);
420 if (d->cp_x + text.h > d->width)
422 d->cp_y += d->font_height;
423 if (d->cp_y + d->font_height > d->length)
430 d->class->text_draw (d, &text);
434 /* When we can't fit all the values across the page, we write out all
435 the variable names just once. This is where we do it. */
437 write_fallback_headers (struct outp_driver *d)
439 const int max_width = n_chars_width(d) - 10;
445 const char *Line = _("Line");
446 char *leader = local_alloc (strlen (Line) + INT_DIGITS + 1 + 1);
448 while (index < cmd.n_variables)
450 struct outp_text text;
452 /* Ensure that there is enough room for a line of text. */
453 if (d->cp_y + d->font_height > d->length)
456 /* The leader is a string like `Line 1: '. Write the leader. */
457 sprintf(leader, "%s %d:", Line, ++line_number);
458 text.options = OUTP_T_JUST_LEFT;
459 ls_init (&text.s, leader, strlen (leader));
462 d->class->text_draw (d, &text);
472 int var_width = cmd.v_variables[index]->print.w;
473 if (width + var_width > max_width && width != 0)
477 d->cp_y += d->font_height;
485 sprintf (varname, " %s", cmd.v_variables[index]->name);
486 write_varname (d, varname, text.h);
489 while (++index < cmd.n_variables);
493 d->cp_y += d->font_height;
498 /* There are three possible layouts for the LIST procedure:
500 1. If the values and their variables' name fit across the page,
501 then they are listed across the page in that way.
503 2. If the values can fit across the page, but not the variable
504 names, then as many variable names as necessary are printed
505 vertically to compensate.
507 3. If not even the values can fit across the page, the variable
508 names are listed just once, at the beginning, in a compact format,
509 and the values are listed with a variable name label at the
510 beginning of each line for easier reference.
512 This is complicated by the fact that we have to do all this for
513 every output driver, not just once. */
515 determine_layout (void)
517 struct outp_driver *d;
519 /* This is the largest page width of any driver, so we can tell what
520 size buffer to allocate. */
521 int largest_page_width = 0;
523 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
525 size_t column; /* Current column. */
526 int width; /* Accumulated width. */
527 int height; /* Height of vertical names. */
528 int max_width; /* Page width. */
530 struct list_ext *prc;
532 if (d->class == &html_class)
534 else if (d->class == &devind_class)
537 tab_output_text (TAT_NONE, "(devind not supported on LIST yet)");
541 assert (d->class->special == 0);
544 d->class->open_page (d);
546 max_width = n_chars_width (d);
547 largest_page_width = max (largest_page_width, max_width);
549 prc = d->prc = xmalloc (sizeof *prc);
555 for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
557 struct variable *v = cmd.v_variables[column];
558 width += max (v->print.w, (int) strlen (v->name));
560 if (width <= max_width)
562 prc->header_rows = 2;
563 d->class->text_set_font_by_name (d, "FIXED");
568 for (width = cmd.n_variables - 1, height = 0, column = 0;
569 column < cmd.n_variables && width <= max_width;
572 struct variable *v = cmd.v_variables[column];
574 if (strlen (v->name) > height)
575 height = strlen (v->name);
578 /* If it fit then we need to determine how many labels can be
579 written horizontally. */
580 if (width <= max_width && height <= SHORT_NAME_LEN)
583 prc->n_vertical = SIZE_MAX;
585 for (column = cmd.n_variables; column-- != 0; )
587 struct variable *v = cmd.v_variables[column];
588 int trial_width = (width - v->print.w
589 + max (v->print.w, (int) strlen (v->name)));
591 if (trial_width > max_width)
593 prc->n_vertical = column + 1;
598 assert (prc->n_vertical != SIZE_MAX);
600 prc->n_vertical = cmd.n_variables;
601 /* Finally determine the length of the headers. */
602 for (prc->header_rows = 0, column = 0;
603 column < prc->n_vertical;
605 prc->header_rows = max (prc->header_rows,
606 strlen (cmd.v_variables[column]->name));
609 d->class->text_set_font_by_name (d, "FIXED");
613 /* Otherwise use the ugly fallback listing format. */
615 prc->header_rows = 0;
617 d->cp_y += d->font_height;
618 write_fallback_headers (d);
619 d->cp_y += d->font_height;
620 d->class->text_set_font_by_name (d, "FIXED");
623 line_buf = xmalloc (max (1022, largest_page_width) + 2);
627 list_cases (struct ccase *c, void *aux UNUSED)
629 struct outp_driver *d;
632 if (case_idx < cmd.first || case_idx > cmd.last
633 || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
636 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
637 if (d->class->special == 0)
639 const struct list_ext *prc = d->prc;
640 const int max_width = n_chars_width (d);
644 if (!prc->header_rows)
645 x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
647 for (column = 0; column < cmd.n_variables; column++)
649 struct variable *v = cmd.v_variables[column];
652 if (prc->type == 0 && column >= prc->n_vertical)
653 width = max ((int) strlen (v->name), v->print.w);
657 if (width + x > max_width && x != 0)
659 if (!n_lines_remaining (d))
666 write_line (d, line_buf);
669 if (!prc->header_rows)
670 x = nsprintf (line_buf, "%8s: ", v->name);
673 if (width > v->print.w)
675 memset(&line_buf[x], ' ', width - v->print.w);
676 x += width - v->print.w;
679 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
680 data_out (&line_buf[x], &v->print, case_data (c, v->fv));
683 union value case_idx_value;
684 case_idx_value.f = case_idx;
685 data_out (&line_buf[x], &v->print, &case_idx_value);
692 if (!n_lines_remaining (d))
699 write_line (d, line_buf);
701 else if (d->class == &html_class)
703 struct html_driver_ext *x = d->ext;
706 fputs (" <TR>\n", x->file.file);
708 for (column = 0; column < cmd.n_variables; column++)
710 struct variable *v = cmd.v_variables[column];
713 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
714 data_out (buf, &v->print, case_data (c, v->fv));
717 union value case_idx_value;
718 case_idx_value.f = case_idx;
719 data_out (buf, &v->print, &case_idx_value);
723 fprintf (x->file.file, " <TD ALIGN=RIGHT>%s</TD>\n",
724 &buf[strspn (buf, " ")]);
727 fputs (" </TR>\n", x->file.file);
729 else if (d->class == &devind_class)