Work to get rid of GCC 4.0 warnings, part 1.
[pspp] / 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., 51 Franklin Street, Fifth Floor, Boston, MA
18    02110-1301, USA. */
19
20 #include <config.h>
21 #include "error.h"
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include "alloc.h"
25 #include "case.h"
26 #include "command.h"
27 #include "devind.h"
28 #include "dictionary.h"
29 #include "lexer.h"
30 #include "error.h"
31 #include "magic.h"
32 #include "misc.h"
33 #include "htmlP.h"
34 #include "output.h"
35 #include "size_max.h"
36 #include "som.h"
37 #include "tab.h"
38 #include "var.h"
39 #include "vfm.h"
40 #include "format.h"
41
42 #include "gettext.h"
43 #define _(msgid) gettext (msgid)
44
45 /* (headers) */
46
47 #include "debug-print.h"
48
49 /* (specification)
50    list (lst_):
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,
54             wrap:!wrap/single,
55             weight:weight/!noweight.
56 */
57 /* (declarations) */
58 /* (functions) */
59
60 /* Layout for one output driver. */
61 struct list_ext
62   {
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. */
67   };
68
69 /* Parsed command. */
70 static struct cmd_list cmd;
71
72 /* Current case number. */
73 static int case_idx;
74
75 /* Line buffer. */
76 static char *line_buf;
77
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);
82
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 *);
89
90 /* Returns the number of text lines that can fit on the remainder of
91    the page. */
92 static inline unsigned
93 n_lines_remaining (struct outp_driver *d)
94 {
95   int diff;
96
97   diff = d->length - d->cp_y;
98   return (diff > 0) ? (diff / d->font_height) : 0;
99 }
100
101 /* Returns the number of fixed-width character that can fit across the
102    page. */
103 static inline unsigned
104 n_chars_width (struct outp_driver *d)
105 {
106   return d->width / d->fixed_width;
107 }
108
109 /* Writes the line S at the current position and advances to the next
110    line.  */
111 static void
112 write_line (struct outp_driver *d, char *s)
113 {
114   struct outp_text text;
115   
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));
119   text.x = d->cp_x;
120   text.y = d->cp_y;
121   d->class->text_draw (d, &text);
122   d->cp_x = 0;
123   d->cp_y += d->font_height;
124 }
125     
126 /* Parses and executes the LIST procedure. */
127 int
128 cmd_list (void)
129 {
130   struct variable casenum_var;
131
132   if (!parse_list (&cmd))
133     return CMD_FAILURE;
134   
135   /* Fill in defaults. */
136   if (cmd.step == NOT_LONG)
137     cmd.step = 1;
138   if (cmd.first == NOT_LONG)
139     cmd.first = 1;
140   if (cmd.last == NOT_LONG)
141     cmd.last = LONG_MAX;
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)
146     {
147       msg (SE, _("No variables specified."));
148       return CMD_FAILURE;
149     }
150
151   /* Verify arguments. */
152   if (cmd.first > cmd.last)
153     {
154       int t;
155       msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
156            "specified.  The values will be swapped."), cmd.first, cmd.last);
157       t = cmd.first;
158       cmd.first = cmd.last;
159       cmd.last = t;
160     }
161   if (cmd.first < 1)
162     {
163       msg (SW, _("The first case (%ld) to list is less than 1.  The value is "
164            "being reset to 1."), cmd.first);
165       cmd.first = 1;
166     }
167   if (cmd.last < 1)
168     {
169       msg (SW, _("The last case (%ld) to list is less than 1.  The value is "
170            "being reset to 1."), cmd.last);
171       cmd.last = 1;
172     }
173   if (cmd.step < 1)
174     {
175       msg (SW, _("The step value %ld is less than 1.  The value is being "
176            "reset to 1."), cmd.step);
177       cmd.step = 1;
178     }
179
180   /* Weighting variable. */
181   if (cmd.weight == LST_WEIGHT)
182     {
183       if (dict_get_weight (default_dict) != NULL)
184         {
185           size_t i;
186
187           for (i = 0; i < cmd.n_variables; i++)
188             if (cmd.v_variables[i] == dict_get_weight (default_dict))
189               break;
190           if (i >= cmd.n_variables)
191             {
192               /* Add the weight variable to the end of the variable list. */
193               cmd.n_variables++;
194               cmd.v_variables = xrealloc (cmd.v_variables,
195                                           (cmd.n_variables
196                                            * sizeof *cmd.v_variables));
197               cmd.v_variables[cmd.n_variables - 1]
198                 = dict_get_weight (default_dict);
199             }
200         }
201       else
202         msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
203     }
204
205   /* Case number. */
206   if (cmd.numbering == LST_NUMBERED)
207     {
208       /* Initialize the case-number variable. */
209       strcpy (casenum_var.name, "Case#");
210       casenum_var.type = NUMERIC;
211       casenum_var.fv = -1;
212       casenum_var.print = make_output_format (FMT_F,
213                                               (cmd.last == LONG_MAX
214                                                ? 5 : intlog10 (cmd.last)), 0);
215
216       /* Add the weight variable at the beginning of the variable list. */
217       cmd.n_variables++;
218       cmd.v_variables = xrealloc (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;
223     }
224
225   determine_layout ();
226
227   case_idx = 0;
228   procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
229   free (line_buf);
230
231   clean_up ();
232
233   return CMD_SUCCESS;
234 }
235
236 /* Writes headers to all devices.  This is done at the beginning of
237    each SPLIT FILE group. */
238 static void
239 write_all_headers (void *aux UNUSED)
240 {
241   struct outp_driver *d;
242
243   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
244     {
245       if (!d->class->special)
246         {
247           d->cp_y += d->font_height;            /* Blank line. */
248           write_header (d);
249         }
250       else if (d->class == &html_class)
251         {
252           struct html_driver_ext *x = d->ext;
253   
254           assert (d->driver_open);
255           if (x->sequence_no == 0 && !d->class->open_page (d))
256             {
257               msg (ME, _("Cannot open first page on HTML device %s."),
258                    d->name);
259               return;
260             }
261
262           fputs ("<TABLE BORDER=1>\n  <TR>\n", x->file.file);
263           
264           {
265             size_t i;
266
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);
270           }
271
272           fputs ("  <TR>\n", x->file.file);
273         }
274       else if (d->class == &devind_class) 
275         {
276           /* FIXME */
277         }
278       else
279         assert (0);
280     }
281 }
282
283 /* Writes the headers.  Some of them might be vertical; most are
284    probably horizontal. */
285 static void
286 write_header (struct outp_driver *d)
287 {
288   struct list_ext *prc = d->prc;
289
290   if (!prc->header_rows)
291     return;
292   
293   if (n_lines_remaining (d) < prc->header_rows + 1)
294     {
295       outp_eject_page (d);
296       assert (n_lines_remaining (d) >= prc->header_rows + 1);
297     }
298
299   /* Design the header. */
300   if (!prc->header)
301     {
302       size_t i;
303       size_t x;
304       
305       /* Allocate, initialize header. */
306       prc->header = xmalloc (sizeof (char *) * prc->header_rows);
307       {
308         int w = n_chars_width (d);
309         for (i = 0; i < prc->header_rows; i++)
310           {
311             prc->header[i] = xmalloc (w + 1);
312             memset (prc->header[i], ' ', w);
313           }
314       }
315
316       /* Put in vertical names. */
317       for (i = x = 0; i < prc->n_vertical; i++)
318         {
319           struct variable *v = cmd.v_variables[i];
320           size_t j;
321
322           memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
323           x += v->print.w - 1;
324           for (j = 0; j < strlen (v->name); j++)
325             prc->header[strlen (v->name) - j - 1][x] = v->name[j];
326           x += 2;
327         }
328
329       /* Put in horizontal names. */
330       for (; i < cmd.n_variables; i++)
331         {
332           struct variable *v = cmd.v_variables[i];
333           
334           memset (&prc->header[prc->header_rows - 1][x], '-',
335                   max (v->print.w, (int) strlen (v->name)));
336           if ((int) strlen (v->name) < v->print.w)
337             x += v->print.w - strlen (v->name);
338           memcpy (&prc->header[0][x], v->name, strlen (v->name));
339           x += strlen (v->name) + 1;
340         }
341
342       /* Add null bytes. */
343       for (i = 0; i < prc->header_rows; i++)
344         {
345           for (x = n_chars_width (d); x >= 1; x--)
346             if (prc->header[i][x - 1] != ' ')
347               {
348                 prc->header[i][x] = 0;
349                 break;
350               }
351           assert (x);
352         }
353     }
354
355   /* Write out the header, in back-to-front order except for the last line. */
356   if (prc->header_rows >= 2) 
357     {
358       size_t i;
359         
360       for (i = prc->header_rows - 1; i-- != 0; )
361         write_line (d, prc->header[i]); 
362     }
363   write_line (d, prc->header[prc->header_rows - 1]);
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         size_t 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       size_t column;    /* Current column. */
526       int width;        /* Accumulated width. */
527       int height;       /* Height of vertical names. */
528       int max_width;    /* Page width. */
529
530       struct list_ext *prc;
531
532       if (d->class == &html_class)
533         continue;
534       else if (d->class == &devind_class) 
535         {
536           /* FIXME */
537           tab_output_text (TAT_NONE, "(devind not supported on LIST yet)");
538           continue;
539         }
540       
541       assert (d->class->special == 0);
542
543       if (!d->page_open)
544         d->class->open_page (d);
545       
546       max_width = n_chars_width (d);
547       largest_page_width = max (largest_page_width, max_width);
548
549       prc = d->prc = xmalloc (sizeof *prc);
550       prc->type = 0;
551       prc->n_vertical = 0;
552       prc->header = NULL;
553
554       /* Try layout #1. */
555       for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
556         {
557           struct variable *v = cmd.v_variables[column];
558           width += max (v->print.w, (int) strlen (v->name));
559         }
560       if (width <= max_width)
561         {
562           prc->header_rows = 2;
563           d->class->text_set_font_by_name (d, "FIXED");
564           continue;
565         }
566
567       /* Try layout #2. */
568       for (width = cmd.n_variables - 1, height = 0, column = 0;
569            column < cmd.n_variables && width <= max_width;
570            column++) 
571         {
572           struct variable *v = cmd.v_variables[column];
573           width += v->print.w;
574           if (strlen (v->name) > height)
575             height = strlen (v->name);
576         }
577       
578       /* If it fit then we need to determine how many labels can be
579          written horizontally. */
580       if (width <= max_width && height <= SHORT_NAME_LEN)
581         {
582 #ifndef NDEBUG
583           prc->n_vertical = SIZE_MAX;
584 #endif
585           for (column = cmd.n_variables; column-- != 0; )
586             {
587               struct variable *v = cmd.v_variables[column];
588               int trial_width = (width - v->print.w
589                                  + max (v->print.w, (int) strlen (v->name)));
590               
591               if (trial_width > max_width)
592                 {
593                   prc->n_vertical = column + 1;
594                   break;
595                 }
596               width = trial_width;
597             }
598           assert (prc->n_vertical != SIZE_MAX);
599
600           prc->n_vertical = cmd.n_variables;
601           /* Finally determine the length of the headers. */
602           for (prc->header_rows = 0, column = 0;
603                column < prc->n_vertical;
604                column++)
605             prc->header_rows = max (prc->header_rows,
606                                     strlen (cmd.v_variables[column]->name));
607           prc->header_rows++;
608
609           d->class->text_set_font_by_name (d, "FIXED");
610           continue;
611         }
612
613       /* Otherwise use the ugly fallback listing format. */
614       prc->type = 1;
615       prc->header_rows = 0;
616
617       d->cp_y += d->font_height;
618       write_fallback_headers (d);
619       d->cp_y += d->font_height;
620       d->class->text_set_font_by_name (d, "FIXED");
621     }
622
623   line_buf = xmalloc (max (1022, largest_page_width) + 2);
624 }
625
626 static int
627 list_cases (struct ccase *c, void *aux UNUSED)
628 {
629   struct outp_driver *d;
630   
631   case_idx++;
632   if (case_idx < cmd.first || case_idx > cmd.last
633       || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
634     return 1;
635
636   for (d = outp_drivers (NULL); d; d = outp_drivers (d))
637     if (d->class->special == 0)
638       {
639         const struct list_ext *prc = d->prc;
640         const int max_width = n_chars_width (d);
641         int column;
642         int x = 0;
643
644         if (!prc->header_rows)
645           x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
646       
647         for (column = 0; column < cmd.n_variables; column++)
648           {
649             struct variable *v = cmd.v_variables[column];
650             int width;
651
652             if (prc->type == 0 && column >= prc->n_vertical)
653               width = max ((int) strlen (v->name), v->print.w);
654             else
655               width = v->print.w;
656
657             if (width + x > max_width && x != 0)
658               {
659                 if (!n_lines_remaining (d))
660                   {
661                     outp_eject_page (d);
662                     write_header (d);
663                   }
664               
665                 line_buf[x] = 0;
666                 write_line (d, line_buf);
667
668                 x = 0;
669                 if (!prc->header_rows)
670                   x = nsprintf (line_buf, "%8s: ", v->name);
671               }
672
673             if (width > v->print.w)
674               {
675                 memset(&line_buf[x], ' ', width - v->print.w);
676                 x += width - v->print.w;
677               }
678
679             if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
680               data_out (&line_buf[x], &v->print, case_data (c, v->fv));
681             else 
682               {
683                 union value case_idx_value;
684                 case_idx_value.f = case_idx;
685                 data_out (&line_buf[x], &v->print, &case_idx_value); 
686               }
687             x += v->print.w;
688           
689             line_buf[x++] = ' ';
690           }
691       
692         if (!n_lines_remaining (d))
693           {
694             outp_eject_page (d);
695             write_header (d);
696           }
697               
698         line_buf[x] = 0;
699         write_line (d, line_buf);
700       }
701     else if (d->class == &html_class)
702       {
703         struct html_driver_ext *x = d->ext;
704         int column;
705
706         fputs ("  <TR>\n", x->file.file);
707         
708         for (column = 0; column < cmd.n_variables; column++)
709           {
710             struct variable *v = cmd.v_variables[column];
711             char buf[41];
712             
713             if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
714               data_out (buf, &v->print, case_data (c, v->fv));
715             else 
716               {
717                 union value case_idx_value;
718                 case_idx_value.f = case_idx;
719                 data_out (buf, &v->print, &case_idx_value); 
720               }
721             buf[v->print.w] = 0;
722
723             fprintf (x->file.file, "    <TD ALIGN=RIGHT>%s</TD>\n",
724                      &buf[strspn (buf, " ")]);
725           }
726           
727         fputs ("  </TR>\n", x->file.file);
728       }
729     else if (d->class == &devind_class) 
730       {
731         /* FIXME */
732       }
733     else
734       assert (0);
735
736   return 1;
737 }
738
739 /* 
740    Local Variables:
741    mode: c
742    End:
743 */