Redo VFM interface. Get rid of compaction_necessary, compaction_nval,
[pspp-builds.git] / src / list.q
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
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.
9
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.
14
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
18    02111-1307, USA. */
19
20 #include <config.h>
21 #include <assert.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include "alloc.h"
25 #include "command.h"
26 #include "devind.h"
27 #include "lexer.h"
28 #include "error.h"
29 #include "magic.h"
30 #include "misc.h"
31 #include "htmlP.h"
32 #include "output.h"
33 #include "som.h"
34 #include "tab.h"
35 #include "var.h"
36 #include "vfm.h"
37 #include "format.h"
38
39 #include "debug-print.h"
40
41 #if DEBUGGING
42 static void debug_print (void);
43 #endif
44
45 /* (specification)
46    list (lst_):
47      *variables=varlist("PV_NO_SCRATCH");
48      cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
49      format=numbering:numbered/!unnumbered,
50             wrap:!wrap/single,
51             weight:weight/!noweight.
52 */
53 /* (declarations) */
54 /* (functions) */
55
56 /* Layout for one output driver. */
57 struct list_ext
58   {
59     int type;           /* 0=Values and labels fit across the page. */
60     int n_vertical;     /* Number of labels to list vertically. */
61     int header_rows;    /* Number of header rows. */
62     char **header;      /* The header itself. */
63   };
64
65 /* Parsed command. */
66 static struct cmd_list cmd;
67
68 /* Current case number. */
69 static int case_num;
70
71 /* Line buffer. */
72 static char *line_buf;
73
74 /* TTY-style output functions. */
75 static int n_lines_remaining (struct outp_driver *d);
76 static int n_chars_width (struct outp_driver *d);
77 static void write_line (struct outp_driver *d, char *s);
78
79 /* Other functions. */
80 static int list_cases (struct ccase *, void *);
81 static void determine_layout (void);
82 static void clean_up (void);
83 static void write_header (struct outp_driver *);
84 static void write_all_headers (void *);
85
86 /* Returns the number of text lines that can fit on the remainder of
87    the page. */
88 static inline int
89 n_lines_remaining (struct outp_driver *d)
90 {
91   int diff;
92
93   diff = d->length - d->cp_y;
94   return (diff > 0) ? (diff / d->font_height) : 0;
95 }
96
97 /* Returns the number of fixed-width character that can fit across the
98    page. */
99 static inline int
100 n_chars_width (struct outp_driver *d)
101 {
102   return d->width / d->fixed_width;
103 }
104
105 /* Writes the line S at the current position and advances to the next
106    line.  */
107 static void
108 write_line (struct outp_driver *d, char *s)
109 {
110   struct outp_text text;
111   
112   assert (d->cp_y + d->font_height <= d->length);
113   text.options = OUTP_T_JUST_LEFT;
114   ls_init (&text.s, s, strlen (s));
115   text.x = d->cp_x;
116   text.y = d->cp_y;
117   d->class->text_draw (d, &text);
118   d->cp_x = 0;
119   d->cp_y += d->font_height;
120 }
121     
122 /* Parses and executes the LIST procedure. */
123 int
124 cmd_list (void)
125 {
126   struct variable casenum_var;
127
128   lex_match_id ("LIST");
129   if (!parse_list (&cmd))
130     return CMD_FAILURE;
131   
132   /* Fill in defaults. */
133   if (cmd.step == NOT_LONG)
134     cmd.step = 1;
135   if (cmd.first == NOT_LONG)
136     cmd.first = 1;
137   if (cmd.last == NOT_LONG)
138     cmd.last = LONG_MAX;
139   if (!cmd.sbc_variables)
140     dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
141                    (1u << DC_SYSTEM) | (1u << DC_SCRATCH));
142   if (cmd.n_variables == 0)
143     {
144       msg (SE, _("No variables specified."));
145       return CMD_FAILURE;
146     }
147
148   /* Verify arguments. */
149   if (cmd.first > cmd.last)
150     {
151       int t;
152       msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
153            "specified.  The values will be swapped."), cmd.first, cmd.last);
154       t = cmd.first;
155       cmd.first = cmd.last;
156       cmd.last = t;
157     }
158   if (cmd.first < 1)
159     {
160       msg (SW, _("The first case (%ld) to list is less than 1.  The value is "
161            "being reset to 1."), cmd.first);
162       cmd.first = 1;
163     }
164   if (cmd.last < 1)
165     {
166       msg (SW, _("The last case (%ld) to list is less than 1.  The value is "
167            "being reset to 1."), cmd.last);
168       cmd.last = 1;
169     }
170   if (cmd.step < 1)
171     {
172       msg (SW, _("The step value %ld is less than 1.  The value is being "
173            "reset to 1."), cmd.step);
174       cmd.step = 1;
175     }
176
177   /* Weighting variable. */
178   if (cmd.weight == LST_WEIGHT)
179     {
180       if (dict_get_weight (default_dict) != NULL)
181         {
182           int i;
183
184           for (i = 0; i < cmd.n_variables; i++)
185             if (cmd.v_variables[i] == dict_get_weight (default_dict))
186               break;
187           if (i >= cmd.n_variables)
188             {
189               /* Add the weight variable to the end of the variable list. */
190               cmd.n_variables++;
191               cmd.v_variables = xrealloc (cmd.v_variables,
192                                           (cmd.n_variables
193                                            * sizeof *cmd.v_variables));
194               cmd.v_variables[cmd.n_variables - 1]
195                 = dict_get_weight (default_dict);
196             }
197         }
198       else
199         msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
200     }
201
202   /* Case number. */
203   if (cmd.numbering == LST_NUMBERED)
204     {
205       /* Initialize the case-number variable. */
206       strcpy (casenum_var.name, "Case#");
207       casenum_var.type = NUMERIC;
208       casenum_var.fv = -1;
209       casenum_var.print.type = FMT_F;
210       casenum_var.print.w = (cmd.last == LONG_MAX ? 5 : intlog10 (cmd.last));
211       casenum_var.print.d = 0;
212
213       /* Add the weight variable at the beginning of the variable list. */
214       cmd.n_variables++;
215       cmd.v_variables = xrealloc (cmd.v_variables,
216                                   cmd.n_variables * sizeof *cmd.v_variables);
217       memmove (&cmd.v_variables[1], &cmd.v_variables[0],
218                (cmd.n_variables - 1) * sizeof *cmd.v_variables);
219       cmd.v_variables[0] = &casenum_var;
220     }
221
222 #if DEBUGGING
223   /* Print out command. */
224   debug_print ();
225 #endif
226
227   determine_layout ();
228
229   case_num = 0;
230   procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
231   free (line_buf);
232
233   clean_up ();
234
235   return CMD_SUCCESS;
236 }
237
238 /* Writes headers to all devices.  This is done at the beginning of
239    each SPLIT FILE group. */
240 static void
241 write_all_headers (void *aux UNUSED)
242 {
243   struct outp_driver *d;
244
245   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
246     {
247       if (!d->class->special)
248         {
249           d->cp_y += d->font_height;            /* Blank line. */
250           write_header (d);
251         }
252       else if (d->class == &html_class)
253         {
254           struct html_driver_ext *x = d->ext;
255   
256           assert (d->driver_open);
257           if (x->sequence_no == 0 && !d->class->open_page (d))
258             {
259               msg (ME, _("Cannot open first page on HTML device %s."),
260                    d->name);
261               return;
262             }
263
264           fputs ("<TABLE BORDER=1>\n  <TR>\n", x->file.file);
265           
266           {
267             int i;
268
269             for (i = 0; i < cmd.n_variables; i++)
270               fprintf (x->file.file, "    <TH><I><B>%s</B></I></TH>\n",
271                        cmd.v_variables[i]->name);
272           }
273
274           fputs ("  <TR>\n", x->file.file);
275         }
276       else if (d->class == &devind_class) 
277         {
278           /* FIXME */
279         }
280       else
281         assert (0);
282     }
283 }
284
285 /* Writes the headers.  Some of them might be vertical; most are
286    probably horizontal. */
287 static void
288 write_header (struct outp_driver *d)
289 {
290   struct list_ext *prc = d->prc;
291
292   if (!prc->header_rows)
293     return;
294   
295   if (n_lines_remaining (d) < prc->header_rows + 1)
296     {
297       outp_eject_page (d);
298       assert (n_lines_remaining (d) >= prc->header_rows + 1);
299     }
300
301   /* Design the header. */
302   if (!prc->header)
303     {
304       int i, x;
305
306       /* Allocate, initialize header. */
307       prc->header = xmalloc (sizeof (char *) * prc->header_rows);
308       {
309         int w = n_chars_width (d);
310         for (i = 0; i < prc->header_rows; i++)
311           {
312             prc->header[i] = xmalloc (w + 1);
313             memset (prc->header[i], ' ', w);
314           }
315       }
316
317       /* Put in vertical names. */
318       for (i = x = 0; i < prc->n_vertical; i++)
319         {
320           struct variable *v = cmd.v_variables[i];
321           int j;
322
323           memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
324           x += v->print.w - 1;
325           for (j = 0; j < (int) strlen (v->name); j++)
326             prc->header[strlen (v->name) - j - 1][x] = v->name[j];
327           x += 2;
328         }
329
330       /* Put in horizontal names. */
331       for (; i < cmd.n_variables; i++)
332         {
333           struct variable *v = cmd.v_variables[i];
334           
335           memset (&prc->header[prc->header_rows - 1][x], '-',
336                   max (v->print.w, (int) strlen (v->name)));
337           if ((int) strlen (v->name) < v->print.w)
338             x += v->print.w - strlen (v->name);
339           memcpy (&prc->header[0][x], v->name, strlen (v->name));
340           x += strlen (v->name) + 1;
341         }
342
343       /* Add null bytes. */
344       for (i = 0; i < prc->header_rows; i++)
345         {
346           for (x = n_chars_width (d); x >= 1; x--)
347             if (prc->header[i][x - 1] != ' ')
348               {
349                 prc->header[i][x] = 0;
350                 break;
351               }
352           assert (x);
353         }
354     }
355
356   /* Write out the header, in back-to-front order except for the last line. */
357   {
358     int i;
359     
360     for (i = prc->header_rows - 2; i >= 0; i--)
361       write_line (d, prc->header[i]);
362     write_line (d, prc->header[prc->header_rows - 1]);
363   }
364 }
365       
366   
367 /* Frees up all the memory we've allocated. */
368 static void
369 clean_up (void)
370 {
371   struct outp_driver *d;
372   
373   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
374     if (d->class->special == 0)
375       {
376         struct list_ext *prc = d->prc;
377         int i;
378
379         if (prc->header)
380           {
381             for (i = 0; i < prc->header_rows; i++)
382               free (prc->header[i]);
383             free (prc->header);
384           }
385         free (prc);
386       
387         d->class->text_set_font_by_name (d, "PROP");
388       }
389     else if (d->class == &html_class)
390       {
391         if (d->driver_open && d->page_open)
392           {
393             struct html_driver_ext *x = d->ext;
394
395             fputs ("</TABLE>\n", x->file.file);
396           }
397       }
398     else if (d->class == &devind_class) 
399       {
400         /* FIXME */
401       }
402     else
403       assert (0);
404   
405   free (cmd.v_variables);
406 }
407
408 /* Writes string STRING at the current position.  If the text would
409    fall off the side of the page, then advance to the next line,
410    indenting by amount INDENT. */
411 static void
412 write_varname (struct outp_driver *d, char *string, int indent)
413 {
414   struct outp_text text;
415
416   text.options = OUTP_T_JUST_LEFT;
417   ls_init (&text.s, string, strlen (string));
418   d->class->text_metrics (d, &text);
419   
420   if (d->cp_x + text.h > d->width)
421     {
422       d->cp_y += d->font_height;
423       if (d->cp_y + d->font_height > d->length)
424         outp_eject_page (d);
425       d->cp_x = indent;
426     }
427
428   text.x = d->cp_x;
429   text.y = d->cp_y;
430   d->class->text_draw (d, &text);
431   d->cp_x += text.h;
432 }
433
434 /* When we can't fit all the values across the page, we write out all
435    the variable names just once.  This is where we do it. */
436 static void
437 write_fallback_headers (struct outp_driver *d)
438 {
439   const int max_width = n_chars_width(d) - 10;
440   
441   int index = 0;
442   int width = 0;
443   int line_number = 0;
444
445   const char *Line = _("Line");
446   char *leader = local_alloc (strlen (Line) + INT_DIGITS + 1 + 1);
447       
448   while (index < cmd.n_variables)
449     {
450       struct outp_text text;
451
452       /* Ensure that there is enough room for a line of text. */
453       if (d->cp_y + d->font_height > d->length)
454         outp_eject_page (d);
455       
456       /* The leader is a string like `Line 1: '.  Write the leader. */
457       sprintf(leader, "%s %d:", Line, ++line_number);
458       text.options = OUTP_T_JUST_LEFT;
459       ls_init (&text.s, leader, strlen (leader));
460       text.x = 0;
461       text.y = d->cp_y;
462       d->class->text_draw (d, &text);
463       d->cp_x = text.h;
464
465       goto entry;
466       do
467         {
468           width++;
469
470         entry:
471           {
472             int var_width = cmd.v_variables[index]->print.w;
473             if (width + var_width > max_width && width != 0)
474               {
475                 width = 0;
476                 d->cp_x = 0;
477                 d->cp_y += d->font_height;
478                 break;
479               }
480             width += var_width;
481           }
482           
483           {
484             char varname[10];
485             sprintf (varname, " %s", cmd.v_variables[index]->name);
486             write_varname (d, varname, text.h);
487           }
488         }
489       while (++index < cmd.n_variables);
490
491     }
492   d->cp_x = 0;
493   d->cp_y += d->font_height;
494   
495   local_free (leader);
496 }
497
498 /* There are three possible layouts for the LIST procedure:
499
500    1. If the values and their variables' name fit across the page,
501    then they are listed across the page in that way.
502
503    2. If the values can fit across the page, but not the variable
504    names, then as many variable names as necessary are printed
505    vertically to compensate.
506
507    3. If not even the values can fit across the page, the variable
508    names are listed just once, at the beginning, in a compact format,
509    and the values are listed with a variable name label at the
510    beginning of each line for easier reference.
511
512    This is complicated by the fact that we have to do all this for
513    every output driver, not just once.  */
514 static void
515 determine_layout (void)
516 {
517   struct outp_driver *d;
518   
519   /* This is the largest page width of any driver, so we can tell what
520      size buffer to allocate. */
521   int largest_page_width = 0;
522   
523   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
524     {
525       int column;       /* Current column. */
526       int width;        /* Accumulated width. */
527       int max_width;    /* Page width. */
528       
529       struct list_ext *prc;
530
531       if (d->class == &html_class)
532         continue;
533       else if (d->class == &devind_class) 
534         {
535           /* FIXME */
536           tab_output_text (TAT_NONE, "(devind not supported on LIST yet)");
537           continue;
538         }
539       
540       assert (d->class->special == 0);
541
542       if (!d->page_open)
543         d->class->open_page (d);
544       
545       max_width = n_chars_width (d);
546       largest_page_width = max (largest_page_width, max_width);
547
548       prc = d->prc = xmalloc (sizeof *prc);
549       prc->type = 0;
550       prc->n_vertical = 0;
551       prc->header = NULL;
552
553       /* Try layout #1. */
554       for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
555         {
556           struct variable *v = cmd.v_variables[column];
557           width += max (v->print.w, (int) strlen (v->name));
558         }
559       if (width <= max_width)
560         {
561           prc->header_rows = 2;
562           d->class->text_set_font_by_name (d, "FIXED");
563           continue;
564         }
565
566       /* Try layout #2. */
567       for (width = cmd.n_variables - 1, column = 0;
568            column < cmd.n_variables && width <= max_width;
569            column++)
570           width += cmd.v_variables[column]->print.w;
571       
572       /* If it fit then we need to determine how many labels can be
573          written horizontally. */
574       if (width <= max_width)
575         {
576 #ifndef NDEBUG
577           prc->n_vertical = -1;
578 #endif
579           for (column = cmd.n_variables - 1; column >= 0; column--)
580             {
581               struct variable *v = cmd.v_variables[column];
582               int trial_width = (width - v->print.w
583                                  + max (v->print.w, (int) strlen (v->name)));
584               
585               if (trial_width > max_width)
586                 {
587                   prc->n_vertical = column + 1;
588                   break;
589                 }
590               width = trial_width;
591             }
592           assert(prc->n_vertical != -1);
593
594           prc->n_vertical = cmd.n_variables;
595           /* Finally determine the length of the headers. */
596           for (prc->header_rows = 0, column = 0;
597                column < prc->n_vertical;
598                column++)
599             prc->header_rows = max (prc->header_rows,
600                                     (int) strlen (cmd.v_variables[column]->name));
601           prc->header_rows++;
602
603           d->class->text_set_font_by_name (d, "FIXED");
604           continue;
605         }
606
607       /* Otherwise use the ugly fallback listing format. */
608       prc->type = 1;
609       prc->header_rows = 0;
610
611       d->cp_y += d->font_height;
612       write_fallback_headers (d);
613       d->cp_y += d->font_height;
614       d->class->text_set_font_by_name (d, "FIXED");
615     }
616
617   line_buf = xmalloc (max (1022, largest_page_width) + 2);
618 }
619
620 static int
621 list_cases (struct ccase *c, void *aux UNUSED)
622 {
623   struct outp_driver *d;
624   
625   case_num++;
626   if (case_num < cmd.first || case_num > cmd.last
627       || (cmd.step != 1 && (case_num - cmd.first) % cmd.step))
628     return 1;
629
630   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
631     if (d->class->special == 0)
632       {
633         const struct list_ext *prc = d->prc;
634         const int max_width = n_chars_width (d);
635         int column;
636         int x = 0;
637
638         if (!prc->header_rows)
639           x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
640       
641         for (column = 0; column < cmd.n_variables; column++)
642           {
643             struct variable *v = cmd.v_variables[column];
644             int width;
645
646             if (prc->type == 0 && column >= prc->n_vertical)
647               width = max ((int) strlen (v->name), v->print.w);
648             else
649               width = v->print.w;
650
651             if (width + x > max_width && x != 0)
652               {
653                 if (!n_lines_remaining (d))
654                   {
655                     outp_eject_page (d);
656                     write_header (d);
657                   }
658               
659                 line_buf[x] = 0;
660                 write_line (d, line_buf);
661
662                 x = 0;
663                 if (!prc->header_rows)
664                   x = nsprintf (line_buf, "%8s: ", v->name);
665               }
666
667             if (width > v->print.w)
668               {
669                 memset(&line_buf[x], ' ', width - v->print.w);
670                 x += width - v->print.w;
671               }
672
673             if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
674               data_out (&line_buf[x], &v->print, &c->data[v->fv]);
675             else 
676               {
677                 union value case_num_value;
678                 case_num_value.f = case_num;
679                 data_out (&line_buf[x], &v->print, &case_num_value); 
680               }
681             x += v->print.w;
682           
683             line_buf[x++] = ' ';
684           }
685       
686         if (!n_lines_remaining (d))
687           {
688             outp_eject_page (d);
689             write_header (d);
690           }
691               
692         line_buf[x] = 0;
693         write_line (d, line_buf);
694       }
695     else if (d->class == &html_class)
696       {
697         struct html_driver_ext *x = d->ext;
698         int column;
699
700         fputs ("  <TR>\n", x->file.file);
701         
702         for (column = 0; column < cmd.n_variables; column++)
703           {
704             struct variable *v = cmd.v_variables[column];
705             char buf[41];
706             
707             if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
708               data_out (buf, &v->print, &c->data[v->fv]);
709             else 
710               {
711                 union value case_num_value;
712                 case_num_value.f = case_num;
713                 data_out (buf, &v->print, &case_num_value); 
714               }
715             buf[v->print.w] = 0;
716
717             fprintf (x->file.file, "    <TD ALIGN=RIGHT>%s</TD>\n",
718                      &buf[strspn (buf, " ")]);
719           }
720           
721         fputs ("  </TR>\n", x->file.file);
722       }
723     else if (d->class == &devind_class) 
724       {
725         /* FIXME */
726       }
727     else
728       assert (0);
729
730   return 1;
731 }
732 \f
733 /* Debugging output. */
734
735 #if DEBUGGING
736 /* Prints out the command as parsed by cmd_list(). */
737 static void
738 debug_print (void)
739 {
740   int i;
741
742   puts ("LIST");
743   printf ("  VARIABLES=");
744   for (i = 0; i < cmd.n_variables; i++)
745     {
746       if (i)
747         putc (' ', stdout);
748       fputs (cmd.v_variables[i]->name, stdout);
749     }
750
751   printf ("\n  /CASES=FROM %ld TO %ld BY %ld\n", cmd.first, cmd.last, cmd.step);
752
753   fputs ("  /FORMAT=", stdout);
754   if (cmd.numbering == LST_NUMBERED)
755     fputs ("NUMBERED", stdout);
756   else
757     fputs ("UNNUMBERED", stdout);
758   putc (' ', stdout);
759   if (cmd.wrap == LST_WRAP)
760     fputs ("WRAP", stdout);
761   else
762     fputs ("SINGLE", stdout);
763   putc (' ', stdout);
764   if (cmd.weight == LST_WEIGHT)
765     fputs ("WEIGHT", stdout);
766   else
767     fputs ("NOWEIGHT", stdout);
768   puts (".");
769 }
770 #endif /* DEBUGGING */
771
772 /* 
773    Local Variables:
774    mode: c
775    End:
776 */