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"
43 #define _(msgid) gettext (msgid)
47 #include "debug-print.h"
51 *variables=varlist("PV_NO_SCRATCH");
52 cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
53 format=numbering:numbered/!unnumbered,
55 weight:weight/!noweight.
60 /* Layout for one output driver. */
63 int type; /* 0=Values and labels fit across the page. */
64 size_t n_vertical; /* Number of labels to list vertically. */
65 size_t header_rows; /* Number of header rows. */
66 char **header; /* The header itself. */
70 static struct cmd_list cmd;
72 /* Current case number. */
76 static char *line_buf;
78 /* TTY-style output functions. */
79 static unsigned n_lines_remaining (struct outp_driver *d);
80 static unsigned n_chars_width (struct outp_driver *d);
81 static void write_line (struct outp_driver *d, char *s);
83 /* Other functions. */
84 static int list_cases (struct ccase *, void *);
85 static void determine_layout (void);
86 static void clean_up (void);
87 static void write_header (struct outp_driver *);
88 static void write_all_headers (void *);
90 /* Returns the number of text lines that can fit on the remainder of
92 static inline unsigned
93 n_lines_remaining (struct outp_driver *d)
97 diff = d->length - d->cp_y;
98 return (diff > 0) ? (diff / d->font_height) : 0;
101 /* Returns the number of fixed-width character that can fit across the
103 static inline unsigned
104 n_chars_width (struct outp_driver *d)
106 return d->width / d->fixed_width;
109 /* Writes the line S at the current position and advances to the next
112 write_line (struct outp_driver *d, char *s)
114 struct outp_text text;
116 assert (d->cp_y + d->font_height <= d->length);
117 text.options = OUTP_T_JUST_LEFT;
118 ls_init (&text.s, s, strlen (s));
121 d->class->text_draw (d, &text);
123 d->cp_y += d->font_height;
126 /* Parses and executes the LIST procedure. */
130 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 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. */
304 /* Allocate, initialize header. */
305 prc->header = xnmalloc (prc->header_rows, sizeof *prc->header);
307 int w = n_chars_width (d);
308 for (i = 0; i < prc->header_rows; i++)
310 prc->header[i] = xmalloc (w + 1);
311 memset (prc->header[i], ' ', w);
315 /* Put in vertical names. */
316 for (i = x = 0; i < prc->n_vertical; i++)
318 struct variable *v = cmd.v_variables[i];
321 memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
323 for (j = 0; j < strlen (v->name); j++)
324 prc->header[strlen (v->name) - j - 1][x] = v->name[j];
328 /* Put in horizontal names. */
329 for (; i < cmd.n_variables; i++)
331 struct variable *v = cmd.v_variables[i];
333 memset (&prc->header[prc->header_rows - 1][x], '-',
334 max (v->print.w, (int) strlen (v->name)));
335 if ((int) strlen (v->name) < v->print.w)
336 x += v->print.w - strlen (v->name);
337 memcpy (&prc->header[0][x], v->name, strlen (v->name));
338 x += strlen (v->name) + 1;
341 /* Add null bytes. */
342 for (i = 0; i < prc->header_rows; i++)
344 for (x = n_chars_width (d); x >= 1; x--)
345 if (prc->header[i][x - 1] != ' ')
347 prc->header[i][x] = 0;
354 /* Write out the header, in back-to-front order except for the last line. */
355 if (prc->header_rows >= 2)
359 for (i = prc->header_rows - 1; i-- != 0; )
360 write_line (d, prc->header[i]);
362 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 size_t column; /* Current column. */
525 int width; /* Accumulated width. */
526 int height; /* Height of vertical names. */
527 int max_width; /* Page width. */
529 struct list_ext *prc;
531 if (d->class == &html_class)
533 else if (d->class == &devind_class)
536 tab_output_text (TAT_NONE, "(devind not supported on LIST yet)");
540 assert (d->class->special == 0);
543 d->class->open_page (d);
545 max_width = n_chars_width (d);
546 largest_page_width = max (largest_page_width, max_width);
548 prc = d->prc = xmalloc (sizeof *prc);
554 for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
556 struct variable *v = cmd.v_variables[column];
557 width += max (v->print.w, (int) strlen (v->name));
559 if (width <= max_width)
561 prc->header_rows = 2;
562 d->class->text_set_font_by_name (d, "FIXED");
567 for (width = cmd.n_variables - 1, height = 0, column = 0;
568 column < cmd.n_variables && width <= max_width;
571 struct variable *v = cmd.v_variables[column];
573 if (strlen (v->name) > height)
574 height = strlen (v->name);
577 /* If it fit then we need to determine how many labels can be
578 written horizontally. */
579 if (width <= max_width && height <= SHORT_NAME_LEN)
582 prc->n_vertical = SIZE_MAX;
584 for (column = cmd.n_variables; column-- != 0; )
586 struct variable *v = cmd.v_variables[column];
587 int trial_width = (width - v->print.w
588 + max (v->print.w, (int) strlen (v->name)));
590 if (trial_width > max_width)
592 prc->n_vertical = column + 1;
597 assert (prc->n_vertical != SIZE_MAX);
599 prc->n_vertical = cmd.n_variables;
600 /* Finally determine the length of the headers. */
601 for (prc->header_rows = 0, column = 0;
602 column < prc->n_vertical;
604 prc->header_rows = max (prc->header_rows,
605 strlen (cmd.v_variables[column]->name));
608 d->class->text_set_font_by_name (d, "FIXED");
612 /* Otherwise use the ugly fallback listing format. */
614 prc->header_rows = 0;
616 d->cp_y += d->font_height;
617 write_fallback_headers (d);
618 d->cp_y += d->font_height;
619 d->class->text_set_font_by_name (d, "FIXED");
622 line_buf = xmalloc (max (1022, largest_page_width) + 2);
626 list_cases (struct ccase *c, void *aux UNUSED)
628 struct outp_driver *d;
631 if (case_idx < cmd.first || case_idx > cmd.last
632 || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
635 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
636 if (d->class->special == 0)
638 const struct list_ext *prc = d->prc;
639 const int max_width = n_chars_width (d);
643 if (!prc->header_rows)
644 x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
646 for (column = 0; column < cmd.n_variables; column++)
648 struct variable *v = cmd.v_variables[column];
651 if (prc->type == 0 && column >= prc->n_vertical)
652 width = max ((int) strlen (v->name), v->print.w);
656 if (width + x > max_width && x != 0)
658 if (!n_lines_remaining (d))
665 write_line (d, line_buf);
668 if (!prc->header_rows)
669 x = nsprintf (line_buf, "%8s: ", v->name);
672 if (width > v->print.w)
674 memset(&line_buf[x], ' ', width - v->print.w);
675 x += width - v->print.w;
678 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
679 data_out (&line_buf[x], &v->print, case_data (c, v->fv));
682 union value case_idx_value;
683 case_idx_value.f = case_idx;
684 data_out (&line_buf[x], &v->print, &case_idx_value);
691 if (!n_lines_remaining (d))
698 write_line (d, line_buf);
700 else if (d->class == &html_class)
702 struct html_driver_ext *x = d->ext;
705 fputs (" <TR>\n", x->file.file);
707 for (column = 0; column < cmd.n_variables; column++)
709 struct variable *v = cmd.v_variables[column];
712 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
713 data_out (buf, &v->print, case_data (c, v->fv));
716 union value case_idx_value;
717 case_idx_value.f = case_idx;
718 data_out (buf, &v->print, &case_idx_value);
722 fprintf (x->file.file, " <TD ALIGN=RIGHT>%s</TD>\n",
723 &buf[strspn (buf, " ")]);
726 fputs (" </TR>\n", x->file.file);
728 else if (d->class == &devind_class)