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 #define _(msgid) gettext (msgid)
46 #include "debug-print.h"
50 *variables=varlist("PV_NO_SCRATCH");
51 cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
52 format=numbering:numbered/!unnumbered,
54 weight:weight/!noweight.
59 /* Layout for one output driver. */
62 int type; /* 0=Values and labels fit across the page. */
63 int n_vertical; /* Number of labels to list vertically. */
64 int header_rows; /* Number of header rows. */
65 char **header; /* The header itself. */
69 static struct cmd_list cmd;
71 /* Current case number. */
75 static char *line_buf;
77 /* TTY-style output functions. */
78 static int n_lines_remaining (struct outp_driver *d);
79 static int n_chars_width (struct outp_driver *d);
80 static void write_line (struct outp_driver *d, char *s);
82 /* Other functions. */
83 static int list_cases (struct ccase *, void *);
84 static void determine_layout (void);
85 static void clean_up (void);
86 static void write_header (struct outp_driver *);
87 static void write_all_headers (void *);
89 /* Returns the number of text lines that can fit on the remainder of
92 n_lines_remaining (struct outp_driver *d)
96 diff = d->length - d->cp_y;
97 return (diff > 0) ? (diff / d->font_height) : 0;
100 /* Returns the number of fixed-width character that can fit across the
103 n_chars_width (struct outp_driver *d)
105 return d->width / d->fixed_width;
108 /* Writes the line S at the current position and advances to the next
111 write_line (struct outp_driver *d, char *s)
113 struct outp_text text;
115 assert (d->cp_y + d->font_height <= d->length);
116 text.options = OUTP_T_JUST_LEFT;
117 ls_init (&text.s, s, strlen (s));
120 d->class->text_draw (d, &text);
122 d->cp_y += d->font_height;
125 /* Parses and executes the LIST procedure. */
129 struct variable casenum_var;
131 if (!parse_list (&cmd))
134 /* Fill in defaults. */
135 if (cmd.step == NOT_LONG)
137 if (cmd.first == NOT_LONG)
139 if (cmd.last == NOT_LONG)
141 if (!cmd.sbc_variables)
142 dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
143 (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
144 if (cmd.n_variables == 0)
146 msg (SE, _("No variables specified."));
150 /* Verify arguments. */
151 if (cmd.first > cmd.last)
154 msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
155 "specified. The values will be swapped."), cmd.first, cmd.last);
157 cmd.first = cmd.last;
162 msg (SW, _("The first case (%ld) to list is less than 1. The value is "
163 "being reset to 1."), cmd.first);
168 msg (SW, _("The last case (%ld) to list is less than 1. The value is "
169 "being reset to 1."), cmd.last);
174 msg (SW, _("The step value %ld is less than 1. The value is being "
175 "reset to 1."), cmd.step);
179 /* Weighting variable. */
180 if (cmd.weight == LST_WEIGHT)
182 if (dict_get_weight (default_dict) != NULL)
186 for (i = 0; i < cmd.n_variables; i++)
187 if (cmd.v_variables[i] == dict_get_weight (default_dict))
189 if (i >= cmd.n_variables)
191 /* Add the weight variable to the end of the variable list. */
193 cmd.v_variables = xrealloc (cmd.v_variables,
195 * sizeof *cmd.v_variables));
196 cmd.v_variables[cmd.n_variables - 1]
197 = dict_get_weight (default_dict);
201 msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
205 if (cmd.numbering == LST_NUMBERED)
207 /* Initialize the case-number variable. */
208 strcpy (casenum_var.name, "Case#");
209 casenum_var.type = NUMERIC;
211 casenum_var.print = make_output_format (FMT_F,
212 (cmd.last == LONG_MAX
213 ? 5 : intlog10 (cmd.last)), 0);
215 /* Add the weight variable at the beginning of the variable list. */
217 cmd.v_variables = xrealloc (cmd.v_variables,
218 cmd.n_variables * sizeof *cmd.v_variables);
219 memmove (&cmd.v_variables[1], &cmd.v_variables[0],
220 (cmd.n_variables - 1) * sizeof *cmd.v_variables);
221 cmd.v_variables[0] = &casenum_var;
227 procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
235 /* Writes headers to all devices. This is done at the beginning of
236 each SPLIT FILE group. */
238 write_all_headers (void *aux UNUSED)
240 struct outp_driver *d;
242 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
244 if (!d->class->special)
246 d->cp_y += d->font_height; /* Blank line. */
249 else if (d->class == &html_class)
251 struct html_driver_ext *x = d->ext;
253 assert (d->driver_open);
254 if (x->sequence_no == 0 && !d->class->open_page (d))
256 msg (ME, _("Cannot open first page on HTML device %s."),
261 fputs ("<TABLE BORDER=1>\n <TR>\n", x->file.file);
266 for (i = 0; i < cmd.n_variables; i++)
267 fprintf (x->file.file, " <TH><I><B>%s</B></I></TH>\n",
268 cmd.v_variables[i]->name);
271 fputs (" <TR>\n", x->file.file);
273 else if (d->class == &devind_class)
282 /* Writes the headers. Some of them might be vertical; most are
283 probably horizontal. */
285 write_header (struct outp_driver *d)
287 struct list_ext *prc = d->prc;
289 if (!prc->header_rows)
292 if (n_lines_remaining (d) < prc->header_rows + 1)
295 assert (n_lines_remaining (d) >= prc->header_rows + 1);
298 /* Design the header. */
303 /* Allocate, initialize header. */
304 prc->header = xmalloc (sizeof (char *) * prc->header_rows);
306 int w = n_chars_width (d);
307 for (i = 0; i < prc->header_rows; i++)
309 prc->header[i] = xmalloc (w + 1);
310 memset (prc->header[i], ' ', w);
314 /* Put in vertical names. */
315 for (i = x = 0; i < prc->n_vertical; i++)
317 struct variable *v = cmd.v_variables[i];
320 memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
322 for (j = 0; j < (int) strlen (v->name); j++)
323 prc->header[strlen (v->name) - j - 1][x] = v->name[j];
327 /* Put in horizontal names. */
328 for (; i < cmd.n_variables; i++)
330 struct variable *v = cmd.v_variables[i];
332 memset (&prc->header[prc->header_rows - 1][x], '-',
333 max (v->print.w, (int) strlen (v->name)));
334 if ((int) strlen (v->name) < v->print.w)
335 x += v->print.w - strlen (v->name);
336 memcpy (&prc->header[0][x], v->name, strlen (v->name));
337 x += strlen (v->name) + 1;
340 /* Add null bytes. */
341 for (i = 0; i < prc->header_rows; i++)
343 for (x = n_chars_width (d); x >= 1; x--)
344 if (prc->header[i][x - 1] != ' ')
346 prc->header[i][x] = 0;
353 /* Write out the header, in back-to-front order except for the last line. */
357 for (i = prc->header_rows - 2; i >= 0; i--)
358 write_line (d, prc->header[i]);
359 write_line (d, prc->header[prc->header_rows - 1]);
364 /* Frees up all the memory we've allocated. */
368 struct outp_driver *d;
370 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
371 if (d->class->special == 0)
373 struct list_ext *prc = d->prc;
378 for (i = 0; i < prc->header_rows; i++)
379 free (prc->header[i]);
384 d->class->text_set_font_by_name (d, "PROP");
386 else if (d->class == &html_class)
388 if (d->driver_open && d->page_open)
390 struct html_driver_ext *x = d->ext;
392 fputs ("</TABLE>\n", x->file.file);
395 else if (d->class == &devind_class)
402 free (cmd.v_variables);
405 /* Writes string STRING at the current position. If the text would
406 fall off the side of the page, then advance to the next line,
407 indenting by amount INDENT. */
409 write_varname (struct outp_driver *d, char *string, int indent)
411 struct outp_text text;
413 text.options = OUTP_T_JUST_LEFT;
414 ls_init (&text.s, string, strlen (string));
415 d->class->text_metrics (d, &text);
417 if (d->cp_x + text.h > d->width)
419 d->cp_y += d->font_height;
420 if (d->cp_y + d->font_height > d->length)
427 d->class->text_draw (d, &text);
431 /* When we can't fit all the values across the page, we write out all
432 the variable names just once. This is where we do it. */
434 write_fallback_headers (struct outp_driver *d)
436 const int max_width = n_chars_width(d) - 10;
442 const char *Line = _("Line");
443 char *leader = local_alloc (strlen (Line) + INT_DIGITS + 1 + 1);
445 while (index < cmd.n_variables)
447 struct outp_text text;
449 /* Ensure that there is enough room for a line of text. */
450 if (d->cp_y + d->font_height > d->length)
453 /* The leader is a string like `Line 1: '. Write the leader. */
454 sprintf(leader, "%s %d:", Line, ++line_number);
455 text.options = OUTP_T_JUST_LEFT;
456 ls_init (&text.s, leader, strlen (leader));
459 d->class->text_draw (d, &text);
469 int var_width = cmd.v_variables[index]->print.w;
470 if (width + var_width > max_width && width != 0)
474 d->cp_y += d->font_height;
482 sprintf (varname, " %s", cmd.v_variables[index]->name);
483 write_varname (d, varname, text.h);
486 while (++index < cmd.n_variables);
490 d->cp_y += d->font_height;
495 /* There are three possible layouts for the LIST procedure:
497 1. If the values and their variables' name fit across the page,
498 then they are listed across the page in that way.
500 2. If the values can fit across the page, but not the variable
501 names, then as many variable names as necessary are printed
502 vertically to compensate.
504 3. If not even the values can fit across the page, the variable
505 names are listed just once, at the beginning, in a compact format,
506 and the values are listed with a variable name label at the
507 beginning of each line for easier reference.
509 This is complicated by the fact that we have to do all this for
510 every output driver, not just once. */
512 determine_layout (void)
514 struct outp_driver *d;
516 /* This is the largest page width of any driver, so we can tell what
517 size buffer to allocate. */
518 int largest_page_width = 0;
520 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
522 int column; /* Current column. */
523 int width; /* Accumulated width. */
524 int height; /* Height of vertical names. */
525 int max_width; /* Page width. */
527 struct list_ext *prc;
529 if (d->class == &html_class)
531 else if (d->class == &devind_class)
534 tab_output_text (TAT_NONE, "(devind not supported on LIST yet)");
538 assert (d->class->special == 0);
541 d->class->open_page (d);
543 max_width = n_chars_width (d);
544 largest_page_width = max (largest_page_width, max_width);
546 prc = d->prc = xmalloc (sizeof *prc);
552 for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
554 struct variable *v = cmd.v_variables[column];
555 width += max (v->print.w, (int) strlen (v->name));
557 if (width <= max_width)
559 prc->header_rows = 2;
560 d->class->text_set_font_by_name (d, "FIXED");
565 for (width = cmd.n_variables - 1, height = 0, column = 0;
566 column < cmd.n_variables && width <= max_width;
569 struct variable *v = cmd.v_variables[column];
571 if (strlen (v->name) > height)
572 height = strlen (v->name);
575 /* If it fit then we need to determine how many labels can be
576 written horizontally. */
577 if (width <= max_width && height <= SHORT_NAME_LEN)
580 prc->n_vertical = -1;
582 for (column = cmd.n_variables - 1; column >= 0; column--)
584 struct variable *v = cmd.v_variables[column];
585 int trial_width = (width - v->print.w
586 + max (v->print.w, (int) strlen (v->name)));
588 if (trial_width > max_width)
590 prc->n_vertical = column + 1;
595 assert(prc->n_vertical != -1);
597 prc->n_vertical = cmd.n_variables;
598 /* Finally determine the length of the headers. */
599 for (prc->header_rows = 0, column = 0;
600 column < prc->n_vertical;
602 prc->header_rows = max (prc->header_rows,
603 (int) strlen (cmd.v_variables[column]->name));
606 d->class->text_set_font_by_name (d, "FIXED");
610 /* Otherwise use the ugly fallback listing format. */
612 prc->header_rows = 0;
614 d->cp_y += d->font_height;
615 write_fallback_headers (d);
616 d->cp_y += d->font_height;
617 d->class->text_set_font_by_name (d, "FIXED");
620 line_buf = xmalloc (max (1022, largest_page_width) + 2);
624 list_cases (struct ccase *c, void *aux UNUSED)
626 struct outp_driver *d;
629 if (case_idx < cmd.first || case_idx > cmd.last
630 || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
633 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
634 if (d->class->special == 0)
636 const struct list_ext *prc = d->prc;
637 const int max_width = n_chars_width (d);
641 if (!prc->header_rows)
642 x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
644 for (column = 0; column < cmd.n_variables; column++)
646 struct variable *v = cmd.v_variables[column];
649 if (prc->type == 0 && column >= prc->n_vertical)
650 width = max ((int) strlen (v->name), v->print.w);
654 if (width + x > max_width && x != 0)
656 if (!n_lines_remaining (d))
663 write_line (d, line_buf);
666 if (!prc->header_rows)
667 x = nsprintf (line_buf, "%8s: ", v->name);
670 if (width > v->print.w)
672 memset(&line_buf[x], ' ', width - v->print.w);
673 x += width - v->print.w;
676 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
677 data_out (&line_buf[x], &v->print, case_data (c, v->fv));
680 union value case_idx_value;
681 case_idx_value.f = case_idx;
682 data_out (&line_buf[x], &v->print, &case_idx_value);
689 if (!n_lines_remaining (d))
696 write_line (d, line_buf);
698 else if (d->class == &html_class)
700 struct html_driver_ext *x = d->ext;
703 fputs (" <TR>\n", x->file.file);
705 for (column = 0; column < cmd.n_variables; column++)
707 struct variable *v = cmd.v_variables[column];
710 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
711 data_out (buf, &v->print, case_data (c, v->fv));
714 union value case_idx_value;
715 case_idx_value.f = case_idx;
716 data_out (buf, &v->print, &case_idx_value);
720 fprintf (x->file.file, " <TD ALIGN=RIGHT>%s</TD>\n",
721 &buf[strspn (buf, " ")]);
724 fputs (" </TR>\n", x->file.file);
726 else if (d->class == &devind_class)