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"
42 #include "debug-print.h"
46 *variables=varlist("PV_NO_SCRATCH");
47 cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
48 format=numbering:numbered/!unnumbered,
50 weight:weight/!noweight.
55 /* Layout for one output driver. */
58 int type; /* 0=Values and labels fit across the page. */
59 int n_vertical; /* Number of labels to list vertically. */
60 int header_rows; /* Number of header rows. */
61 char **header; /* The header itself. */
65 static struct cmd_list cmd;
67 /* Current case number. */
71 static char *line_buf;
73 /* TTY-style output functions. */
74 static int n_lines_remaining (struct outp_driver *d);
75 static int n_chars_width (struct outp_driver *d);
76 static void write_line (struct outp_driver *d, char *s);
78 /* Other functions. */
79 static int list_cases (struct ccase *, void *);
80 static void determine_layout (void);
81 static void clean_up (void);
82 static void write_header (struct outp_driver *);
83 static void write_all_headers (void *);
85 /* Returns the number of text lines that can fit on the remainder of
88 n_lines_remaining (struct outp_driver *d)
92 diff = d->length - d->cp_y;
93 return (diff > 0) ? (diff / d->font_height) : 0;
96 /* Returns the number of fixed-width character that can fit across the
99 n_chars_width (struct outp_driver *d)
101 return d->width / d->fixed_width;
104 /* Writes the line S at the current position and advances to the next
107 write_line (struct outp_driver *d, char *s)
109 struct outp_text text;
111 assert (d->cp_y + d->font_height <= d->length);
112 text.options = OUTP_T_JUST_LEFT;
113 ls_init (&text.s, s, strlen (s));
116 d->class->text_draw (d, &text);
118 d->cp_y += d->font_height;
121 /* Parses and executes the LIST procedure. */
125 struct variable casenum_var;
127 if (!parse_list (&cmd))
130 /* Fill in defaults. */
131 if (cmd.step == NOT_LONG)
133 if (cmd.first == NOT_LONG)
135 if (cmd.last == NOT_LONG)
137 if (!cmd.sbc_variables)
138 dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
139 (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
140 if (cmd.n_variables == 0)
142 msg (SE, _("No variables specified."));
146 /* Verify arguments. */
147 if (cmd.first > cmd.last)
150 msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
151 "specified. The values will be swapped."), cmd.first, cmd.last);
153 cmd.first = cmd.last;
158 msg (SW, _("The first case (%ld) to list is less than 1. The value is "
159 "being reset to 1."), cmd.first);
164 msg (SW, _("The last case (%ld) to list is less than 1. The value is "
165 "being reset to 1."), cmd.last);
170 msg (SW, _("The step value %ld is less than 1. The value is being "
171 "reset to 1."), cmd.step);
175 /* Weighting variable. */
176 if (cmd.weight == LST_WEIGHT)
178 if (dict_get_weight (default_dict) != NULL)
182 for (i = 0; i < cmd.n_variables; i++)
183 if (cmd.v_variables[i] == dict_get_weight (default_dict))
185 if (i >= cmd.n_variables)
187 /* Add the weight variable to the end of the variable list. */
189 cmd.v_variables = xrealloc (cmd.v_variables,
191 * sizeof *cmd.v_variables));
192 cmd.v_variables[cmd.n_variables - 1]
193 = dict_get_weight (default_dict);
197 msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
201 if (cmd.numbering == LST_NUMBERED)
203 /* Initialize the case-number variable. */
204 strcpy (casenum_var.name, "Case#");
205 casenum_var.type = NUMERIC;
207 casenum_var.print = make_output_format (FMT_F,
208 (cmd.last == LONG_MAX
209 ? 5 : intlog10 (cmd.last)), 0);
211 /* Add the weight variable at the beginning of the variable list. */
213 cmd.v_variables = xrealloc (cmd.v_variables,
214 cmd.n_variables * sizeof *cmd.v_variables);
215 memmove (&cmd.v_variables[1], &cmd.v_variables[0],
216 (cmd.n_variables - 1) * sizeof *cmd.v_variables);
217 cmd.v_variables[0] = &casenum_var;
223 procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
231 /* Writes headers to all devices. This is done at the beginning of
232 each SPLIT FILE group. */
234 write_all_headers (void *aux UNUSED)
236 struct outp_driver *d;
238 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
240 if (!d->class->special)
242 d->cp_y += d->font_height; /* Blank line. */
245 else if (d->class == &html_class)
247 struct html_driver_ext *x = d->ext;
249 assert (d->driver_open);
250 if (x->sequence_no == 0 && !d->class->open_page (d))
252 msg (ME, _("Cannot open first page on HTML device %s."),
257 fputs ("<TABLE BORDER=1>\n <TR>\n", x->file.file);
262 for (i = 0; i < cmd.n_variables; i++)
263 fprintf (x->file.file, " <TH><I><B>%s</B></I></TH>\n",
264 cmd.v_variables[i]->name);
267 fputs (" <TR>\n", x->file.file);
269 else if (d->class == &devind_class)
278 /* Writes the headers. Some of them might be vertical; most are
279 probably horizontal. */
281 write_header (struct outp_driver *d)
283 struct list_ext *prc = d->prc;
285 if (!prc->header_rows)
288 if (n_lines_remaining (d) < prc->header_rows + 1)
291 assert (n_lines_remaining (d) >= prc->header_rows + 1);
294 /* Design the header. */
299 /* Allocate, initialize header. */
300 prc->header = xmalloc (sizeof (char *) * prc->header_rows);
302 int w = n_chars_width (d);
303 for (i = 0; i < prc->header_rows; i++)
305 prc->header[i] = xmalloc (w + 1);
306 memset (prc->header[i], ' ', w);
310 /* Put in vertical names. */
311 for (i = x = 0; i < prc->n_vertical; i++)
313 struct variable *v = cmd.v_variables[i];
316 memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
318 for (j = 0; j < (int) strlen (v->name); j++)
319 prc->header[strlen (v->name) - j - 1][x] = v->name[j];
323 /* Put in horizontal names. */
324 for (; i < cmd.n_variables; i++)
326 struct variable *v = cmd.v_variables[i];
328 memset (&prc->header[prc->header_rows - 1][x], '-',
329 max (v->print.w, (int) strlen (v->name)));
330 if ((int) strlen (v->name) < v->print.w)
331 x += v->print.w - strlen (v->name);
332 memcpy (&prc->header[0][x], v->name, strlen (v->name));
333 x += strlen (v->name) + 1;
336 /* Add null bytes. */
337 for (i = 0; i < prc->header_rows; i++)
339 for (x = n_chars_width (d); x >= 1; x--)
340 if (prc->header[i][x - 1] != ' ')
342 prc->header[i][x] = 0;
349 /* Write out the header, in back-to-front order except for the last line. */
353 for (i = prc->header_rows - 2; i >= 0; i--)
354 write_line (d, prc->header[i]);
355 write_line (d, prc->header[prc->header_rows - 1]);
360 /* Frees up all the memory we've allocated. */
364 struct outp_driver *d;
366 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
367 if (d->class->special == 0)
369 struct list_ext *prc = d->prc;
374 for (i = 0; i < prc->header_rows; i++)
375 free (prc->header[i]);
380 d->class->text_set_font_by_name (d, "PROP");
382 else if (d->class == &html_class)
384 if (d->driver_open && d->page_open)
386 struct html_driver_ext *x = d->ext;
388 fputs ("</TABLE>\n", x->file.file);
391 else if (d->class == &devind_class)
398 free (cmd.v_variables);
401 /* Writes string STRING at the current position. If the text would
402 fall off the side of the page, then advance to the next line,
403 indenting by amount INDENT. */
405 write_varname (struct outp_driver *d, char *string, int indent)
407 struct outp_text text;
409 text.options = OUTP_T_JUST_LEFT;
410 ls_init (&text.s, string, strlen (string));
411 d->class->text_metrics (d, &text);
413 if (d->cp_x + text.h > d->width)
415 d->cp_y += d->font_height;
416 if (d->cp_y + d->font_height > d->length)
423 d->class->text_draw (d, &text);
427 /* When we can't fit all the values across the page, we write out all
428 the variable names just once. This is where we do it. */
430 write_fallback_headers (struct outp_driver *d)
432 const int max_width = n_chars_width(d) - 10;
438 const char *Line = _("Line");
439 char *leader = local_alloc (strlen (Line) + INT_DIGITS + 1 + 1);
441 while (index < cmd.n_variables)
443 struct outp_text text;
445 /* Ensure that there is enough room for a line of text. */
446 if (d->cp_y + d->font_height > d->length)
449 /* The leader is a string like `Line 1: '. Write the leader. */
450 sprintf(leader, "%s %d:", Line, ++line_number);
451 text.options = OUTP_T_JUST_LEFT;
452 ls_init (&text.s, leader, strlen (leader));
455 d->class->text_draw (d, &text);
465 int var_width = cmd.v_variables[index]->print.w;
466 if (width + var_width > max_width && width != 0)
470 d->cp_y += d->font_height;
478 sprintf (varname, " %s", cmd.v_variables[index]->name);
479 write_varname (d, varname, text.h);
482 while (++index < cmd.n_variables);
486 d->cp_y += d->font_height;
491 /* There are three possible layouts for the LIST procedure:
493 1. If the values and their variables' name fit across the page,
494 then they are listed across the page in that way.
496 2. If the values can fit across the page, but not the variable
497 names, then as many variable names as necessary are printed
498 vertically to compensate.
500 3. If not even the values can fit across the page, the variable
501 names are listed just once, at the beginning, in a compact format,
502 and the values are listed with a variable name label at the
503 beginning of each line for easier reference.
505 This is complicated by the fact that we have to do all this for
506 every output driver, not just once. */
508 determine_layout (void)
510 struct outp_driver *d;
512 /* This is the largest page width of any driver, so we can tell what
513 size buffer to allocate. */
514 int largest_page_width = 0;
516 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
518 int column; /* Current column. */
519 int width; /* Accumulated width. */
520 int height; /* Height of vertical names. */
521 int max_width; /* Page width. */
523 struct list_ext *prc;
525 if (d->class == &html_class)
527 else if (d->class == &devind_class)
530 tab_output_text (TAT_NONE, "(devind not supported on LIST yet)");
534 assert (d->class->special == 0);
537 d->class->open_page (d);
539 max_width = n_chars_width (d);
540 largest_page_width = max (largest_page_width, max_width);
542 prc = d->prc = xmalloc (sizeof *prc);
548 for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
550 struct variable *v = cmd.v_variables[column];
551 width += max (v->print.w, (int) strlen (v->name));
553 if (width <= max_width)
555 prc->header_rows = 2;
556 d->class->text_set_font_by_name (d, "FIXED");
561 for (width = cmd.n_variables - 1, height = 0, column = 0;
562 column < cmd.n_variables && width <= max_width;
565 struct variable *v = cmd.v_variables[column];
567 if (strlen (v->name) > height)
568 height = strlen (v->name);
571 /* If it fit then we need to determine how many labels can be
572 written horizontally. */
573 if (width <= max_width && height <= SHORT_NAME_LEN)
576 prc->n_vertical = -1;
578 for (column = cmd.n_variables - 1; column >= 0; column--)
580 struct variable *v = cmd.v_variables[column];
581 int trial_width = (width - v->print.w
582 + max (v->print.w, (int) strlen (v->name)));
584 if (trial_width > max_width)
586 prc->n_vertical = column + 1;
591 assert(prc->n_vertical != -1);
593 prc->n_vertical = cmd.n_variables;
594 /* Finally determine the length of the headers. */
595 for (prc->header_rows = 0, column = 0;
596 column < prc->n_vertical;
598 prc->header_rows = max (prc->header_rows,
599 (int) strlen (cmd.v_variables[column]->name));
602 d->class->text_set_font_by_name (d, "FIXED");
606 /* Otherwise use the ugly fallback listing format. */
608 prc->header_rows = 0;
610 d->cp_y += d->font_height;
611 write_fallback_headers (d);
612 d->cp_y += d->font_height;
613 d->class->text_set_font_by_name (d, "FIXED");
616 line_buf = xmalloc (max (1022, largest_page_width) + 2);
620 list_cases (struct ccase *c, void *aux UNUSED)
622 struct outp_driver *d;
625 if (case_idx < cmd.first || case_idx > cmd.last
626 || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
629 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
630 if (d->class->special == 0)
632 const struct list_ext *prc = d->prc;
633 const int max_width = n_chars_width (d);
637 if (!prc->header_rows)
638 x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
640 for (column = 0; column < cmd.n_variables; column++)
642 struct variable *v = cmd.v_variables[column];
645 if (prc->type == 0 && column >= prc->n_vertical)
646 width = max ((int) strlen (v->name), v->print.w);
650 if (width + x > max_width && x != 0)
652 if (!n_lines_remaining (d))
659 write_line (d, line_buf);
662 if (!prc->header_rows)
663 x = nsprintf (line_buf, "%8s: ", v->name);
666 if (width > v->print.w)
668 memset(&line_buf[x], ' ', width - v->print.w);
669 x += width - v->print.w;
672 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
673 data_out (&line_buf[x], &v->print, case_data (c, v->fv));
676 union value case_idx_value;
677 case_idx_value.f = case_idx;
678 data_out (&line_buf[x], &v->print, &case_idx_value);
685 if (!n_lines_remaining (d))
692 write_line (d, line_buf);
694 else if (d->class == &html_class)
696 struct html_driver_ext *x = d->ext;
699 fputs (" <TR>\n", x->file.file);
701 for (column = 0; column < cmd.n_variables; column++)
703 struct variable *v = cmd.v_variables[column];
706 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
707 data_out (buf, &v->print, case_data (c, v->fv));
710 union value case_idx_value;
711 case_idx_value.f = case_idx;
712 data_out (buf, &v->print, &case_idx_value);
716 fprintf (x->file.file, " <TD ALIGN=RIGHT>%s</TD>\n",
717 &buf[strspn (buf, " ")]);
720 fputs (" </TR>\n", x->file.file);
722 else if (d->class == &devind_class)