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
37 #include "debug-print.h"
40 static void debug_print (void);
45 *variables=varlist("PV_NO_SCRATCH");
46 cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
47 format=numbering:numbered/!unnumbered,
49 weight:weight/!noweight.
54 /* Layout for one output driver. */
57 int type; /* 0=Values and labels fit across the page. */
58 int n_vertical; /* Number of labels to list vertically. */
59 int header_rows; /* Number of header rows. */
60 char **header; /* The header itself. */
64 static struct cmd_list cmd;
66 /* Current case number. */
70 static char *line_buf;
72 /* TTY-style output functions. */
73 static int n_lines_remaining (struct outp_driver *d);
74 static int n_chars_width (struct outp_driver *d);
75 static void write_line (struct outp_driver *d, char *s);
77 /* Other functions. */
78 static int list_cases (struct ccase *);
79 static void determine_layout (void);
80 static void clean_up (void);
81 static void write_header (struct outp_driver *);
82 static void write_all_headers (void);
84 /* Returns the number of text lines that can fit on the remainder of
87 n_lines_remaining (struct outp_driver *d)
91 diff = d->length - d->cp_y;
92 return (diff > 0) ? (diff / d->font_height) : 0;
95 /* Returns the number of fixed-width character that can fit across the
98 n_chars_width (struct outp_driver *d)
100 return d->width / d->fixed_width;
103 /* Writes the line S at the current position and advances to the next
106 write_line (struct outp_driver *d, char *s)
108 struct outp_text text;
110 assert (d->cp_y + d->font_height <= d->length);
111 text.options = OUTP_T_JUST_LEFT;
112 ls_init (&text.s, s, strlen (s));
115 d->class->text_draw (d, &text);
117 d->cp_y += d->font_height;
120 /* Parses and executes the LIST procedure. */
124 struct variable casenum_var;
126 lex_match_id ("LIST");
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.type = FMT_F;
208 casenum_var.print.w = (cmd.last == LONG_MAX ? 5 : intlog10 (cmd.last));
209 casenum_var.print.d = 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;
221 /* Print out command. */
228 procedure (write_all_headers, list_cases, NULL);
236 /* Writes headers to all devices. This is done at the beginning of
237 each SPLIT FILE group. */
239 write_all_headers (void)
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 && d->page_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);
279 /* Writes the headers. Some of them might be vertical; most are
280 probably horizontal. */
282 write_header (struct outp_driver *d)
284 struct list_ext *prc = d->prc;
286 if (!prc->header_rows)
289 if (n_lines_remaining (d) < prc->header_rows + 1)
292 assert (n_lines_remaining (d) >= prc->header_rows + 1);
295 /* Design the header. */
300 /* Allocate, initialize header. */
301 prc->header = xmalloc (sizeof (char *) * prc->header_rows);
303 int w = n_chars_width (d);
304 for (i = 0; i < prc->header_rows; i++)
306 prc->header[i] = xmalloc (w + 1);
307 memset (prc->header[i], ' ', w);
311 /* Put in vertical names. */
312 for (i = x = 0; i < prc->n_vertical; i++)
314 struct variable *v = cmd.v_variables[i];
317 memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
319 for (j = 0; j < (int) strlen (v->name); j++)
320 prc->header[strlen (v->name) - j - 1][x] = v->name[j];
324 /* Put in horizontal names. */
325 for (; i < cmd.n_variables; i++)
327 struct variable *v = cmd.v_variables[i];
329 memset (&prc->header[prc->header_rows - 1][x], '-',
330 max (v->print.w, (int) strlen (v->name)));
331 if ((int) strlen (v->name) < v->print.w)
332 x += v->print.w - strlen (v->name);
333 memcpy (&prc->header[0][x], v->name, strlen (v->name));
334 x += strlen (v->name) + 1;
337 /* Add null bytes. */
338 for (i = 0; i < prc->header_rows; i++)
340 for (x = n_chars_width (d); x >= 1; x--)
341 if (prc->header[i][x - 1] != ' ')
343 prc->header[i][x] = 0;
350 /* Write out the header, in back-to-front order except for the last line. */
354 for (i = prc->header_rows - 2; i >= 0; i--)
355 write_line (d, prc->header[i]);
356 write_line (d, prc->header[prc->header_rows - 1]);
361 /* Frees up all the memory we've allocated. */
365 struct outp_driver *d;
367 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
368 if (d->class->special == 0)
370 struct list_ext *prc = d->prc;
375 for (i = 0; i < prc->header_rows; i++)
376 free (prc->header[i]);
381 d->class->text_set_font_by_name (d, "PROP");
383 else if (d->class == &html_class)
385 if (d->driver_open && d->page_open)
387 struct html_driver_ext *x = d->ext;
389 fputs ("</TABLE>\n", x->file.file);
395 free (cmd.v_variables);
398 /* Writes string STRING at the current position. If the text would
399 fall off the side of the page, then advance to the next line,
400 indenting by amount INDENT. */
402 write_varname (struct outp_driver *d, char *string, int indent)
404 struct outp_text text;
406 text.options = OUTP_T_JUST_LEFT;
407 ls_init (&text.s, string, strlen (string));
408 d->class->text_metrics (d, &text);
410 if (d->cp_x + text.h > d->width)
412 d->cp_y += d->font_height;
413 if (d->cp_y + d->font_height > d->length)
420 d->class->text_draw (d, &text);
424 /* When we can't fit all the values across the page, we write out all
425 the variable names just once. This is where we do it. */
427 write_fallback_headers (struct outp_driver *d)
429 const int max_width = n_chars_width(d) - 10;
435 const char *Line = _("Line");
436 char *leader = local_alloc (strlen (Line) + INT_DIGITS + 1 + 1);
438 while (index < cmd.n_variables)
440 struct outp_text text;
442 /* Ensure that there is enough room for a line of text. */
443 if (d->cp_y + d->font_height > d->length)
446 /* The leader is a string like `Line 1: '. Write the leader. */
447 sprintf(leader, "%s %d:", Line, ++line_number);
448 text.options = OUTP_T_JUST_LEFT;
449 ls_init (&text.s, leader, strlen (leader));
452 d->class->text_draw (d, &text);
462 int var_width = cmd.v_variables[index]->print.w;
463 if (width + var_width > max_width && width != 0)
467 d->cp_y += d->font_height;
475 sprintf (varname, " %s", cmd.v_variables[index]->name);
476 write_varname (d, varname, text.h);
479 while (++index < cmd.n_variables);
483 d->cp_y += d->font_height;
488 /* There are three possible layouts for the LIST procedure:
490 1. If the values and their variables' name fit across the page,
491 then they are listed across the page in that way.
493 2. If the values can fit across the page, but not the variable
494 names, then as many variable names as necessary are printed
495 vertically to compensate.
497 3. If not even the values can fit across the page, the variable
498 names are listed just once, at the beginning, in a compact format,
499 and the values are listed with a variable name label at the
500 beginning of each line for easier reference.
502 This is complicated by the fact that we have to do all this for
503 every output driver, not just once. */
505 determine_layout (void)
507 struct outp_driver *d;
509 /* This is the largest page width of any driver, so we can tell what
510 size buffer to allocate. */
511 int largest_page_width = 0;
513 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
515 int column; /* Current column. */
516 int width; /* Accumulated width. */
517 int max_width; /* Page width. */
519 struct list_ext *prc;
521 if (d->class == &html_class)
524 assert (d->class->special == 0);
527 d->class->open_page (d);
529 max_width = n_chars_width (d);
530 largest_page_width = max (largest_page_width, max_width);
532 prc = d->prc = xmalloc (sizeof *prc);
538 for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
540 struct variable *v = cmd.v_variables[column];
541 width += max (v->print.w, (int) strlen (v->name));
543 if (width <= max_width)
545 prc->header_rows = 2;
546 d->class->text_set_font_by_name (d, "FIXED");
551 for (width = cmd.n_variables - 1, column = 0;
552 column < cmd.n_variables && width <= max_width;
554 width += cmd.v_variables[column]->print.w;
556 /* If it fit then we need to determine how many labels can be
557 written horizontally. */
558 if (width <= max_width)
561 prc->n_vertical = -1;
563 for (column = cmd.n_variables - 1; column >= 0; column--)
565 struct variable *v = cmd.v_variables[column];
566 int trial_width = (width - v->print.w
567 + max (v->print.w, (int) strlen (v->name)));
569 if (trial_width > max_width)
571 prc->n_vertical = column + 1;
576 assert(prc->n_vertical != -1);
578 prc->n_vertical = cmd.n_variables;
579 /* Finally determine the length of the headers. */
580 for (prc->header_rows = 0, column = 0;
581 column < prc->n_vertical;
583 prc->header_rows = max (prc->header_rows,
584 (int) strlen (cmd.v_variables[column]->name));
587 d->class->text_set_font_by_name (d, "FIXED");
591 /* Otherwise use the ugly fallback listing format. */
593 prc->header_rows = 0;
595 d->cp_y += d->font_height;
596 write_fallback_headers (d);
597 d->cp_y += d->font_height;
598 d->class->text_set_font_by_name (d, "FIXED");
601 line_buf = xmalloc (max (1022, largest_page_width) + 2);
605 list_cases (struct ccase *c)
607 struct outp_driver *d;
610 if (case_num < cmd.first || case_num > cmd.last
611 || (cmd.step != 1 && (case_num - cmd.first) % cmd.step))
614 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
615 if (d->class->special == 0)
617 const struct list_ext *prc = d->prc;
618 const int max_width = n_chars_width (d);
622 if (!prc->header_rows)
623 x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
625 for (column = 0; column < cmd.n_variables; column++)
627 struct variable *v = cmd.v_variables[column];
630 if (prc->type == 0 && column >= prc->n_vertical)
631 width = max ((int) strlen (v->name), v->print.w);
635 if (width + x > max_width && x != 0)
637 if (!n_lines_remaining (d))
644 write_line (d, line_buf);
647 if (!prc->header_rows)
648 x = nsprintf (line_buf, "%8s: ", v->name);
651 if (width > v->print.w)
653 memset(&line_buf[x], ' ', width - v->print.w);
654 x += width - v->print.w;
660 if (formats[v->print.type].cat & FCAT_STRING)
661 value.c = c->data[v->fv].s;
662 else if (v->fv == -1)
665 value.f = c->data[v->fv].f;
667 data_out (&line_buf[x], &v->print, &value);
674 if (!n_lines_remaining (d))
681 write_line (d, line_buf);
683 else if (d->class == &html_class)
685 struct html_driver_ext *x = d->ext;
688 fputs (" <TR>\n", x->file.file);
690 for (column = 0; column < cmd.n_variables; column++)
692 struct variable *v = cmd.v_variables[column];
696 if (formats[v->print.type].cat & FCAT_STRING)
697 value.c = c->data[v->fv].s;
698 else if (v->fv == -1)
701 value.f = c->data[v->fv].f;
703 data_out (buf, &v->print, &value);
706 fprintf (x->file.file, " <TD ALIGN=RIGHT>%s</TD>\n",
707 &buf[strspn (buf, " ")]);
710 fputs (" </TR>\n", x->file.file);
718 /* Debugging output. */
721 /* Prints out the command as parsed by cmd_list(). */
728 printf (" VARIABLES=");
729 for (i = 0; i < cmd.n_variables; i++)
733 fputs (cmd.v_variables[i]->name, stdout);
736 printf ("\n /CASES=FROM %ld TO %ld BY %ld\n", cmd.first, cmd.last, cmd.step);
738 fputs (" /FORMAT=", stdout);
739 if (cmd.numbering == LST_NUMBERED)
740 fputs ("NUMBERED", stdout);
742 fputs ("UNNUMBERED", stdout);
744 if (cmd.wrap == LST_WRAP)
745 fputs ("WRAP", stdout);
747 fputs ("SINGLE", stdout);
749 if (cmd.weight == LST_WEIGHT)
750 fputs ("WEIGHT", stdout);
752 fputs ("NOWEIGHT", stdout);
755 #endif /* DEBUGGING */