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
39 #include "debug-print.h"
42 static void debug_print (void);
47 *variables=varlist("PV_NO_SCRATCH");
48 cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
49 format=numbering:numbered/!unnumbered,
51 weight:weight/!noweight.
56 /* Layout for one output driver. */
59 int type; /* 0=Values and labels fit across the page. */
60 int n_vertical; /* Number of labels to list vertically. */
61 int header_rows; /* Number of header rows. */
62 char **header; /* The header itself. */
66 static struct cmd_list cmd;
68 /* Current case number. */
72 static char *line_buf;
74 /* TTY-style output functions. */
75 static int n_lines_remaining (struct outp_driver *d);
76 static int n_chars_width (struct outp_driver *d);
77 static void write_line (struct outp_driver *d, char *s);
79 /* Other functions. */
80 static int list_cases (struct ccase *, void *);
81 static void determine_layout (void);
82 static void clean_up (void);
83 static void write_header (struct outp_driver *);
84 static void write_all_headers (void *);
86 /* Returns the number of text lines that can fit on the remainder of
89 n_lines_remaining (struct outp_driver *d)
93 diff = d->length - d->cp_y;
94 return (diff > 0) ? (diff / d->font_height) : 0;
97 /* Returns the number of fixed-width character that can fit across the
100 n_chars_width (struct outp_driver *d)
102 return d->width / d->fixed_width;
105 /* Writes the line S at the current position and advances to the next
108 write_line (struct outp_driver *d, char *s)
110 struct outp_text text;
112 assert (d->cp_y + d->font_height <= d->length);
113 text.options = OUTP_T_JUST_LEFT;
114 ls_init (&text.s, s, strlen (s));
117 d->class->text_draw (d, &text);
119 d->cp_y += d->font_height;
122 /* Parses and executes the LIST procedure. */
126 struct variable casenum_var;
128 if (!parse_list (&cmd))
131 /* Fill in defaults. */
132 if (cmd.step == NOT_LONG)
134 if (cmd.first == NOT_LONG)
136 if (cmd.last == NOT_LONG)
138 if (!cmd.sbc_variables)
139 dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
140 (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
141 if (cmd.n_variables == 0)
143 msg (SE, _("No variables specified."));
147 /* Verify arguments. */
148 if (cmd.first > cmd.last)
151 msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
152 "specified. The values will be swapped."), cmd.first, cmd.last);
154 cmd.first = cmd.last;
159 msg (SW, _("The first case (%ld) to list is less than 1. The value is "
160 "being reset to 1."), cmd.first);
165 msg (SW, _("The last case (%ld) to list is less than 1. The value is "
166 "being reset to 1."), cmd.last);
171 msg (SW, _("The step value %ld is less than 1. The value is being "
172 "reset to 1."), cmd.step);
176 /* Weighting variable. */
177 if (cmd.weight == LST_WEIGHT)
179 if (dict_get_weight (default_dict) != NULL)
183 for (i = 0; i < cmd.n_variables; i++)
184 if (cmd.v_variables[i] == dict_get_weight (default_dict))
186 if (i >= cmd.n_variables)
188 /* Add the weight variable to the end of the variable list. */
190 cmd.v_variables = xrealloc (cmd.v_variables,
192 * sizeof *cmd.v_variables));
193 cmd.v_variables[cmd.n_variables - 1]
194 = dict_get_weight (default_dict);
198 msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
202 if (cmd.numbering == LST_NUMBERED)
204 /* Initialize the case-number variable. */
205 strcpy (casenum_var.name, "Case#");
206 casenum_var.type = NUMERIC;
208 casenum_var.print.type = FMT_F;
209 casenum_var.print.w = (cmd.last == LONG_MAX ? 5 : intlog10 (cmd.last));
210 casenum_var.print.d = 0;
212 /* Add the weight variable at the beginning of the variable list. */
214 cmd.v_variables = xrealloc (cmd.v_variables,
215 cmd.n_variables * sizeof *cmd.v_variables);
216 memmove (&cmd.v_variables[1], &cmd.v_variables[0],
217 (cmd.n_variables - 1) * sizeof *cmd.v_variables);
218 cmd.v_variables[0] = &casenum_var;
222 /* Print out command. */
229 procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
237 /* Writes headers to all devices. This is done at the beginning of
238 each SPLIT FILE group. */
240 write_all_headers (void *aux UNUSED)
242 struct outp_driver *d;
244 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
246 if (!d->class->special)
248 d->cp_y += d->font_height; /* Blank line. */
251 else if (d->class == &html_class)
253 struct html_driver_ext *x = d->ext;
255 assert (d->driver_open);
256 if (x->sequence_no == 0 && !d->class->open_page (d))
258 msg (ME, _("Cannot open first page on HTML device %s."),
263 fputs ("<TABLE BORDER=1>\n <TR>\n", x->file.file);
268 for (i = 0; i < cmd.n_variables; i++)
269 fprintf (x->file.file, " <TH><I><B>%s</B></I></TH>\n",
270 cmd.v_variables[i]->name);
273 fputs (" <TR>\n", x->file.file);
275 else if (d->class == &devind_class)
284 /* Writes the headers. Some of them might be vertical; most are
285 probably horizontal. */
287 write_header (struct outp_driver *d)
289 struct list_ext *prc = d->prc;
291 if (!prc->header_rows)
294 if (n_lines_remaining (d) < prc->header_rows + 1)
297 assert (n_lines_remaining (d) >= prc->header_rows + 1);
300 /* 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 < (int) 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. */
359 for (i = prc->header_rows - 2; i >= 0; i--)
360 write_line (d, prc->header[i]);
361 write_line (d, prc->header[prc->header_rows - 1]);
366 /* Frees up all the memory we've allocated. */
370 struct outp_driver *d;
372 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
373 if (d->class->special == 0)
375 struct list_ext *prc = d->prc;
380 for (i = 0; i < prc->header_rows; i++)
381 free (prc->header[i]);
386 d->class->text_set_font_by_name (d, "PROP");
388 else if (d->class == &html_class)
390 if (d->driver_open && d->page_open)
392 struct html_driver_ext *x = d->ext;
394 fputs ("</TABLE>\n", x->file.file);
397 else if (d->class == &devind_class)
404 free (cmd.v_variables);
407 /* Writes string STRING at the current position. If the text would
408 fall off the side of the page, then advance to the next line,
409 indenting by amount INDENT. */
411 write_varname (struct outp_driver *d, char *string, int indent)
413 struct outp_text text;
415 text.options = OUTP_T_JUST_LEFT;
416 ls_init (&text.s, string, strlen (string));
417 d->class->text_metrics (d, &text);
419 if (d->cp_x + text.h > d->width)
421 d->cp_y += d->font_height;
422 if (d->cp_y + d->font_height > d->length)
429 d->class->text_draw (d, &text);
433 /* When we can't fit all the values across the page, we write out all
434 the variable names just once. This is where we do it. */
436 write_fallback_headers (struct outp_driver *d)
438 const int max_width = n_chars_width(d) - 10;
444 const char *Line = _("Line");
445 char *leader = local_alloc (strlen (Line) + INT_DIGITS + 1 + 1);
447 while (index < cmd.n_variables)
449 struct outp_text text;
451 /* Ensure that there is enough room for a line of text. */
452 if (d->cp_y + d->font_height > d->length)
455 /* The leader is a string like `Line 1: '. Write the leader. */
456 sprintf(leader, "%s %d:", Line, ++line_number);
457 text.options = OUTP_T_JUST_LEFT;
458 ls_init (&text.s, leader, strlen (leader));
461 d->class->text_draw (d, &text);
471 int var_width = cmd.v_variables[index]->print.w;
472 if (width + var_width > max_width && width != 0)
476 d->cp_y += d->font_height;
484 sprintf (varname, " %s", cmd.v_variables[index]->name);
485 write_varname (d, varname, text.h);
488 while (++index < cmd.n_variables);
492 d->cp_y += d->font_height;
497 /* There are three possible layouts for the LIST procedure:
499 1. If the values and their variables' name fit across the page,
500 then they are listed across the page in that way.
502 2. If the values can fit across the page, but not the variable
503 names, then as many variable names as necessary are printed
504 vertically to compensate.
506 3. If not even the values can fit across the page, the variable
507 names are listed just once, at the beginning, in a compact format,
508 and the values are listed with a variable name label at the
509 beginning of each line for easier reference.
511 This is complicated by the fact that we have to do all this for
512 every output driver, not just once. */
514 determine_layout (void)
516 struct outp_driver *d;
518 /* This is the largest page width of any driver, so we can tell what
519 size buffer to allocate. */
520 int largest_page_width = 0;
522 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
524 int column; /* Current column. */
525 int width; /* Accumulated width. */
526 int max_width; /* Page width. */
528 struct list_ext *prc;
530 if (d->class == &html_class)
532 else if (d->class == &devind_class)
535 tab_output_text (TAT_NONE, "(devind not supported on LIST yet)");
539 assert (d->class->special == 0);
542 d->class->open_page (d);
544 max_width = n_chars_width (d);
545 largest_page_width = max (largest_page_width, max_width);
547 prc = d->prc = xmalloc (sizeof *prc);
553 for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
555 struct variable *v = cmd.v_variables[column];
556 width += max (v->print.w, (int) strlen (v->name));
558 if (width <= max_width)
560 prc->header_rows = 2;
561 d->class->text_set_font_by_name (d, "FIXED");
566 for (width = cmd.n_variables - 1, column = 0;
567 column < cmd.n_variables && width <= max_width;
569 width += cmd.v_variables[column]->print.w;
571 /* If it fit then we need to determine how many labels can be
572 written horizontally. */
573 if (width <= max_width)
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_num < cmd.first || case_num > cmd.last
626 || (cmd.step != 1 && (case_num - 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, &c->data[v->fv]);
676 union value case_num_value;
677 case_num_value.f = case_num;
678 data_out (&line_buf[x], &v->print, &case_num_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, &c->data[v->fv]);
710 union value case_num_value;
711 case_num_value.f = case_num;
712 data_out (buf, &v->print, &case_num_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)
732 /* Debugging output. */
735 /* Prints out the command as parsed by cmd_list(). */
742 printf (" VARIABLES=");
743 for (i = 0; i < cmd.n_variables; i++)
747 fputs (cmd.v_variables[i]->name, stdout);
750 printf ("\n /CASES=FROM %ld TO %ld BY %ld\n", cmd.first, cmd.last, cmd.step);
752 fputs (" /FORMAT=", stdout);
753 if (cmd.numbering == LST_NUMBERED)
754 fputs ("NUMBERED", stdout);
756 fputs ("UNNUMBERED", stdout);
758 if (cmd.wrap == LST_WRAP)
759 fputs ("WRAP", stdout);
761 fputs ("SINGLE", stdout);
763 if (cmd.weight == LST_WEIGHT)
764 fputs ("WEIGHT", stdout);
766 fputs ("NOWEIGHT", stdout);
769 #endif /* DEBUGGING */