1 /************************************************************************
2 ************************************************************************
4 Copyright (C) 2003-2004 GRAME, Centre National de Creation Musicale
5 ---------------------------------------------------------------------
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 ************************************************************************
20 ************************************************************************/
23 * @file drawschema.cpp
24 * Implement block-diagram schema generation in svg or postscript format.
25 * The result is a folder containing one or more schema files in svg or
26 * ps format. Complex block-diagrams are automatically splitted.
33 #include <sys/types.h>
53 #include "occurrences.hh"
54 #include "boxcomplexity.h"
57 #include "drawschema.hh"
58 #include "compatibility.hh"
60 #include "description.hh"
61 #include "property.hh"
66 #define linkcolor "#b3d1dc"
67 #define normalcolor "#ffeaa2"
68 #define uicolor "#F1CFA1"
69 #define slotcolor "#ffffd7"
70 #define numcolor "#ffffff"
74 #define linkcolor "#F57900"
75 #define normalcolor "#4B71A1"
76 #define uicolor "#47945E"
77 #define slotcolor "#EDD400"
78 #define numcolor "#4B71A1"
82 #define linkcolor "#47945E"
83 #define normalcolor "#4B71A1"
84 #define uicolor "#f44800"
85 #define slotcolor "#EDD400"
86 #define numcolor "#f44800"
90 #define linkcolor "#47945E"
91 #define normalcolor "#4B71A1"
92 #define uicolor "#816647"
93 #define slotcolor "#EDD400"
94 #define numcolor "#f44800"
98 #define linkcolor "#003366"
99 #define normalcolor "#4B71A1"
100 #define uicolor "#816647"
101 #define slotcolor "#EDD400"
102 #define numcolor "#f44800"
106 #define linkcolor "#003366"
107 #define normalcolor "#4B71A1"
108 #define uicolor "#477881"
109 #define slotcolor "#816647"
110 #define numcolor "#f44800"
115 #define linkcolor "#003366"
116 #define normalcolor "#4B71A1"
117 #define uicolor "#477881"
118 #define slotcolor "#47945E"
119 #define numcolor "#f44800"
120 #define invcolor "#ffffff"
125 // external parameters
126 extern int gFoldThreshold
; // max diagram complexity before folding
129 // internal state during drawing
130 static Occurrences
* gOccurrences
;
131 static bool sFoldingFlag
; // true with complex block-diagrams
132 static stack
<Tree
> gPendingExp
; // Expressions that need to be drawn
133 static set
<Tree
> gDrawnExp
; // Expressions drawn or scheduled so far
134 static const char* gDevSuffix
; // .svg or .ps used to choose output device
135 static char gCurrentDir
[512]; // room to save current directory name
136 static string gSchemaFileName
; // name of schema file beeing generated
137 static map
<Tree
,string
> gBackLink
; // link to enclosing file for sub schema
139 // prototypes of internal functions
140 static void writeSchemaFile(Tree bd
);
141 static schema
* generateDiagramSchema (Tree bd
);
142 static schema
* generateInsideSchema(Tree t
);
143 static void scheduleDrawing(Tree t
);
144 static bool pendingDrawing(Tree
& t
);
145 static schema
* generateAbstractionSchema(schema
* x
, Tree t
);
146 static schema
* generateOutputSlotSchema(Tree a
);
147 static schema
* generateInputSlotSchema(Tree a
);
148 static schema
* generateBargraphSchema(Tree t
);
149 static schema
* generateUserInterfaceSchema(Tree t
);
150 static char* legalFileName(Tree t
, int n
, char* dst
);
151 static int cholddir ();
152 static int mkchdir(const char* dirname
);
158 *The entry point to generate from a block diagram as a set of
159 *svg files stored in the directory "<projname>-svg/" or
160 *"<projname>-ps/" depending of <dev>.
162 void drawSchema(Tree bd
, const char* projname
, const char* dev
)
165 sFoldingFlag
= boxComplexity(bd
) > gFoldThreshold
;
167 mkchdir(projname
); // create a directory to store files
169 scheduleDrawing(bd
); // schedule the initial drawing
171 Tree t
; while (pendingDrawing(t
)) {
172 writeSchemaFile(t
); // generate all the pending drawing
175 cholddir(); // return to current directory
179 /************************************************************************
180 ************************************************************************
182 ************************************************************************
183 ************************************************************************/
186 //------------------- to schedule and retreive drawing ------------------
189 * Schedule a makeBlockSchema diagram to be drawn.
191 static void scheduleDrawing(Tree t
)
193 if (gDrawnExp
.find(t
) == gDrawnExp
.end()) {
195 gBackLink
.insert(make_pair(t
,gSchemaFileName
)); // remember the enclosing filename
201 * Retrieve next block diagram that must be drawn
203 static bool pendingDrawing(Tree
& t
)
205 if (gPendingExp
.empty()) return false;
206 t
= gPendingExp
.top();
213 //------------------------ dealing with files -------------------------
216 * Write a top level diagram. A top level diagram
217 * is decorated with its definition name property
218 * and is drawn in an individual file
220 static void writeSchemaFile(Tree bd
)
227 gOccurrences
= new Occurrences(bd
);
229 bool hasname
= getDefNameProperty(bd
, id
);
233 // create an arbitrary name
234 id
= tree(Node(unique("diagram_")));
237 // generate legal file name for the schema
238 stringstream s1
; s1
<< legalFileName(bd
, 1024, temp
) << "." << gDevSuffix
;
239 gSchemaFileName
= s1
.str();
241 // generate the label of the schema
242 stringstream s2
; s2
<< tree2str(id
);
243 string link
= gBackLink
[bd
];
244 ts
= makeTopSchema(generateInsideSchema(bd
), 20, s2
.str(), link
);
245 // draw to the device defined by gDevSuffix
246 if (strcmp(gDevSuffix
, "svg") == 0) {
247 SVGDev
dev(s1
.str().c_str(), ts
->width(), ts
->height());
248 ts
->place(0,0, kLeftRight
);
250 { collector c
; ts
->collectTraits(c
); c
.draw(dev
); }
252 PSDev
dev(s1
.str().c_str(), ts
->width(), ts
->height());
253 ts
->place(0,0, kLeftRight
);
257 ts
->collectTraits(c
);
266 * Create a new directory in the current one to store the diagrams.
267 * The current directory is saved to be later restaured.
269 static int mkchdir(const char* dirname
)
271 //cerr << "mkchdir of " << dirname << endl;
272 if (getcwd(gCurrentDir
, 512) != 0) {
273 int status
= mkdir(dirname
, S_IRWXU
| S_IRWXG
| S_IROTH
| S_IXOTH
);
274 if (status
== 0 || errno
== EEXIST
) {
275 if (chdir(dirname
) == 0) {
287 *Switch back to the previously stored current directory
289 static int cholddir ()
291 if (chdir(gCurrentDir
) == 0) {
301 * Transform the definition name property of tree <t> into a
302 * legal file name. The resulting file name is stored in
303 * <dst> a table of at least <n> chars. Returns the <dst> pointer
306 static char* legalFileName(Tree t
, int n
, char* dst
)
310 if (getDefNameProperty(t
, id
)) {
311 const char* src
= tree2str(id
);
312 for (i
=0; isalnum(src
[i
]) && i
<16; i
++) {
317 if (strcmp(dst
, "process") != 0) {
318 // if it is not process add the hex address to make the name unique
319 snprintf(&dst
[i
], n
-i
, "-%p", t
);
326 //------------------------ generating the schema -------------------------
330 * isInverter(t) returns true if t == '*(-1)'. This test is used
331 * to simplify diagram by using a special symbol for inverters.
335 static bool isInverter(Tree t
)
337 // init gInverted table. For some reason doesn't work if done outside
338 if (gInverter
[0] == 0) {
339 gInverter
[0] = boxSeq(boxPar(boxWire(), boxInt(-1)),boxPrim2(sigMul
));
340 gInverter
[1] = boxSeq(boxPar(boxInt(-1), boxWire()),boxPrim2(sigMul
));
341 gInverter
[2] = boxSeq(boxPar(boxWire(), boxReal(-1.0)),boxPrim2(sigMul
));
342 gInverter
[3] = boxSeq(boxPar(boxReal(-1.0), boxWire()),boxPrim2(sigMul
));
343 gInverter
[4] = boxSeq(boxPar(boxInt(0), boxWire()),boxPrim2(sigSub
));
344 gInverter
[5] = boxSeq(boxPar(boxReal(0.0), boxWire()),boxPrim2(sigSub
));
347 //cerr << "isInverter " << t << '$' << boxpp(t) << endl;
348 for (int i
=0; i
<6; i
++) {
349 if (t
== gInverter
[i
]) return true;
356 * Compute the Pure Routing property, that is expressions
357 * only made of cut, wires and slots. No labels will be
358 * dispayed for pure routing expressions.
360 property
<bool> gPureRoutingProperty
;
362 static bool isPureRouting(Tree t
)
368 if (gPureRoutingProperty
.get(t
,r
)) {
370 } else if ( isBoxCut(t
)
374 || (isBoxPar(t
,x
,y
) && isPureRouting(x
) && isPureRouting(y
))
375 || (isBoxSeq(t
,x
,y
) && isPureRouting(x
) && isPureRouting(y
))
376 || (isBoxSplit(t
,x
,y
) && isPureRouting(x
) && isPureRouting(y
))
377 || (isBoxMerge(t
,x
,y
) && isPureRouting(x
) && isPureRouting(y
))
379 gPureRoutingProperty
.set(t
,true);
382 gPureRoutingProperty
.set(t
,false);
389 * Generate an appropriate schema according to
390 * the type of block diagram. When folding is requiered,
391 * instead of going down block-diagrams with a name,
392 * schedule them for an individual file.
394 static schema
* generateDiagramSchema(Tree t
)
399 //cerr << t << " generateDiagramSchema " << boxpp(t)<< endl;
401 if (getDefNameProperty(t
, id
)) {
402 stringstream s
; s
<< tree2str(id
);
403 //cerr << t << "\tNAMED : " << s.str() << endl;
406 if ( sFoldingFlag
&& /*(gOccurrences->getCount(t) > 0) &&*/
407 (boxComplexity(t
) > 2) && getDefNameProperty(t
, id
)) {
409 getBoxType(t
, &ins
, &outs
);
412 l
<< legalFileName(t
,1024,temp
) << "." << gDevSuffix
;
414 return makeBlockSchema(ins
, outs
, s
.str(), linkcolor
, l
.str());
416 } else if (getDefNameProperty(t
, id
) && ! isPureRouting(t
)) {
417 // named case : not a slot, with a name
418 // draw a line around the object with its name
419 stringstream s
; s
<< tree2str(id
);
420 return makeDecorateSchema(generateInsideSchema(t
), 10, s
.str());
424 return generateInsideSchema(t
);
431 * Generate the inside schema of a block diagram
432 * according to its type
434 static schema
* generateInsideSchema(Tree t
)
436 Tree a
, b
, ff
, l
, type
,name
,file
;
447 xtended
* xt
= (xtended
*)getUserData(t
);
449 if (xt
) { return makeBlockSchema(xt
->arity(), 1, xt
->name(), normalcolor
, ""); }
451 else if (isInverter(t
)) { return makeInverterSchema(invcolor
); }
453 else if (isBoxInt(t
, &i
)) { stringstream s
; s
<< i
; return makeBlockSchema(0, 1, s
.str(), numcolor
, "" ); }
454 else if (isBoxReal(t
, &r
)) { stringstream s
; s
<< r
; return makeBlockSchema(0, 1, s
.str(), numcolor
, "" ); }
455 else if (isBoxWire(t
)) { return makeCableSchema(); }
456 else if (isBoxCut(t
)) { return makeCutSchema(); }
458 else if (isBoxPrim0(t
, &p0
)) { return makeBlockSchema(0, 1, prim0name(p0
), normalcolor
, ""); }
459 else if (isBoxPrim1(t
, &p1
)) { return makeBlockSchema(1, 1, prim1name(p1
), normalcolor
, ""); }
460 else if (isBoxPrim2(t
, &p2
)) { return makeBlockSchema(2, 1, prim2name(p2
), normalcolor
, ""); }
461 else if (isBoxPrim3(t
, &p3
)) { return makeBlockSchema(3, 1, prim3name(p3
), normalcolor
, ""); }
462 else if (isBoxPrim4(t
, &p4
)) { return makeBlockSchema(4, 1, prim4name(p4
), normalcolor
, ""); }
463 else if (isBoxPrim5(t
, &p5
)) { return makeBlockSchema(5, 1, prim5name(p5
), normalcolor
, ""); }
465 else if (isBoxFFun(t
, ff
)) { return makeBlockSchema(ffarity(ff
), 1, ffname(ff
), normalcolor
, ""); }
466 else if (isBoxFConst(t
, type
,name
,file
)) { return makeBlockSchema(0, 1, tree2str(name
), normalcolor
, ""); }
467 else if (isBoxFVar (t
, type
, name
,file
)) { return makeBlockSchema(0, 1, tree2str(name
), normalcolor
, ""); }
469 else if (isBoxButton(t
)) { return generateUserInterfaceSchema(t
); }
470 else if (isBoxCheckbox(t
)) { return generateUserInterfaceSchema(t
); }
471 else if (isBoxVSlider(t
)) { return generateUserInterfaceSchema(t
); }
472 else if (isBoxHSlider(t
)) { return generateUserInterfaceSchema(t
); }
473 else if (isBoxNumEntry(t
)) { return generateUserInterfaceSchema(t
); }
474 else if (isBoxVBargraph(t
)) { return generateBargraphSchema(t
); }
475 else if (isBoxHBargraph(t
)) { return generateBargraphSchema(t
); }
477 // don't draw group rectangle when labels are empty (ie "")
478 else if (isBoxVGroup(t
,l
,a
)) { stringstream s
; s
<< "vgroup(" << extractName(l
) << ")";
479 schema
* r
= generateDiagramSchema(a
);
480 return makeDecorateSchema(r
, 10, s
.str()); }
481 else if (isBoxHGroup(t
,l
,a
)) { stringstream s
; s
<< "hgroup(" << extractName(l
) << ")";
482 schema
* r
= generateDiagramSchema(a
);
483 return makeDecorateSchema(r
, 10, s
.str()); }
484 else if (isBoxTGroup(t
,l
,a
)) { stringstream s
; s
<< "tgroup(" << extractName(l
) << ")";
485 schema
* r
= generateDiagramSchema(a
);
486 return makeDecorateSchema(r
, 10, s
.str()); }
488 else if (isBoxSeq(t
, a
, b
)) { return makeSeqSchema(generateDiagramSchema(a
), generateDiagramSchema(b
)); }
489 else if (isBoxPar(t
, a
, b
)) { return makeParSchema(generateDiagramSchema(a
), generateDiagramSchema(b
)); }
490 else if (isBoxSplit(t
, a
, b
)) { return makeSplitSchema(generateDiagramSchema(a
), generateDiagramSchema(b
)); }
491 else if (isBoxMerge(t
, a
, b
)) { return makeMergeSchema(generateDiagramSchema(a
), generateDiagramSchema(b
)); }
492 else if (isBoxRec(t
, a
, b
)) { return makeRecSchema(generateDiagramSchema(a
), generateDiagramSchema(b
)); }
494 else if (isBoxSlot(t
, &i
)) { return generateOutputSlotSchema(t
); }
495 else if (isBoxSymbolic(t
,a
,b
)) {
497 if (getDefNameProperty(t
, id
)) {
498 return generateAbstractionSchema(generateInputSlotSchema(a
), b
);
500 return makeDecorateSchema(generateAbstractionSchema(generateInputSlotSchema(a
), b
), 10, "Abstraction");
506 fprintf(stderr
, "Internal Error, box expression not recognized : "); print(t
, stderr
); fprintf(stderr
, "\n");
513 * Convert User interface element into a textual representation
515 static void UserInterfaceDescription(Tree box
, string
& d
)
517 Tree t1
, label
, cur
, min
, max
, step
;
520 if (isBoxButton(box
, label
)) fout
<< "button(" << extractName(label
) << ')';
521 else if (isBoxCheckbox(box
, label
)) fout
<< "checkbox(" << extractName(label
) << ')';
522 else if (isBoxVSlider(box
, label
, cur
, min
, max
, step
)) {
524 << extractName(label
) << ", "
525 << boxpp(cur
) << ", "
526 << boxpp(min
) << ", "
527 << boxpp(max
) << ", "
528 << boxpp(step
)<< ')';
530 else if (isBoxHSlider(box
, label
, cur
, min
, max
, step
)) {
532 << extractName(label
) << ", "
533 << boxpp(cur
) << ", "
534 << boxpp(min
) << ", "
535 << boxpp(max
) << ", "
536 << boxpp(step
)<< ')';
538 else if (isBoxVGroup(box
, label
, t1
)) {
539 fout
<< "vgroup(" << extractName(label
) << ", " << boxpp(t1
, 0) << ')';
541 else if (isBoxHGroup(box
, label
, t1
)) {
542 fout
<< "hgroup(" << extractName(label
) << ", " << boxpp(t1
, 0) << ')';
544 else if (isBoxTGroup(box
, label
, t1
)) {
545 fout
<< "tgroup(" << extractName(label
) << ", " << boxpp(t1
, 0) << ')';
547 else if (isBoxHBargraph(box
, label
, min
, max
)) {
549 << extractName(label
) << ", "
550 << boxpp(min
) << ", "
551 << boxpp(max
) << ')';
553 else if (isBoxVBargraph(box
, label
, min
, max
)) {
555 << extractName(label
) << ", "
556 << boxpp(min
) << ", "
557 << boxpp(max
) << ')';
559 else if (isBoxNumEntry(box
, label
, cur
, min
, max
, step
)) {
561 << extractName(label
) << ", "
562 << boxpp(cur
) << ", "
563 << boxpp(min
) << ", "
564 << boxpp(max
) << ", "
565 << boxpp(step
)<< ')';
568 cerr
<< "INTERNAL ERROR : unknow user interface element " << endl
;
576 * Generate a 0->1 block schema for a user interface element
578 static schema
* generateUserInterfaceSchema(Tree t
)
580 string s
; UserInterfaceDescription(t
,s
);
581 return makeBlockSchema(0, 1, s
, uicolor
, "");
586 * Generate a 1->1 block schema for a user interface bargraph
588 static schema
* generateBargraphSchema(Tree t
)
590 string s
; UserInterfaceDescription(t
,s
);
591 return makeBlockSchema(1, 1, s
, uicolor
, "");
597 * Generate a 1->0 block schema for an input slot
599 static schema
* generateInputSlotSchema(Tree a
)
601 Tree id
; assert(getDefNameProperty(a
, id
));
602 stringstream s
; s
<< tree2str(id
);
603 return makeBlockSchema(1, 0, s
.str(), slotcolor
, "");
609 * Generate a 0->1 block schema for an output slot
611 static schema
* generateOutputSlotSchema(Tree a
)
613 Tree id
; assert(getDefNameProperty(a
, id
));
614 stringstream s
; s
<< tree2str(id
);
615 return makeBlockSchema(0, 1, s
.str(), slotcolor
, "");
621 * Generate an abstraction schema by placing in sequence
622 * the input slots and the body
624 static schema
* generateAbstractionSchema(schema
* x
, Tree t
)
628 while (isBoxSymbolic(t
,a
,b
)) {
629 x
= makeParSchema(x
, generateInputSlotSchema(a
));
632 return makeSeqSchema(x
, generateDiagramSchema(t
));