e42fac099f
This is compatible to old Fluid files. The .fl file will not change (except for the project wide setting itself) and no information is lost. As the only limitation, it is not possible to write any combination of FL_META and FL_CTRL while "use FL_COMMAND" is set. git-svn-id: file:///fltk/svn/fltk/branches/branch-1.1@5808 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
651 lines
16 KiB
C++
651 lines
16 KiB
C++
//
|
|
// "$Id$"
|
|
//
|
|
// Fluid file routines for the Fast Light Tool Kit (FLTK).
|
|
//
|
|
// You may find the basic read_* and write_* routines to
|
|
// be useful for other programs. I have used them many times.
|
|
// They are somewhat similar to tcl, using matching { and }
|
|
// to quote strings.
|
|
//
|
|
// Copyright 1998-2006 by Bill Spitzak and others.
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Library General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Library General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Library General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
// USA.
|
|
//
|
|
// Please report all bugs and problems on the following page:
|
|
//
|
|
// http://www.fltk.org/str.php
|
|
//
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include "../src/flstring.h"
|
|
#include <stdarg.h>
|
|
#include "alignment_panel.h"
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// BASIC FILE WRITING:
|
|
|
|
static FILE *fout;
|
|
|
|
int open_write(const char *s) {
|
|
if (!s) {fout = stdout; return 1;}
|
|
FILE *f = fopen(s,"w");
|
|
if (!f) return 0;
|
|
fout = f;
|
|
return 1;
|
|
}
|
|
|
|
int close_write() {
|
|
if (fout != stdout) {
|
|
int x = fclose(fout);
|
|
fout = stdout;
|
|
return x >= 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int needspace;
|
|
int is_id(char); // in code.C
|
|
|
|
// write a string, quoting characters if necessary:
|
|
void write_word(const char *w) {
|
|
if (needspace) putc(' ', fout);
|
|
needspace = 1;
|
|
if (!w || !*w) {fprintf(fout,"{}"); return;}
|
|
const char *p;
|
|
// see if it is a single word:
|
|
for (p = w; is_id(*p); p++) ;
|
|
if (!*p) {fprintf(fout,"%s",w); return;}
|
|
// see if there are matching braces:
|
|
int n = 0;
|
|
for (p = w; *p; p++) {
|
|
if (*p == '{') n++;
|
|
else if (*p == '}') {n--; if (n<0) break;}
|
|
}
|
|
int mismatched = (n != 0);
|
|
// write out brace-quoted string:
|
|
putc('{', fout);
|
|
for (; *w; w++) {
|
|
switch (*w) {
|
|
case '{':
|
|
case '}':
|
|
if (!mismatched) break;
|
|
case '\\':
|
|
case '#':
|
|
putc('\\',fout);
|
|
break;
|
|
}
|
|
putc(*w,fout);
|
|
}
|
|
putc('}', fout);
|
|
}
|
|
|
|
// write an arbitrary formatted word, or a comment, etc:
|
|
void write_string(const char *format, ...) {
|
|
va_list args;
|
|
va_start(args, format);
|
|
if (needspace) fputc(' ',fout);
|
|
vfprintf(fout, format, args);
|
|
va_end(args);
|
|
needspace = !isspace(format[strlen(format)-1] & 255);
|
|
}
|
|
|
|
// start a new line and indent it for a given nesting level:
|
|
void write_indent(int n) {
|
|
fputc('\n',fout);
|
|
while (n--) {fputc(' ',fout); fputc(' ',fout);}
|
|
needspace = 0;
|
|
}
|
|
|
|
// write a '{' at the given indenting level:
|
|
void write_open(int) {
|
|
if (needspace) fputc(' ',fout);
|
|
fputc('{',fout);
|
|
needspace = 0;
|
|
}
|
|
|
|
// write a '}' at the given indenting level:
|
|
void write_close(int n) {
|
|
if (needspace) write_indent(n);
|
|
fputc('}',fout);
|
|
needspace = 1;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// BASIC FILE READING:
|
|
|
|
static FILE *fin;
|
|
static int lineno;
|
|
static const char *fname;
|
|
|
|
int open_read(const char *s) {
|
|
lineno = 1;
|
|
if (!s) {fin = stdin; fname = "stdin"; return 1;}
|
|
FILE *f = fopen(s,"r");
|
|
if (!f) return 0;
|
|
fin = f;
|
|
fname = s;
|
|
return 1;
|
|
}
|
|
|
|
int close_read() {
|
|
if (fin != stdin) {
|
|
int x = fclose(fin);
|
|
fin = 0;
|
|
return x >= 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#include <FL/fl_message.H>
|
|
|
|
void read_error(const char *format, ...) {
|
|
va_list args;
|
|
va_start(args, format);
|
|
if (!fin) {
|
|
char buffer[1024];
|
|
vsnprintf(buffer, sizeof(buffer), format, args);
|
|
fl_message(buffer);
|
|
} else {
|
|
fprintf(stderr, "%s:%d: ", fname, lineno);
|
|
vfprintf(stderr, format, args);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
va_end(args);
|
|
}
|
|
|
|
static int hexdigit(int x) {
|
|
if (isdigit(x)) return x-'0';
|
|
if (isupper(x)) return x-'A'+10;
|
|
if (islower(x)) return x-'a'+10;
|
|
return 20;
|
|
}
|
|
|
|
|
|
static int read_quoted() { // read whatever character is after a \ .
|
|
int c,d,x;
|
|
switch(c = fgetc(fin)) {
|
|
case '\n': lineno++; return -1;
|
|
case 'a' : return('\a');
|
|
case 'b' : return('\b');
|
|
case 'f' : return('\f');
|
|
case 'n' : return('\n');
|
|
case 'r' : return('\r');
|
|
case 't' : return('\t');
|
|
case 'v' : return('\v');
|
|
case 'x' : /* read hex */
|
|
for (c=x=0; x<3; x++) {
|
|
int ch = fgetc(fin);
|
|
d = hexdigit(ch);
|
|
if (d > 15) {ungetc(ch,fin); break;}
|
|
c = (c<<4)+d;
|
|
}
|
|
break;
|
|
default: /* read octal */
|
|
if (c<'0' || c>'7') break;
|
|
c -= '0';
|
|
for (x=0; x<2; x++) {
|
|
int ch = fgetc(fin);
|
|
d = hexdigit(ch);
|
|
if (d>7) {ungetc(ch,fin); break;}
|
|
c = (c<<3)+d;
|
|
}
|
|
break;
|
|
}
|
|
return(c);
|
|
}
|
|
|
|
// return a word read from the file, or NULL at the EOF:
|
|
// This will skip all comments (# to end of line), and evaluate
|
|
// all \xxx sequences and use \ at the end of line to remove the newline.
|
|
// A word is any one of:
|
|
// a continuous string of non-space chars except { and } and #
|
|
// everything between matching {...} (unless wantbrace != 0)
|
|
// the characters '{' and '}'
|
|
|
|
static char *buffer;
|
|
static int buflen;
|
|
static void expand_buffer(int length) {
|
|
if (length >= buflen) {
|
|
if (!buflen) {
|
|
buflen = length+1;
|
|
buffer = (char*)malloc(buflen);
|
|
} else {
|
|
buflen = 2*buflen;
|
|
if (length >= buflen) buflen = length+1;
|
|
buffer = (char *)realloc((void *)buffer,buflen);
|
|
}
|
|
}
|
|
}
|
|
|
|
const char *read_word(int wantbrace) {
|
|
int x;
|
|
|
|
// skip all the whitespace before it:
|
|
for (;;) {
|
|
x = getc(fin);
|
|
if (x < 0 && feof(fin)) { // eof
|
|
return 0;
|
|
} else if (x == '#') { // comment
|
|
do x = getc(fin); while (x >= 0 && x != '\n');
|
|
lineno++;
|
|
continue;
|
|
} else if (x == '\n') {
|
|
lineno++;
|
|
} else if (!isspace(x & 255)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
expand_buffer(100);
|
|
|
|
if (x == '{' && !wantbrace) {
|
|
|
|
// read in whatever is between braces
|
|
int length = 0;
|
|
int nesting = 0;
|
|
for (;;) {
|
|
x = getc(fin);
|
|
if (x<0) {read_error("Missing '}'"); break;}
|
|
else if (x == '#') { // embedded comment
|
|
do x = getc(fin); while (x >= 0 && x != '\n');
|
|
lineno++;
|
|
continue;
|
|
} else if (x == '\n') lineno++;
|
|
else if (x == '\\') {x = read_quoted(); if (x<0) continue;}
|
|
else if (x == '{') nesting++;
|
|
else if (x == '}') {if (!nesting--) break;}
|
|
buffer[length++] = x;
|
|
expand_buffer(length);
|
|
}
|
|
buffer[length] = 0;
|
|
return buffer;
|
|
|
|
} else if (x == '{' || x == '}') {
|
|
// all the punctuation is a word:
|
|
buffer[0] = x;
|
|
buffer[1] = 0;
|
|
return buffer;
|
|
|
|
} else {
|
|
|
|
// read in an unquoted word:
|
|
int length = 0;
|
|
for (;;) {
|
|
if (x == '\\') {x = read_quoted(); if (x<0) continue;}
|
|
else if (x<0 || isspace(x & 255) || x=='{' || x=='}' || x=='#') break;
|
|
buffer[length++] = x;
|
|
expand_buffer(length);
|
|
x = getc(fin);
|
|
}
|
|
ungetc(x, fin);
|
|
buffer[length] = 0;
|
|
return buffer;
|
|
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
#include <FL/Fl.H>
|
|
#include "Fl_Widget_Type.h"
|
|
|
|
// global int variables:
|
|
extern int i18n_type;
|
|
extern const char* i18n_include;
|
|
extern const char* i18n_function;
|
|
extern const char* i18n_file;
|
|
extern const char* i18n_set;
|
|
|
|
|
|
extern int header_file_set;
|
|
extern int code_file_set;
|
|
extern const char* header_file_name;
|
|
extern const char* code_file_name;
|
|
|
|
int write_file(const char *filename, int selected_only) {
|
|
if (!open_write(filename)) return 0;
|
|
write_string("# data file for the Fltk User Interface Designer (fluid)\n"
|
|
"version %.4f",FL_VERSION);
|
|
if(!include_H_from_C)
|
|
write_string("\ndo_not_include_H_from_C");
|
|
if(use_FL_COMMAND)
|
|
write_string("\nuse_FL_COMMAND");
|
|
if (i18n_type) {
|
|
write_string("\ni18n_type %d", i18n_type);
|
|
write_string("\ni18n_include %s", i18n_include);
|
|
switch (i18n_type) {
|
|
case 1 : /* GNU gettext */
|
|
write_string("\ni18n_function %s", i18n_function);
|
|
break;
|
|
case 2 : /* POSIX catgets */
|
|
if (i18n_file[0]) write_string("\ni18n_file %s", i18n_file);
|
|
write_string("\ni18n_set %s", i18n_set);
|
|
break;
|
|
}
|
|
}
|
|
if (!selected_only) {
|
|
write_string("\nheader_name"); write_word(header_file_name);
|
|
write_string("\ncode_name"); write_word(code_file_name);
|
|
}
|
|
for (Fl_Type *p = Fl_Type::first; p;) {
|
|
if (!selected_only || p->selected) {
|
|
p->write();
|
|
write_string("\n");
|
|
int q = p->level;
|
|
for (p = p->next; p && p->level > q; p = p->next);
|
|
} else {
|
|
p = p->next;
|
|
}
|
|
}
|
|
return close_write();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// read all the objects out of the input file:
|
|
|
|
void read_fdesign();
|
|
|
|
double read_version;
|
|
|
|
extern Fl_Type *Fl_Type_make(const char *tn);
|
|
|
|
static void read_children(Fl_Type *p, int paste) {
|
|
Fl_Type::current = p;
|
|
for (;;) {
|
|
const char *c = read_word();
|
|
REUSE_C:
|
|
if (!c) {
|
|
if (p && !paste) read_error("Missing '}'");
|
|
break;
|
|
}
|
|
|
|
if (!strcmp(c,"}")) {
|
|
if (!p) read_error("Unexpected '}'");
|
|
break;
|
|
}
|
|
|
|
// this is the first word in a .fd file:
|
|
if (!strcmp(c,"Magic:")) {
|
|
read_fdesign();
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(c,"version")) {
|
|
c = read_word();
|
|
read_version = strtod(c,0);
|
|
if (read_version<=0 || read_version>FL_VERSION)
|
|
read_error("unknown version '%s'",c);
|
|
continue;
|
|
}
|
|
|
|
// back compatability with Vincent Penne's original class code:
|
|
if (!p && !strcmp(c,"define_in_struct")) {
|
|
Fl_Type *t = Fl_Type_make("class");
|
|
t->name(read_word());
|
|
Fl_Type::current = p = t;
|
|
paste = 1; // stops "missing }" error
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(c,"do_not_include_H_from_C")) {
|
|
include_H_from_C=0;
|
|
goto CONTINUE;
|
|
}
|
|
if (!strcmp(c,"use_FL_COMMAND")) {
|
|
use_FL_COMMAND=1;
|
|
goto CONTINUE;
|
|
}
|
|
if (!strcmp(c,"i18n_type")) {
|
|
i18n_type = atoi(read_word());
|
|
goto CONTINUE;
|
|
}
|
|
if (!strcmp(c,"i18n_function")) {
|
|
i18n_function = strdup(read_word());
|
|
goto CONTINUE;
|
|
}
|
|
if (!strcmp(c,"i18n_file")) {
|
|
i18n_file = strdup(read_word());
|
|
goto CONTINUE;
|
|
}
|
|
if (!strcmp(c,"i18n_set")) {
|
|
i18n_set = strdup(read_word());
|
|
goto CONTINUE;
|
|
}
|
|
if (!strcmp(c,"i18n_include")) {
|
|
i18n_include = strdup(read_word());
|
|
goto CONTINUE;
|
|
}
|
|
if (!strcmp(c,"i18n_type"))
|
|
{
|
|
i18n_type = atoi(read_word());
|
|
goto CONTINUE;
|
|
}
|
|
if (!strcmp(c,"i18n_type"))
|
|
{
|
|
i18n_type = atoi(read_word());
|
|
goto CONTINUE;
|
|
}
|
|
if (!strcmp(c,"header_name")) {
|
|
if (!header_file_set) header_file_name = strdup(read_word());
|
|
else read_word();
|
|
goto CONTINUE;
|
|
}
|
|
|
|
if (!strcmp(c,"code_name")) {
|
|
if (!code_file_set) code_file_name = strdup(read_word());
|
|
else read_word();
|
|
goto CONTINUE;
|
|
}
|
|
|
|
if (!strcmp(c, "snap") || !strcmp(c, "gridx") || !strcmp(c, "gridy")) {
|
|
// grid settings are now global
|
|
read_word();
|
|
goto CONTINUE;
|
|
}
|
|
|
|
{Fl_Type *t = Fl_Type_make(c);
|
|
if (!t) {
|
|
read_error("Unknown word \"%s\"", c);
|
|
continue;
|
|
}
|
|
t->name(read_word());
|
|
|
|
c = read_word(1);
|
|
if (strcmp(c,"{") && t->is_class()) { // <prefix> <name>
|
|
((Fl_Class_Type*)t)->prefix(t->name());
|
|
t->name(c);
|
|
c = read_word(1);
|
|
}
|
|
|
|
if (strcmp(c,"{")) {
|
|
read_error("Missing property list for %s\n",t->title());
|
|
goto REUSE_C;
|
|
}
|
|
|
|
t->open_ = 0;
|
|
for (;;) {
|
|
const char *cc = read_word();
|
|
if (!cc || !strcmp(cc,"}")) break;
|
|
t->read_property(cc);
|
|
}
|
|
|
|
if (!t->is_parent()) continue;
|
|
c = read_word(1);
|
|
if (strcmp(c,"{")) {
|
|
read_error("Missing child list for %s\n",t->title());
|
|
goto REUSE_C;
|
|
}
|
|
read_children(t, 0);}
|
|
Fl_Type::current = p;
|
|
CONTINUE:;
|
|
}
|
|
}
|
|
|
|
extern void deselect();
|
|
|
|
int read_file(const char *filename, int merge) {
|
|
Fl_Type *o;
|
|
read_version = 0.0;
|
|
if (!open_read(filename)) return 0;
|
|
if (merge) deselect(); else delete_all();
|
|
read_children(Fl_Type::current, merge);
|
|
Fl_Type::current = 0;
|
|
// Force menu items to be rebuilt...
|
|
for (o = Fl_Type::first; o; o = o->next)
|
|
if (o->is_menu_button()) o->add_child(0,0);
|
|
for (o = Fl_Type::first; o; o = o->next)
|
|
if (o->selected) {Fl_Type::current = o; break;}
|
|
selection_changed(Fl_Type::current);
|
|
return close_read();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Read Forms and XForms fdesign files:
|
|
|
|
int read_fdesign_line(const char*& name, const char*& value) {
|
|
|
|
int length = 0;
|
|
int x;
|
|
// find a colon:
|
|
for (;;) {
|
|
x = getc(fin);
|
|
if (x < 0 && feof(fin)) return 0;
|
|
if (x == '\n') {length = 0; continue;} // no colon this line...
|
|
if (!isspace(x & 255)) {
|
|
buffer[length++] = x;
|
|
expand_buffer(length);
|
|
}
|
|
if (x == ':') break;
|
|
}
|
|
int valueoffset = length;
|
|
buffer[length-1] = 0;
|
|
|
|
// skip to start of value:
|
|
for (;;) {
|
|
x = getc(fin);
|
|
if ((x < 0 && feof(fin)) || x == '\n' || !isspace(x & 255)) break;
|
|
}
|
|
|
|
// read the value:
|
|
for (;;) {
|
|
if (x == '\\') {x = read_quoted(); if (x<0) continue;}
|
|
else if (x == '\n') break;
|
|
buffer[length++] = x;
|
|
expand_buffer(length);
|
|
x = getc(fin);
|
|
}
|
|
buffer[length] = 0;
|
|
name = buffer;
|
|
value = buffer+valueoffset;
|
|
return 1;
|
|
}
|
|
|
|
int fdesign_flip;
|
|
int fdesign_magic;
|
|
#include <FL/Fl_Group.H>
|
|
|
|
static const char *class_matcher[] = {
|
|
"FL_CHECKBUTTON", "Fl_Check_Button",
|
|
"FL_ROUNDBUTTON", "Fl_Round_Button",
|
|
"FL_ROUND3DBUTTON", "Fl_Round_Button",
|
|
"FL_LIGHTBUTTON", "Fl_Light_Button",
|
|
"FL_FRAME", "Fl_Box",
|
|
"FL_LABELFRAME", "Fl_Box",
|
|
"FL_TEXT", "Fl_Box",
|
|
"FL_VALSLIDER", "Fl_Value_Slider",
|
|
"FL_MENU", "Fl_Menu_Button",
|
|
"3", "FL_BITMAP",
|
|
"1", "FL_BOX",
|
|
"71","FL_BROWSER",
|
|
"11","FL_BUTTON",
|
|
"4", "FL_CHART",
|
|
"42","FL_CHOICE",
|
|
"61","FL_CLOCK",
|
|
"25","FL_COUNTER",
|
|
"22","FL_DIAL",
|
|
"101","FL_FREE",
|
|
"31","FL_INPUT",
|
|
"12","Fl_Light_Button",
|
|
"41","FL_MENU",
|
|
"23","FL_POSITIONER",
|
|
"13","Fl_Round_Button",
|
|
"21","FL_SLIDER",
|
|
"2", "FL_BOX", // was FL_TEXT
|
|
"62","FL_TIMER",
|
|
"24","Fl_Value_Slider",
|
|
0};
|
|
|
|
void read_fdesign() {
|
|
fdesign_magic = atoi(read_word());
|
|
fdesign_flip = (fdesign_magic < 13000);
|
|
Fl_Widget_Type *window = 0;
|
|
Fl_Widget_Type *group = 0;
|
|
Fl_Widget_Type *widget = 0;
|
|
if (!Fl_Type::current) {
|
|
Fl_Type *t = Fl_Type_make("Function");
|
|
t->name("create_the_forms()");
|
|
Fl_Type::current = t;
|
|
}
|
|
for (;;) {
|
|
const char *name;
|
|
const char *value;
|
|
if (!read_fdesign_line(name, value)) break;
|
|
|
|
if (!strcmp(name,"Name")) {
|
|
|
|
window = (Fl_Widget_Type*)Fl_Type_make("Fl_Window");
|
|
window->name(value);
|
|
window->label(value);
|
|
Fl_Type::current = widget = window;
|
|
|
|
} else if (!strcmp(name,"class")) {
|
|
|
|
if (!strcmp(value,"FL_BEGIN_GROUP")) {
|
|
group = widget = (Fl_Widget_Type*)Fl_Type_make("Fl_Group");
|
|
Fl_Type::current = group;
|
|
} else if (!strcmp(value,"FL_END_GROUP")) {
|
|
if (group) {
|
|
Fl_Group* g = (Fl_Group*)(group->o);
|
|
g->begin();
|
|
g->forms_end();
|
|
Fl_Group::current(0);
|
|
}
|
|
group = widget = 0;
|
|
Fl_Type::current = window;
|
|
} else {
|
|
for (int i = 0; class_matcher[i]; i += 2)
|
|
if (!strcmp(value,class_matcher[i])) {
|
|
value = class_matcher[i+1]; break;}
|
|
widget = (Fl_Widget_Type*)Fl_Type_make(value);
|
|
if (!widget) {
|
|
printf("class %s not found, using Fl_Button\n", value);
|
|
widget = (Fl_Widget_Type*)Fl_Type_make("Fl_Button");
|
|
}
|
|
}
|
|
|
|
} else if (widget) {
|
|
if (!widget->read_fdesign(name, value))
|
|
printf("Ignoring \"%s: %s\"\n", name, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// End of "$Id$".
|
|
//
|