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
27 #include "dictionary.h"
38 #include "procedure.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 size_t n_vertical; /* Number of labels to list vertically. */
64 size_t 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 unsigned n_lines_remaining (struct outp_driver *d);
79 static unsigned n_chars_width (struct outp_driver *d);
80 static void write_line (struct outp_driver *d, char *s);
82 /* Other functions. */
83 static bool 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
91 static inline unsigned
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
102 static inline unsigned
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;
132 if (!parse_list (&cmd))
135 /* Fill in defaults. */
136 if (cmd.step == NOT_LONG)
138 if (cmd.first == NOT_LONG)
140 if (cmd.last == NOT_LONG)
142 if (!cmd.sbc_variables)
143 dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
144 (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
145 if (cmd.n_variables == 0)
147 msg (SE, _("No variables specified."));
151 /* Verify arguments. */
152 if (cmd.first > cmd.last)
155 msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
156 "specified. The values will be swapped."), cmd.first, cmd.last);
158 cmd.first = cmd.last;
163 msg (SW, _("The first case (%ld) to list is less than 1. The value is "
164 "being reset to 1."), cmd.first);
169 msg (SW, _("The last case (%ld) to list is less than 1. The value is "
170 "being reset to 1."), cmd.last);
175 msg (SW, _("The step value %ld is less than 1. The value is being "
176 "reset to 1."), cmd.step);
180 /* Weighting variable. */
181 if (cmd.weight == LST_WEIGHT)
183 if (dict_get_weight (default_dict) != NULL)
187 for (i = 0; i < cmd.n_variables; i++)
188 if (cmd.v_variables[i] == dict_get_weight (default_dict))
190 if (i >= cmd.n_variables)
192 /* Add the weight variable to the end of the variable list. */
194 cmd.v_variables = xnrealloc (cmd.v_variables, cmd.n_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 = xnrealloc (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 ok = procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
232 return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
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);
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. */
300 /* Allocate, initialize header. */
301 prc->header = xnmalloc (prc->header_rows, sizeof *prc->header);
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 < 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. */
351 if (prc->header_rows >= 2)
355 for (i = prc->header_rows - 1; i-- != 0; )
356 write_line (d, prc->header[i]);
358 write_line (d, prc->header[prc->header_rows - 1]);
362 /* Frees up all the memory we've allocated. */
366 struct outp_driver *d;
368 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
369 if (d->class->special == 0)
371 struct list_ext *prc = d->prc;
376 for (i = 0; i < prc->header_rows; i++)
377 free (prc->header[i]);
382 d->class->text_set_font_by_name (d, "PROP");
384 else if (d->class == &html_class)
386 if (d->driver_open && d->page_open)
388 struct html_driver_ext *x = d->ext;
390 fputs ("</TABLE>\n", x->file.file);
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 size_t column; /* Current column. */
517 int width; /* Accumulated width. */
518 int height; /* Height of vertical names. */
519 int max_width; /* Page width. */
521 struct list_ext *prc;
523 if (d->class == &html_class)
526 assert (d->class->special == 0);
529 d->class->open_page (d);
531 max_width = n_chars_width (d);
532 largest_page_width = max (largest_page_width, max_width);
534 prc = d->prc = xmalloc (sizeof *prc);
540 for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
542 struct variable *v = cmd.v_variables[column];
543 width += max (v->print.w, (int) strlen (v->name));
545 if (width <= max_width)
547 prc->header_rows = 2;
548 d->class->text_set_font_by_name (d, "FIXED");
553 for (width = cmd.n_variables - 1, height = 0, column = 0;
554 column < cmd.n_variables && width <= max_width;
557 struct variable *v = cmd.v_variables[column];
559 if (strlen (v->name) > height)
560 height = strlen (v->name);
563 /* If it fit then we need to determine how many labels can be
564 written horizontally. */
565 if (width <= max_width && height <= SHORT_NAME_LEN)
568 prc->n_vertical = SIZE_MAX;
570 for (column = cmd.n_variables; column-- != 0; )
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 != SIZE_MAX);
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 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);
611 /* Writes case C to output. */
613 list_cases (struct ccase *c, void *aux UNUSED)
615 struct outp_driver *d;
618 if (case_idx < cmd.first || case_idx > cmd.last
619 || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
622 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
623 if (d->class->special == 0)
625 const struct list_ext *prc = d->prc;
626 const int max_width = n_chars_width (d);
630 if (!prc->header_rows)
631 x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
633 for (column = 0; column < cmd.n_variables; column++)
635 struct variable *v = cmd.v_variables[column];
638 if (prc->type == 0 && column >= prc->n_vertical)
639 width = max ((int) strlen (v->name), v->print.w);
643 if (width + x > max_width && x != 0)
645 if (!n_lines_remaining (d))
652 write_line (d, line_buf);
655 if (!prc->header_rows)
656 x = nsprintf (line_buf, "%8s: ", v->name);
659 if (width > v->print.w)
661 memset(&line_buf[x], ' ', width - v->print.w);
662 x += width - v->print.w;
665 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
666 data_out (&line_buf[x], &v->print, case_data (c, v->fv));
669 union value case_idx_value;
670 case_idx_value.f = case_idx;
671 data_out (&line_buf[x], &v->print, &case_idx_value);
678 if (!n_lines_remaining (d))
685 write_line (d, line_buf);
687 else if (d->class == &html_class)
689 struct html_driver_ext *x = d->ext;
692 fputs (" <TR>\n", x->file.file);
694 for (column = 0; column < cmd.n_variables; column++)
696 struct variable *v = cmd.v_variables[column];
699 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
700 data_out (buf, &v->print, case_data (c, v->fv));
703 union value case_idx_value;
704 case_idx_value.f = case_idx;
705 data_out (buf, &v->print, &case_idx_value);
709 fprintf (x->file.file, " <TD ALIGN=RIGHT>%s</TD>\n",
710 &buf[strspn (buf, " ")]);
713 fputs (" </TR>\n", x->file.file);