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., 59 Temple Place - Suite 330, Boston, MA
40 #include "debug-print.h"
44 *variables=varlist("PV_NO_SCRATCH");
45 cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
46 format=numbering:numbered/!unnumbered,
48 weight:weight/!noweight.
53 /* Layout for one output driver. */
56 int type; /* 0=Values and labels fit across the page. */
57 int n_vertical; /* Number of labels to list vertically. */
58 int header_rows; /* Number of header rows. */
59 char **header; /* The header itself. */
63 static struct cmd_list cmd;
65 /* Current case number. */
69 static char *line_buf;
71 /* TTY-style output functions. */
72 static int n_lines_remaining (struct outp_driver *d);
73 static int n_chars_width (struct outp_driver *d);
74 static void write_line (struct outp_driver *d, char *s);
76 /* Other functions. */
77 static int list_cases (struct ccase *, void *);
78 static void determine_layout (void);
79 static void clean_up (void);
80 static void write_header (struct outp_driver *);
81 static void write_all_headers (void *);
83 /* Returns the number of text lines that can fit on the remainder of
86 n_lines_remaining (struct outp_driver *d)
90 diff = d->length - d->cp_y;
91 return (diff > 0) ? (diff / d->font_height) : 0;
94 /* Returns the number of fixed-width character that can fit across the
97 n_chars_width (struct outp_driver *d)
99 return d->width / d->fixed_width;
102 /* Writes the line S at the current position and advances to the next
105 write_line (struct outp_driver *d, char *s)
107 struct outp_text text;
109 assert (d->cp_y + d->font_height <= d->length);
110 text.options = OUTP_T_JUST_LEFT;
111 ls_init (&text.s, s, strlen (s));
114 d->class->text_draw (d, &text);
116 d->cp_y += d->font_height;
119 /* Parses and executes the LIST procedure. */
123 struct variable casenum_var;
125 if (!parse_list (&cmd))
128 /* Fill in defaults. */
129 if (cmd.step == NOT_LONG)
131 if (cmd.first == NOT_LONG)
133 if (cmd.last == NOT_LONG)
135 if (!cmd.sbc_variables)
136 dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
137 (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
138 if (cmd.n_variables == 0)
140 msg (SE, _("No variables specified."));
144 /* Verify arguments. */
145 if (cmd.first > cmd.last)
148 msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
149 "specified. The values will be swapped."), cmd.first, cmd.last);
151 cmd.first = cmd.last;
156 msg (SW, _("The first case (%ld) to list is less than 1. The value is "
157 "being reset to 1."), cmd.first);
162 msg (SW, _("The last case (%ld) to list is less than 1. The value is "
163 "being reset to 1."), cmd.last);
168 msg (SW, _("The step value %ld is less than 1. The value is being "
169 "reset to 1."), cmd.step);
173 /* Weighting variable. */
174 if (cmd.weight == LST_WEIGHT)
176 if (dict_get_weight (default_dict) != NULL)
180 for (i = 0; i < cmd.n_variables; i++)
181 if (cmd.v_variables[i] == dict_get_weight (default_dict))
183 if (i >= cmd.n_variables)
185 /* Add the weight variable to the end of the variable list. */
187 cmd.v_variables = xrealloc (cmd.v_variables,
189 * sizeof *cmd.v_variables));
190 cmd.v_variables[cmd.n_variables - 1]
191 = dict_get_weight (default_dict);
195 msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
199 if (cmd.numbering == LST_NUMBERED)
201 /* Initialize the case-number variable. */
202 strcpy (casenum_var.name, "Case#");
203 casenum_var.type = NUMERIC;
205 casenum_var.print.type = FMT_F;
206 casenum_var.print.w = (cmd.last == LONG_MAX ? 5 : intlog10 (cmd.last));
207 casenum_var.print.d = 0;
209 /* Add the weight variable at the beginning of the variable list. */
211 cmd.v_variables = xrealloc (cmd.v_variables,
212 cmd.n_variables * sizeof *cmd.v_variables);
213 memmove (&cmd.v_variables[1], &cmd.v_variables[0],
214 (cmd.n_variables - 1) * sizeof *cmd.v_variables);
215 cmd.v_variables[0] = &casenum_var;
221 procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
229 /* Writes headers to all devices. This is done at the beginning of
230 each SPLIT FILE group. */
232 write_all_headers (void *aux UNUSED)
234 struct outp_driver *d;
236 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
238 if (!d->class->special)
240 d->cp_y += d->font_height; /* Blank line. */
243 else if (d->class == &html_class)
245 struct html_driver_ext *x = d->ext;
247 assert (d->driver_open);
248 if (x->sequence_no == 0 && !d->class->open_page (d))
250 msg (ME, _("Cannot open first page on HTML device %s."),
255 fputs ("<TABLE BORDER=1>\n <TR>\n", x->file.file);
260 for (i = 0; i < cmd.n_variables; i++)
261 fprintf (x->file.file, " <TH><I><B>%s</B></I></TH>\n",
262 cmd.v_variables[i]->name);
265 fputs (" <TR>\n", x->file.file);
267 else if (d->class == &devind_class)
276 /* Writes the headers. Some of them might be vertical; most are
277 probably horizontal. */
279 write_header (struct outp_driver *d)
281 struct list_ext *prc = d->prc;
283 if (!prc->header_rows)
286 if (n_lines_remaining (d) < prc->header_rows + 1)
289 assert (n_lines_remaining (d) >= prc->header_rows + 1);
292 /* Design the header. */
297 /* Allocate, initialize header. */
298 prc->header = xmalloc (sizeof (char *) * prc->header_rows);
300 int w = n_chars_width (d);
301 for (i = 0; i < prc->header_rows; i++)
303 prc->header[i] = xmalloc (w + 1);
304 memset (prc->header[i], ' ', w);
308 /* Put in vertical names. */
309 for (i = x = 0; i < prc->n_vertical; i++)
311 struct variable *v = cmd.v_variables[i];
314 memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
316 for (j = 0; j < (int) strlen (v->name); j++)
317 prc->header[strlen (v->name) - j - 1][x] = v->name[j];
321 /* Put in horizontal names. */
322 for (; i < cmd.n_variables; i++)
324 struct variable *v = cmd.v_variables[i];
326 memset (&prc->header[prc->header_rows - 1][x], '-',
327 max (v->print.w, (int) strlen (v->name)));
328 if ((int) strlen (v->name) < v->print.w)
329 x += v->print.w - strlen (v->name);
330 memcpy (&prc->header[0][x], v->name, strlen (v->name));
331 x += strlen (v->name) + 1;
334 /* Add null bytes. */
335 for (i = 0; i < prc->header_rows; i++)
337 for (x = n_chars_width (d); x >= 1; x--)
338 if (prc->header[i][x - 1] != ' ')
340 prc->header[i][x] = 0;
347 /* Write out the header, in back-to-front order except for the last line. */
351 for (i = prc->header_rows - 2; i >= 0; i--)
352 write_line (d, prc->header[i]);
353 write_line (d, prc->header[prc->header_rows - 1]);
358 /* Frees up all the memory we've allocated. */
362 struct outp_driver *d;
364 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
365 if (d->class->special == 0)
367 struct list_ext *prc = d->prc;
372 for (i = 0; i < prc->header_rows; i++)
373 free (prc->header[i]);
378 d->class->text_set_font_by_name (d, "PROP");
380 else if (d->class == &html_class)
382 if (d->driver_open && d->page_open)
384 struct html_driver_ext *x = d->ext;
386 fputs ("</TABLE>\n", x->file.file);
389 else if (d->class == &devind_class)
396 free (cmd.v_variables);
399 /* Writes string STRING at the current position. If the text would
400 fall off the side of the page, then advance to the next line,
401 indenting by amount INDENT. */
403 write_varname (struct outp_driver *d, char *string, int indent)
405 struct outp_text text;
407 text.options = OUTP_T_JUST_LEFT;
408 ls_init (&text.s, string, strlen (string));
409 d->class->text_metrics (d, &text);
411 if (d->cp_x + text.h > d->width)
413 d->cp_y += d->font_height;
414 if (d->cp_y + d->font_height > d->length)
421 d->class->text_draw (d, &text);
425 /* When we can't fit all the values across the page, we write out all
426 the variable names just once. This is where we do it. */
428 write_fallback_headers (struct outp_driver *d)
430 const int max_width = n_chars_width(d) - 10;
436 const char *Line = _("Line");
437 char *leader = local_alloc (strlen (Line) + INT_DIGITS + 1 + 1);
439 while (index < cmd.n_variables)
441 struct outp_text text;
443 /* Ensure that there is enough room for a line of text. */
444 if (d->cp_y + d->font_height > d->length)
447 /* The leader is a string like `Line 1: '. Write the leader. */
448 sprintf(leader, "%s %d:", Line, ++line_number);
449 text.options = OUTP_T_JUST_LEFT;
450 ls_init (&text.s, leader, strlen (leader));
453 d->class->text_draw (d, &text);
463 int var_width = cmd.v_variables[index]->print.w;
464 if (width + var_width > max_width && width != 0)
468 d->cp_y += d->font_height;
476 sprintf (varname, " %s", cmd.v_variables[index]->name);
477 write_varname (d, varname, text.h);
480 while (++index < cmd.n_variables);
484 d->cp_y += d->font_height;
489 /* There are three possible layouts for the LIST procedure:
491 1. If the values and their variables' name fit across the page,
492 then they are listed across the page in that way.
494 2. If the values can fit across the page, but not the variable
495 names, then as many variable names as necessary are printed
496 vertically to compensate.
498 3. If not even the values can fit across the page, the variable
499 names are listed just once, at the beginning, in a compact format,
500 and the values are listed with a variable name label at the
501 beginning of each line for easier reference.
503 This is complicated by the fact that we have to do all this for
504 every output driver, not just once. */
506 determine_layout (void)
508 struct outp_driver *d;
510 /* This is the largest page width of any driver, so we can tell what
511 size buffer to allocate. */
512 int largest_page_width = 0;
514 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
516 int column; /* Current column. */
517 int width; /* Accumulated width. */
518 int max_width; /* Page width. */
520 struct list_ext *prc;
522 if (d->class == &html_class)
524 else if (d->class == &devind_class)
527 tab_output_text (TAT_NONE, "(devind not supported on LIST yet)");
531 assert (d->class->special == 0);
534 d->class->open_page (d);
536 max_width = n_chars_width (d);
537 largest_page_width = max (largest_page_width, max_width);
539 prc = d->prc = xmalloc (sizeof *prc);
545 for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
547 struct variable *v = cmd.v_variables[column];
548 width += max (v->print.w, (int) strlen (v->name));
550 if (width <= max_width)
552 prc->header_rows = 2;
553 d->class->text_set_font_by_name (d, "FIXED");
558 for (width = cmd.n_variables - 1, column = 0;
559 column < cmd.n_variables && width <= max_width;
561 width += cmd.v_variables[column]->print.w;
563 /* If it fit then we need to determine how many labels can be
564 written horizontally. */
565 if (width <= max_width)
568 prc->n_vertical = -1;
570 for (column = cmd.n_variables - 1; column >= 0; column--)
572 struct variable *v = cmd.v_variables[column];
573 int trial_width = (width - v->print.w
574 + max (v->print.w, (int) strlen (v->name)));
576 if (trial_width > max_width)
578 prc->n_vertical = column + 1;
583 assert(prc->n_vertical != -1);
585 prc->n_vertical = cmd.n_variables;
586 /* Finally determine the length of the headers. */
587 for (prc->header_rows = 0, column = 0;
588 column < prc->n_vertical;
590 prc->header_rows = max (prc->header_rows,
591 (int) strlen (cmd.v_variables[column]->name));
594 d->class->text_set_font_by_name (d, "FIXED");
598 /* Otherwise use the ugly fallback listing format. */
600 prc->header_rows = 0;
602 d->cp_y += d->font_height;
603 write_fallback_headers (d);
604 d->cp_y += d->font_height;
605 d->class->text_set_font_by_name (d, "FIXED");
608 line_buf = xmalloc (max (1022, largest_page_width) + 2);
612 list_cases (struct ccase *c, void *aux UNUSED)
614 struct outp_driver *d;
617 if (case_idx < cmd.first || case_idx > cmd.last
618 || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
621 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
622 if (d->class->special == 0)
624 const struct list_ext *prc = d->prc;
625 const int max_width = n_chars_width (d);
629 if (!prc->header_rows)
630 x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
632 for (column = 0; column < cmd.n_variables; column++)
634 struct variable *v = cmd.v_variables[column];
637 if (prc->type == 0 && column >= prc->n_vertical)
638 width = max ((int) strlen (v->name), v->print.w);
642 if (width + x > max_width && x != 0)
644 if (!n_lines_remaining (d))
651 write_line (d, line_buf);
654 if (!prc->header_rows)
655 x = nsprintf (line_buf, "%8s: ", v->name);
658 if (width > v->print.w)
660 memset(&line_buf[x], ' ', width - v->print.w);
661 x += width - v->print.w;
664 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
665 data_out (&line_buf[x], &v->print, case_data (c, v->fv));
668 union value case_idx_value;
669 case_idx_value.f = case_idx;
670 data_out (&line_buf[x], &v->print, &case_idx_value);
677 if (!n_lines_remaining (d))
684 write_line (d, line_buf);
686 else if (d->class == &html_class)
688 struct html_driver_ext *x = d->ext;
691 fputs (" <TR>\n", x->file.file);
693 for (column = 0; column < cmd.n_variables; column++)
695 struct variable *v = cmd.v_variables[column];
698 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
699 data_out (buf, &v->print, case_data (c, v->fv));
702 union value case_idx_value;
703 case_idx_value.f = case_idx;
704 data_out (buf, &v->print, &case_idx_value);
708 fprintf (x->file.file, " <TD ALIGN=RIGHT>%s</TD>\n",
709 &buf[strspn (buf, " ")]);
712 fputs (" </TR>\n", x->file.file);
714 else if (d->class == &devind_class)