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"
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 int 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;
131 if (!parse_list (&cmd))
134 /* Fill in defaults. */
135 if (cmd.step == NOT_LONG)
137 if (cmd.first == NOT_LONG)
139 if (cmd.last == NOT_LONG)
141 if (!cmd.sbc_variables)
142 dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
143 (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
144 if (cmd.n_variables == 0)
146 msg (SE, _("No variables specified."));
150 /* Verify arguments. */
151 if (cmd.first > cmd.last)
154 msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
155 "specified. The values will be swapped."), cmd.first, cmd.last);
157 cmd.first = cmd.last;
162 msg (SW, _("The first case (%ld) to list is less than 1. The value is "
163 "being reset to 1."), cmd.first);
168 msg (SW, _("The last case (%ld) to list is less than 1. The value is "
169 "being reset to 1."), cmd.last);
174 msg (SW, _("The step value %ld is less than 1. The value is being "
175 "reset to 1."), cmd.step);
179 /* Weighting variable. */
180 if (cmd.weight == LST_WEIGHT)
182 if (dict_get_weight (default_dict) != NULL)
186 for (i = 0; i < cmd.n_variables; i++)
187 if (cmd.v_variables[i] == dict_get_weight (default_dict))
189 if (i >= cmd.n_variables)
191 /* Add the weight variable to the end of the variable list. */
193 cmd.v_variables = xnrealloc (cmd.v_variables, cmd.n_variables,
194 sizeof *cmd.v_variables);
195 cmd.v_variables[cmd.n_variables - 1]
196 = dict_get_weight (default_dict);
200 msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
204 if (cmd.numbering == LST_NUMBERED)
206 /* Initialize the case-number variable. */
207 strcpy (casenum_var.name, "Case#");
208 casenum_var.type = NUMERIC;
210 casenum_var.print = make_output_format (FMT_F,
211 (cmd.last == LONG_MAX
212 ? 5 : intlog10 (cmd.last)), 0);
214 /* Add the weight variable at the beginning of the variable list. */
216 cmd.v_variables = xnrealloc (cmd.v_variables,
217 cmd.n_variables, sizeof *cmd.v_variables);
218 memmove (&cmd.v_variables[1], &cmd.v_variables[0],
219 (cmd.n_variables - 1) * sizeof *cmd.v_variables);
220 cmd.v_variables[0] = &casenum_var;
226 procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
234 /* Writes headers to all devices. This is done at the beginning of
235 each SPLIT FILE group. */
237 write_all_headers (void *aux UNUSED)
239 struct outp_driver *d;
241 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
243 if (!d->class->special)
245 d->cp_y += d->font_height; /* Blank line. */
248 else if (d->class == &html_class)
250 struct html_driver_ext *x = d->ext;
252 assert (d->driver_open);
253 if (x->sequence_no == 0 && !d->class->open_page (d))
255 msg (ME, _("Cannot open first page on HTML device %s."),
260 fputs ("<TABLE BORDER=1>\n <TR>\n", x->file.file);
265 for (i = 0; i < cmd.n_variables; i++)
266 fprintf (x->file.file, " <TH><I><B>%s</B></I></TH>\n",
267 cmd.v_variables[i]->name);
270 fputs (" <TR>\n", x->file.file);
277 /* Writes the headers. Some of them might be vertical; most are
278 probably horizontal. */
280 write_header (struct outp_driver *d)
282 struct list_ext *prc = d->prc;
284 if (!prc->header_rows)
287 if (n_lines_remaining (d) < prc->header_rows + 1)
290 assert (n_lines_remaining (d) >= prc->header_rows + 1);
293 /* Design the header. */
299 /* Allocate, initialize header. */
300 prc->header = xnmalloc (prc->header_rows, sizeof *prc->header);
302 int w = n_chars_width (d);
303 for (i = 0; i < prc->header_rows; i++)
305 prc->header[i] = xmalloc (w + 1);
306 memset (prc->header[i], ' ', w);
310 /* Put in vertical names. */
311 for (i = x = 0; i < prc->n_vertical; i++)
313 struct variable *v = cmd.v_variables[i];
316 memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
318 for (j = 0; j < strlen (v->name); j++)
319 prc->header[strlen (v->name) - j - 1][x] = v->name[j];
323 /* Put in horizontal names. */
324 for (; i < cmd.n_variables; i++)
326 struct variable *v = cmd.v_variables[i];
328 memset (&prc->header[prc->header_rows - 1][x], '-',
329 max (v->print.w, (int) strlen (v->name)));
330 if ((int) strlen (v->name) < v->print.w)
331 x += v->print.w - strlen (v->name);
332 memcpy (&prc->header[0][x], v->name, strlen (v->name));
333 x += strlen (v->name) + 1;
336 /* Add null bytes. */
337 for (i = 0; i < prc->header_rows; i++)
339 for (x = n_chars_width (d); x >= 1; x--)
340 if (prc->header[i][x - 1] != ' ')
342 prc->header[i][x] = 0;
349 /* Write out the header, in back-to-front order except for the last line. */
350 if (prc->header_rows >= 2)
354 for (i = prc->header_rows - 1; i-- != 0; )
355 write_line (d, prc->header[i]);
357 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 size_t column; /* Current column. */
516 int width; /* Accumulated width. */
517 int height; /* Height of vertical names. */
518 int max_width; /* Page width. */
520 struct list_ext *prc;
522 if (d->class == &html_class)
525 assert (d->class->special == 0);
528 d->class->open_page (d);
530 max_width = n_chars_width (d);
531 largest_page_width = max (largest_page_width, max_width);
533 prc = d->prc = xmalloc (sizeof *prc);
539 for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
541 struct variable *v = cmd.v_variables[column];
542 width += max (v->print.w, (int) strlen (v->name));
544 if (width <= max_width)
546 prc->header_rows = 2;
547 d->class->text_set_font_by_name (d, "FIXED");
552 for (width = cmd.n_variables - 1, height = 0, column = 0;
553 column < cmd.n_variables && width <= max_width;
556 struct variable *v = cmd.v_variables[column];
558 if (strlen (v->name) > height)
559 height = strlen (v->name);
562 /* If it fit then we need to determine how many labels can be
563 written horizontally. */
564 if (width <= max_width && height <= SHORT_NAME_LEN)
567 prc->n_vertical = SIZE_MAX;
569 for (column = cmd.n_variables; column-- != 0; )
571 struct variable *v = cmd.v_variables[column];
572 int trial_width = (width - v->print.w
573 + max (v->print.w, (int) strlen (v->name)));
575 if (trial_width > max_width)
577 prc->n_vertical = column + 1;
582 assert (prc->n_vertical != SIZE_MAX);
584 prc->n_vertical = cmd.n_variables;
585 /* Finally determine the length of the headers. */
586 for (prc->header_rows = 0, column = 0;
587 column < prc->n_vertical;
589 prc->header_rows = max (prc->header_rows,
590 strlen (cmd.v_variables[column]->name));
593 d->class->text_set_font_by_name (d, "FIXED");
597 /* Otherwise use the ugly fallback listing format. */
599 prc->header_rows = 0;
601 d->cp_y += d->font_height;
602 write_fallback_headers (d);
603 d->cp_y += d->font_height;
604 d->class->text_set_font_by_name (d, "FIXED");
607 line_buf = xmalloc (max (1022, largest_page_width) + 2);
611 list_cases (struct ccase *c, void *aux UNUSED)
613 struct outp_driver *d;
616 if (case_idx < cmd.first || case_idx > cmd.last
617 || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
620 for (d = outp_drivers (NULL); d; d = outp_drivers (d))
621 if (d->class->special == 0)
623 const struct list_ext *prc = d->prc;
624 const int max_width = n_chars_width (d);
628 if (!prc->header_rows)
629 x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
631 for (column = 0; column < cmd.n_variables; column++)
633 struct variable *v = cmd.v_variables[column];
636 if (prc->type == 0 && column >= prc->n_vertical)
637 width = max ((int) strlen (v->name), v->print.w);
641 if (width + x > max_width && x != 0)
643 if (!n_lines_remaining (d))
650 write_line (d, line_buf);
653 if (!prc->header_rows)
654 x = nsprintf (line_buf, "%8s: ", v->name);
657 if (width > v->print.w)
659 memset(&line_buf[x], ' ', width - v->print.w);
660 x += width - v->print.w;
663 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
664 data_out (&line_buf[x], &v->print, case_data (c, v->fv));
667 union value case_idx_value;
668 case_idx_value.f = case_idx;
669 data_out (&line_buf[x], &v->print, &case_idx_value);
676 if (!n_lines_remaining (d))
683 write_line (d, line_buf);
685 else if (d->class == &html_class)
687 struct html_driver_ext *x = d->ext;
690 fputs (" <TR>\n", x->file.file);
692 for (column = 0; column < cmd.n_variables; column++)
694 struct variable *v = cmd.v_variables[column];
697 if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
698 data_out (buf, &v->print, case_data (c, v->fv));
701 union value case_idx_value;
702 case_idx_value.f = case_idx;
703 data_out (buf, &v->print, &case_idx_value);
707 fprintf (x->file.file, " <TD ALIGN=RIGHT>%s</TD>\n",
708 &buf[strspn (buf, " ")]);
711 fputs (" </TR>\n", x->file.file);