1 //
2 // "$Id$"
3 //
4 // Fluid file routines for the Fast Light Tool Kit (FLTK).
5 //
6 // You may find the basic read_* and write_* routines to
7 // be useful for other programs.  I have used them many times.
8 // They are somewhat similar to tcl, using matching { and }
9 // to quote strings.
10 //
11 // Copyright 1998-2016 by Bill Spitzak and others.
12 //
13 // This library is free software. Distribution and use rights are outlined in
14 // the file "COPYING" which should have been included with this file.  If this
15 // file is missing or damaged, see the license at:
16 //
17 //     http://www.fltk.org/COPYING.php
18 //
19 // Please report all bugs and problems on the following page:
20 //
21 //     http://www.fltk.org/str.php
22 //
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include "../src/flstring.h"
27 #include <stdarg.h>
28 #include "alignment_panel.h"
29 #include <FL/Fl.H>
30 #include "Fl_Widget_Type.h"
31 
32 ////////////////////////////////////////////////////////////////
33 // BASIC FILE WRITING:
34 
35 static FILE *fout;
36 
open_write(const char * s)37 int open_write(const char *s) {
38   if (!s) {fout = stdout; return 1;}
39   FILE *f = fl_fopen(s,"w");
40   if (!f) return 0;
41   fout = f;
42   return 1;
43 }
44 
close_write()45 int close_write() {
46   if (fout != stdout) {
47     int x = fclose(fout);
48     fout = stdout;
49     return x >= 0;
50   }
51   return 1;
52 }
53 
54 static int needspace;
55 int is_id(char); // in code.C
56 
57 // write a string, quoting characters if necessary:
write_word(const char * w)58 void write_word(const char *w) {
59   if (needspace) putc(' ', fout);
60   needspace = 1;
61   if (!w || !*w) {fprintf(fout,"{}"); return;}
62   const char *p;
63   // see if it is a single word:
64   for (p = w; is_id(*p); p++) ;
65   if (!*p) {fprintf(fout,"%s",w); return;}
66   // see if there are matching braces:
67   int n = 0;
68   for (p = w; *p; p++) {
69     if (*p == '{') n++;
70     else if (*p == '}') {n--; if (n<0) break;}
71   }
72   int mismatched = (n != 0);
73   // write out brace-quoted string:
74   putc('{', fout);
75   for (; *w; w++) {
76     switch (*w) {
77     case '{':
78     case '}':
79       if (!mismatched) break;
80     case '\\':
81     case '#':
82       putc('\\',fout);
83       break;
84     }
85     putc(*w,fout);
86   }
87   putc('}', fout);
88 }
89 
90 // write an arbitrary formatted word, or a comment, etc.
91 // if needspace is set, then one space is written before the string
92 // unless the format starts with a newline character ('\n'):
write_string(const char * format,...)93 void write_string(const char *format, ...) {
94   va_list args;
95   va_start(args, format);
96   if (needspace && *format != '\n') fputc(' ',fout);
97   vfprintf(fout, format, args);
98   va_end(args);
99   needspace = !isspace(format[strlen(format)-1] & 255);
100 }
101 
102 // start a new line and indent it for a given nesting level:
write_indent(int n)103 void write_indent(int n) {
104   fputc('\n',fout);
105   while (n--) {fputc(' ',fout); fputc(' ',fout);}
106   needspace = 0;
107 }
108 
109 // write a '{' at the given indenting level:
write_open(int)110 void write_open(int) {
111   if (needspace) fputc(' ',fout);
112   fputc('{',fout);
113   needspace = 0;
114 }
115 
116 // write a '}' at the given indenting level:
write_close(int n)117 void write_close(int n) {
118   if (needspace) write_indent(n);
119   fputc('}',fout);
120   needspace = 1;
121 }
122 
123 ////////////////////////////////////////////////////////////////
124 // BASIC FILE READING:
125 
126 static FILE *fin;
127 static int lineno;
128 static const char *fname;
129 
open_read(const char * s)130 int open_read(const char *s) {
131   lineno = 1;
132   if (!s) {fin = stdin; fname = "stdin"; return 1;}
133   FILE *f = fl_fopen(s,"r");
134   if (!f) return 0;
135   fin = f;
136   fname = s;
137   return 1;
138 }
139 
close_read()140 int close_read() {
141   if (fin != stdin) {
142     int x = fclose(fin);
143     fin = 0;
144     return x >= 0;
145   }
146   return 1;
147 }
148 
149 #include <FL/fl_message.H>
150 
read_error(const char * format,...)151 void read_error(const char *format, ...) {
152   va_list args;
153   va_start(args, format);
154   if (!fin) {
155     char buffer[1024];
156     vsnprintf(buffer, sizeof(buffer), format, args);
157     fl_message("%s", buffer);
158   } else {
159     fprintf(stderr, "%s:%d: ", fname, lineno);
160     vfprintf(stderr, format, args);
161     fprintf(stderr, "\n");
162   }
163   va_end(args);
164 }
165 
hexdigit(int x)166 static int hexdigit(int x) {
167   if (isdigit(x)) return x-'0';
168   if (isupper(x)) return x-'A'+10;
169   if (islower(x)) return x-'a'+10;
170   return 20;
171 }
172 
173 
read_quoted()174 static int read_quoted() {	// read whatever character is after a \ .
175   int c,d,x;
176   switch(c = fgetc(fin)) {
177   case '\n': lineno++; return -1;
178   case 'a' : return('\a');
179   case 'b' : return('\b');
180   case 'f' : return('\f');
181   case 'n' : return('\n');
182   case 'r' : return('\r');
183   case 't' : return('\t');
184   case 'v' : return('\v');
185   case 'x' :	/* read hex */
186     for (c=x=0; x<3; x++) {
187       int ch = fgetc(fin);
188       d = hexdigit(ch);
189       if (d > 15) {ungetc(ch,fin); break;}
190       c = (c<<4)+d;
191     }
192     break;
193   default:		/* read octal */
194     if (c<'0' || c>'7') break;
195     c -= '0';
196     for (x=0; x<2; x++) {
197       int ch = fgetc(fin);
198       d = hexdigit(ch);
199       if (d>7) {ungetc(ch,fin); break;}
200       c = (c<<3)+d;
201     }
202     break;
203   }
204   return(c);
205 }
206 
207 // return a word read from the file, or NULL at the EOF:
208 // This will skip all comments (# to end of line), and evaluate
209 // all \xxx sequences and use \ at the end of line to remove the newline.
210 // A word is any one of:
211 //	a continuous string of non-space chars except { and } and #
212 //	everything between matching {...} (unless wantbrace != 0)
213 //	the characters '{' and '}'
214 
215 static char *buffer;
216 static int buflen;
expand_buffer(int length)217 static void expand_buffer(int length) {
218   if (length >= buflen) {
219     if (!buflen) {
220       buflen = length+1;
221       buffer = (char*)malloc(buflen);
222     } else {
223       buflen = 2*buflen;
224       if (length >= buflen) buflen = length+1;
225       buffer = (char *)realloc((void *)buffer,buflen);
226     }
227   }
228 }
229 
read_word(int wantbrace)230 const char *read_word(int wantbrace) {
231   int x;
232 
233   // skip all the whitespace before it:
234   for (;;) {
235     x = getc(fin);
236     if (x < 0 && feof(fin)) {	// eof
237       return 0;
238     } else if (x == '#') {	// comment
239       do x = getc(fin); while (x >= 0 && x != '\n');
240       lineno++;
241       continue;
242     } else if (x == '\n') {
243       lineno++;
244     } else if (!isspace(x & 255)) {
245       break;
246     }
247   }
248 
249   expand_buffer(100);
250 
251   if (x == '{' && !wantbrace) {
252 
253     // read in whatever is between braces
254     int length = 0;
255     int nesting = 0;
256     for (;;) {
257       x = getc(fin);
258       if (x<0) {read_error("Missing '}'"); break;}
259       else if (x == '#') { // embedded comment
260 	do x = getc(fin); while (x >= 0 && x != '\n');
261 	lineno++;
262 	continue;
263       } else if (x == '\n') lineno++;
264       else if (x == '\\') {x = read_quoted(); if (x<0) continue;}
265       else if (x == '{') nesting++;
266       else if (x == '}') {if (!nesting--) break;}
267       buffer[length++] = x;
268       expand_buffer(length);
269     }
270     buffer[length] = 0;
271     return buffer;
272 
273   } else if (x == '{' || x == '}') {
274     // all the punctuation is a word:
275     buffer[0] = x;
276     buffer[1] = 0;
277     return buffer;
278 
279   } else {
280 
281     // read in an unquoted word:
282     int length = 0;
283     for (;;) {
284       if (x == '\\') {x = read_quoted(); if (x<0) continue;}
285       else if (x<0 || isspace(x & 255) || x=='{' || x=='}' || x=='#') break;
286       buffer[length++] = x;
287       expand_buffer(length);
288       x = getc(fin);
289     }
290     ungetc(x, fin);
291     buffer[length] = 0;
292     return buffer;
293 
294   }
295 }
296 
297 ////////////////////////////////////////////////////////////////
298 
299 // global int variables:
300 extern int i18n_type;
301 extern const char* i18n_include;
302 extern const char* i18n_function;
303 extern const char* i18n_file;
304 extern const char* i18n_set;
305 
306 
307 extern int header_file_set;
308 extern int code_file_set;
309 extern const char* header_file_name;
310 extern const char* code_file_name;
311 
write_file(const char * filename,int selected_only)312 int write_file(const char *filename, int selected_only) {
313   if (!open_write(filename)) return 0;
314   write_string("# data file for the Fltk User Interface Designer (fluid)\n"
315 	       "version %.4f",FL_VERSION);
316   if(!include_H_from_C)
317     write_string("\ndo_not_include_H_from_C");
318   if(use_FL_COMMAND)
319     write_string("\nuse_FL_COMMAND");
320   if (i18n_type) {
321     write_string("\ni18n_type %d", i18n_type);
322     write_string("\ni18n_include %s", i18n_include);
323     switch (i18n_type) {
324     case 1 : /* GNU gettext */
325 	write_string("\ni18n_function %s", i18n_function);
326         break;
327     case 2 : /* POSIX catgets */
328         if (i18n_file[0]) write_string("\ni18n_file %s", i18n_file);
329 	write_string("\ni18n_set %s", i18n_set);
330         break;
331     }
332   }
333   if (!selected_only) {
334     write_string("\nheader_name"); write_word(header_file_name);
335     write_string("\ncode_name"); write_word(code_file_name);
336   }
337   for (Fl_Type *p = Fl_Type::first; p;) {
338     if (!selected_only || p->selected) {
339       p->write();
340       write_string("\n");
341       int q = p->level;
342       for (p = p->next; p && p->level > q; p = p->next) {/*empty*/}
343     } else {
344       p = p->next;
345     }
346   }
347   return close_write();
348 }
349 
350 ////////////////////////////////////////////////////////////////
351 // read all the objects out of the input file:
352 
353 void read_fdesign();
354 
355 double read_version;
356 
357 extern Fl_Type *Fl_Type_make(const char *tn);
358 
read_children(Fl_Type * p,int paste)359 static void read_children(Fl_Type *p, int paste) {
360   Fl_Type::current = p;
361   for (;;) {
362     const char *c = read_word();
363   REUSE_C:
364     if (!c) {
365       if (p && !paste) read_error("Missing '}'");
366       break;
367     }
368 
369     if (!strcmp(c,"}")) {
370       if (!p) read_error("Unexpected '}'");
371       break;
372     }
373 
374     // this is the first word in a .fd file:
375     if (!strcmp(c,"Magic:")) {
376       read_fdesign();
377       return;
378     }
379 
380     if (!strcmp(c,"version")) {
381       c = read_word();
382       read_version = strtod(c,0);
383       if (read_version<=0 || read_version>double(FL_VERSION+0.00001))
384         read_error("unknown version '%s'",c);
385       continue;
386     }
387 
388     // back compatibility with Vincent Penne's original class code:
389     if (!p && !strcmp(c,"define_in_struct")) {
390       Fl_Type *t = Fl_Type_make("class");
391       t->name(read_word());
392       Fl_Type::current = p = t;
393       paste = 1; // stops "missing }" error
394       continue;
395     }
396 
397     if (!strcmp(c,"do_not_include_H_from_C")) {
398       include_H_from_C=0;
399       goto CONTINUE;
400     }
401     if (!strcmp(c,"use_FL_COMMAND")) {
402       use_FL_COMMAND=1;
403       goto CONTINUE;
404     }
405     if (!strcmp(c,"i18n_type")) {
406       i18n_type = atoi(read_word());
407       goto CONTINUE;
408     }
409     if (!strcmp(c,"i18n_function")) {
410       i18n_function = strdup(read_word());
411       goto CONTINUE;
412     }
413     if (!strcmp(c,"i18n_file")) {
414       i18n_file = strdup(read_word());
415       goto CONTINUE;
416     }
417     if (!strcmp(c,"i18n_set")) {
418       i18n_set = strdup(read_word());
419       goto CONTINUE;
420     }
421     if (!strcmp(c,"i18n_include")) {
422       i18n_include = strdup(read_word());
423       goto CONTINUE;
424     }
425     if (!strcmp(c,"i18n_type"))
426     {
427       i18n_type = atoi(read_word());
428       goto CONTINUE;
429     }
430     if (!strcmp(c,"i18n_type"))
431     {
432       i18n_type = atoi(read_word());
433       goto CONTINUE;
434     }
435     if (!strcmp(c,"header_name")) {
436       if (!header_file_set) header_file_name = strdup(read_word());
437       else read_word();
438       goto CONTINUE;
439     }
440 
441     if (!strcmp(c,"code_name")) {
442       if (!code_file_set) code_file_name = strdup(read_word());
443       else read_word();
444       goto CONTINUE;
445     }
446 
447     if (!strcmp(c, "snap") || !strcmp(c, "gridx") || !strcmp(c, "gridy")) {
448       // grid settings are now global
449       read_word();
450       goto CONTINUE;
451     }
452 
453     {Fl_Type *t = Fl_Type_make(c);
454     if (!t) {
455       read_error("Unknown word \"%s\"", c);
456       continue;
457     }
458     t->name(read_word());
459 
460     c = read_word(1);
461     if (strcmp(c,"{") && t->is_class()) {   // <prefix> <name>
462       ((Fl_Class_Type*)t)->prefix(t->name());
463       t->name(c);
464       c = read_word(1);
465     }
466 
467     if (strcmp(c,"{")) {
468       read_error("Missing property list for %s\n",t->title());
469       goto REUSE_C;
470     }
471 
472     t->open_ = 0;
473     for (;;) {
474       const char *cc = read_word();
475       if (!cc || !strcmp(cc,"}")) break;
476       t->read_property(cc);
477     }
478 
479     if (!t->is_parent()) continue;
480     c = read_word(1);
481     if (strcmp(c,"{")) {
482       read_error("Missing child list for %s\n",t->title());
483       goto REUSE_C;
484     }
485     read_children(t, 0);}
486     Fl_Type::current = p;
487   CONTINUE:;
488   }
489 }
490 
491 extern void deselect();
492 
read_file(const char * filename,int merge)493 int read_file(const char *filename, int merge) {
494   Fl_Type *o;
495   read_version = 0.0;
496   if (!open_read(filename)) return 0;
497   if (merge) deselect(); else    delete_all();
498   read_children(Fl_Type::current, merge);
499   Fl_Type::current = 0;
500   // Force menu items to be rebuilt...
501   for (o = Fl_Type::first; o; o = o->next)
502     if (o->is_menu_button()) o->add_child(0,0);
503   for (o = Fl_Type::first; o; o = o->next)
504     if (o->selected) {Fl_Type::current = o; break;}
505   selection_changed(Fl_Type::current);
506   return close_read();
507 }
508 
509 ////////////////////////////////////////////////////////////////
510 // Read Forms and XForms fdesign files:
511 
read_fdesign_line(const char * & name,const char * & value)512 int read_fdesign_line(const char*& name, const char*& value) {
513 
514   int length = 0;
515   int x;
516   // find a colon:
517   for (;;) {
518     x = getc(fin);
519     if (x < 0 && feof(fin)) return 0;
520     if (x == '\n') {length = 0; continue;} // no colon this line...
521     if (!isspace(x & 255)) {
522       buffer[length++] = x;
523       expand_buffer(length);
524     }
525     if (x == ':') break;
526   }
527   int valueoffset = length;
528   buffer[length-1] = 0;
529 
530   // skip to start of value:
531   for (;;) {
532     x = getc(fin);
533     if ((x < 0 && feof(fin)) || x == '\n' || !isspace(x & 255)) break;
534   }
535 
536   // read the value:
537   for (;;) {
538     if (x == '\\') {x = read_quoted(); if (x<0) continue;}
539     else if (x == '\n') break;
540     buffer[length++] = x;
541     expand_buffer(length);
542     x = getc(fin);
543   }
544   buffer[length] = 0;
545   name = buffer;
546   value = buffer+valueoffset;
547   return 1;
548 }
549 
550 int fdesign_flip;
551 int fdesign_magic;
552 #include <FL/Fl_Group.H>
553 
554 static const char *class_matcher[] = {
555 "FL_CHECKBUTTON", "Fl_Check_Button",
556 "FL_ROUNDBUTTON", "Fl_Round_Button",
557 "FL_ROUND3DBUTTON", "Fl_Round_Button",
558 "FL_LIGHTBUTTON", "Fl_Light_Button",
559 "FL_FRAME", "Fl_Box",
560 "FL_LABELFRAME", "Fl_Box",
561 "FL_TEXT", "Fl_Box",
562 "FL_VALSLIDER", "Fl_Value_Slider",
563 "FL_MENU", "Fl_Menu_Button",
564 "3", "FL_BITMAP",
565 "1", "FL_BOX",
566 "71","FL_BROWSER",
567 "11","FL_BUTTON",
568 "4", "FL_CHART",
569 "42","FL_CHOICE",
570 "61","FL_CLOCK",
571 "25","FL_COUNTER",
572 "22","FL_DIAL",
573 "101","FL_FREE",
574 "31","FL_INPUT",
575 "12","Fl_Light_Button",
576 "41","FL_MENU",
577 "23","FL_POSITIONER",
578 "13","Fl_Round_Button",
579 "21","FL_SLIDER",
580 "2", "FL_BOX", // was FL_TEXT
581 "62","FL_TIMER",
582 "24","Fl_Value_Slider",
583 0};
584 
read_fdesign()585 void read_fdesign() {
586   fdesign_magic = atoi(read_word());
587   fdesign_flip = (fdesign_magic < 13000);
588   Fl_Widget_Type *window = 0;
589   Fl_Widget_Type *group = 0;
590   Fl_Widget_Type *widget = 0;
591   if (!Fl_Type::current) {
592     Fl_Type *t = Fl_Type_make("Function");
593     t->name("create_the_forms()");
594     Fl_Type::current = t;
595   }
596   for (;;) {
597     const char *name;
598     const char *value;
599     if (!read_fdesign_line(name, value)) break;
600 
601     if (!strcmp(name,"Name")) {
602 
603       window = (Fl_Widget_Type*)Fl_Type_make("Fl_Window");
604       window->name(value);
605       window->label(value);
606       Fl_Type::current = widget = window;
607 
608     } else if (!strcmp(name,"class")) {
609 
610       if (!strcmp(value,"FL_BEGIN_GROUP")) {
611 	group = widget = (Fl_Widget_Type*)Fl_Type_make("Fl_Group");
612 	Fl_Type::current = group;
613       } else if (!strcmp(value,"FL_END_GROUP")) {
614 	if (group) {
615 	  Fl_Group* g = (Fl_Group*)(group->o);
616 	  g->begin();
617 	  g->forms_end();
618 	  Fl_Group::current(0);
619 	}
620 	group = widget = 0;
621 	Fl_Type::current = window;
622       } else {
623 	for (int i = 0; class_matcher[i]; i += 2)
624 	  if (!strcmp(value,class_matcher[i])) {
625 	    value = class_matcher[i+1]; break;}
626 	widget = (Fl_Widget_Type*)Fl_Type_make(value);
627 	if (!widget) {
628 	  printf("class %s not found, using Fl_Button\n", value);
629 	  widget = (Fl_Widget_Type*)Fl_Type_make("Fl_Button");
630 	}
631       }
632 
633     } else if (widget) {
634       if (!widget->read_fdesign(name, value))
635 	printf("Ignoring \"%s: %s\"\n", name, value);
636     }
637   }
638 }
639 
640 //
641 // End of "$Id$".
642 //
643