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"
39 #include "procedure.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 bool 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;
133 if (!parse_list (&cmd))
136 /* Fill in defaults. */
137 if (cmd.step == NOT_LONG)
139 if (cmd.first == NOT_LONG)
141 if (cmd.last == NOT_LONG)
143 if (!cmd.sbc_variables)
144 dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
145 (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
146 if (cmd.n_variables == 0)
148 msg (SE, _("No variables specified."));
152 /* Verify arguments. */
153 if (cmd.first > cmd.last)
156 msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
157 "specified. The values will be swapped."), cmd.first, cmd.last);
159 cmd.first = cmd.last;
164 msg (SW, _("The first case (%ld) to list is less than 1. The value is "
165 "being reset to 1."), cmd.first);
170 msg (SW, _("The last case (%ld) to list is less than 1. The value is "
171 "being reset to 1."), cmd.last);
176 msg (SW, _("The step value %ld is less than 1. The value is being "
177 "reset to 1."), cmd.step);
181 /* Weighting variable. */
182 if (cmd.weight == LST_WEIGHT)
184 if (dict_get_weight (default_dict) != NULL)
188 for (i = 0; i < cmd.n_variables; i++)
189 if (cmd.v_variables[i] == dict_get_weight (default_dict))
191 if (i >= cmd.n_variables)
193 /* Add the weight variable to the end of the variable list. */
195 cmd.v_variables = xnrealloc (cmd.v_variables, cmd.n_variables,
196 sizeof *cmd.v_variables);
197 cmd.v_variables[cmd.n_variables - 1]
198 = dict_get_weight (default_dict);
202 msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
206 if (cmd.numbering == LST_NUMBERED)
208 /* Initialize the case-number variable. */
209 strcpy (casenum_var.name, "Case#");
210 casenum_var.type = NUMERIC;
212 casenum_var.print = make_output_format (FMT_F,
213 (cmd.last == LONG_MAX
214 ? 5 : intlog10 (cmd.last)), 0);
216 /* Add the weight variable at the beginning of the variable list. */
218 cmd.v_variables = xnrealloc (cmd.v_variables,
219 cmd.n_variables, sizeof *cmd.v_variables);
220 memmove (&cmd.v_variables[1], &cmd.v_variables[0],
221 (cmd.n_variables - 1) * sizeof *cmd.v_variables);
222 cmd.v_variables[0] = &casenum_var;
228 ok = procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
233 return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
236 /* Writes headers to all devices. This is done at the beginning of
237 each SPLIT FILE group. */
239 write_all_headers (void *aux UNUSED)
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);
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. */
301 /* Allocate, initialize header. */
302 prc->header = xnmalloc (prc->header_rows, sizeof *prc->header);
304 int w = n_chars_width (d);
305 for (i = 0; i < prc->header_rows; i++)
307 prc->header[i] = xmalloc (w + 1);
308 memset (prc->header[i], ' ', w);
312 /* Put in vertical names. */
313 for (i = x = 0; i < prc->n_vertical; i++)
315 struct variable *v = cmd.v_variables[i];
318 memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
320 for (j = 0; j < strlen (v->name); j++)
321 prc->header[strlen (v->name) - j - 1][x] = v->name[j];
325 /* Put in horizontal names. */
326 for (; i < cmd.n_variables; i++)
328 struct variable *v = cmd.v_variables[i];
330 memset (&prc->header[prc->header_rows - 1][x], '-',
331 max (v->print.w, (int) strlen (v->name)));
332 if ((int) strlen (v->name) < v->print.w)
333 x += v->print.w - strlen (v->name);
334 memcpy (&prc->header[0][x], v->name, strlen (v->name));
335 x += strlen (v->name) + 1;
338 /* Add null bytes. */
339 for (i = 0; i < prc->header_rows; i++)
341 for (x = n_chars_width (d); x >= 1; x--)
342 if (prc->header[i][x - 1] != ' ')
344 prc->header[i][x] = 0;
351 /* Write out the header, in back-to-front order except for the last line. */
352 if (prc->header_rows >= 2)
356 for (i = prc->header_rows - 1; i-- != 0; )
357 write_line (d, prc->header[i]);
359 write_line (d, prc->header[prc->header_rows - 1]);
363 /* Frees up all the memory we've allocated. */
367 struct outp_driver *d;
369 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
370 if (d->class->special == 0)
372 struct list_ext *prc = d->prc;
377 for (i = 0; i < prc->header_rows; i++)
378 free (prc->header[i]);
383 d->class->text_set_font_by_name (d, "PROP");
385 else if (d->class == &html_class)
387 if (d->driver_open && d->page_open)
389 struct html_driver_ext *x = d->ext;
391 fputs ("</TABLE>\n", x->file.file);
397 free (cmd.v_variables);
400 /* Writes string STRING at the current position. If the text would
401 fall off the side of the page, then advance to the next line,
402 indenting by amount INDENT. */
404 write_varname (struct outp_driver *d, char *string, int indent)
406 struct outp_text text;
408 text.options = OUTP_T_JUST_LEFT;
409 ls_init (&text.s, string, strlen (string));
410 d->class->text_metrics (d, &text);
412 if (d->cp_x + text.h > d->width)
414 d->cp_y += d->font_height;
415 if (d->cp_y + d->font_height > d->length)
422 d->class->text_draw (d, &text);
426 /* When we can't fit all the values across the page, we write out all
427 the variable names just once. This is where we do it. */
429 write_fallback_headers (struct outp_driver *d)
431 const int max_width = n_chars_width(d) - 10;
437 const char *Line = _("Line");
438 char *leader = local_alloc (strlen (Line)
439 + INT_STRLEN_BOUND (line_number) + 1 + 1);
441 while (index < cmd.n_variables)
443 struct outp_text text;
445 /* Ensure that there is enough room for a line of text. */
446 if (d->cp_y + d->font_height > d->length)
449 /* The leader is a string like `Line 1: '. Write the leader. */
450 sprintf(leader, "%s %d:", Line, ++line_number);
451 text.options = OUTP_T_JUST_LEFT;
452 ls_init (&text.s, leader, strlen (leader));
455 d->class->text_draw (d, &text);
465 int var_width = cmd.v_variables[index]->print.w;
466 if (width + var_width > max_width && width != 0)
470 d->cp_y += d->font_height;
478 sprintf (varname, " %s", cmd.v_variables[index]->name);
479 write_varname (d, varname, text.h);
482 while (++index < cmd.n_variables);
486 d->cp_y += d->font_height;
491 /* There are three possible layouts for the LIST procedure:
493 1. If the values and their variables' name fit across the page,
494 then they are listed across the page in that way.
496 2. If the values can fit across the page, but not the variable
497 names, then as many variable names as necessary are printed
498 vertically to compensate.
500 3. If not even the values can fit across the page, the variable
501 names are listed just once, at the beginning, in a compact format,
502 and the values are listed with a variable name label at the
503 beginning of each line for easier reference.
505 This is complicated by the fact that we have to do all this for
506 every output driver, not just once. */
508 determine_layout (void)
510 struct outp_driver *d;
512 /* This is the largest page width of any driver, so we can tell what
513 size buffer to allocate. */
514 int largest_page_width = 0;
516 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
518 size_t column; /* Current column. */
519 int width; /* Accumulated width. */
520 int height; /* Height of vertical names. */
521 int max_width; /* Page width. */
523 struct list_ext *prc;
525 if (d->class == &html_class)
528 assert (d->class->special == 0);
531 d->class->open_page (d);
533 max_width = n_chars_width (d);
534 largest_page_width = max (largest_page_width, max_width);
536 prc = d->prc = xmalloc (sizeof *prc);
542 for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
544 struct variable *v = cmd.v_variables[column];
545 width += max (v->print.w, (int) strlen (v->name));
547 if (width <= max_width)
549 prc->header_rows = 2;
550 d->class->text_set_font_by_name (d, "FIXED");
555 for (width = cmd.n_variables - 1, height = 0, column = 0;
556 column < cmd.n_variables && width <= max_width;
559 struct variable *v = cmd.v_variables[column];
561 if (strlen (v->name) > height)
562 height = strlen (v->name);
565 /* If it fit then we need to determine how many labels can be
566 written horizontally. */
567 if (width <= max_width && height <= SHORT_NAME_LEN)
570 prc->n_vertical = SIZE_MAX;
572 for (column = cmd.n_variables; column-- != 0; )
574 struct variable *v = cmd.v_variables[column];
575 int trial_width = (width - v->print.w
576 + max (v->print.w, (int) strlen (v->name)));
578 if (trial_width > max_width)
580 prc->n_vertical = column + 1;
585 assert (prc->n_vertical != SIZE_MAX);
587 prc->n_vertical = cmd.n_variables;
588 /* Finally determine the length of the headers. */
589 for (prc->header_rows = 0, column = 0;
590 column < prc->n_vertical;
592 prc->header_rows = max (prc->header_rows,
593 strlen (cmd.v_variables[column]->name));
596 d->class->text_set_font_by_name (d, "FIXED");
600 /* Otherwise use the ugly fallback listing format. */
602 prc->header_rows = 0;
604 d->cp_y += d->font_height;
605 write_fallback_headers (d);
606 d->cp_y += d->font_height;
607 d->class->text_set_font_by_name (d, "FIXED");
610 line_buf = xmalloc (max (1022, largest_page_width) + 2);
613 /* Writes case C to output. */
615 list_cases (struct ccase *c, void *aux UNUSED)
617 struct outp_driver *d;
620 if (case_idx < cmd.first || case_idx > cmd.last
621 || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
624 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
625 if (d->class->special == 0)
627 const struct list_ext *prc = d->prc;
628 const int max_width = n_chars_width (d);
632 if (!prc->header_rows)
633 x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
635 for (column = 0; column < cmd.n_variables; column++)
637 struct variable *v = cmd.v_variables[column];
640 if (prc->type == 0 && column >= prc->n_vertical)
641 width = max ((int) strlen (v->name), v->print.w);
645 if (width + x > max_width && x != 0)
647 if (!n_lines_remaining (d))
654 write_line (d, line_buf);
657 if (!prc->header_rows)
658 x = nsprintf (line_buf, "%8s: ", v->name);
661 if (width > v->print.w)
663 memset(&line_buf[x], ' ', width - v->print.w);
664 x += width - v->print.w;
667 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
668 data_out (&line_buf[x], &v->print, case_data (c, v->fv));
671 union value case_idx_value;
672 case_idx_value.f = case_idx;
673 data_out (&line_buf[x], &v->print, &case_idx_value);
680 if (!n_lines_remaining (d))
687 write_line (d, line_buf);
689 else if (d->class == &html_class)
691 struct html_driver_ext *x = d->ext;
694 fputs (" <TR>\n", x->file.file);
696 for (column = 0; column < cmd.n_variables; column++)
698 struct variable *v = cmd.v_variables[column];
701 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
702 data_out (buf, &v->print, case_data (c, v->fv));
705 union value case_idx_value;
706 case_idx_value.f = case_idx;
707 data_out (buf, &v->print, &case_idx_value);
711 fprintf (x->file.file, " <TD ALIGN=RIGHT>%s</TD>\n",
712 &buf[strspn (buf, " ")]);
715 fputs (" </TR>\n", x->file.file);