#!/usr/bin/env wish-f # # usage: tkdraw [filename] # # a simple drawing tool implemented as a wish script, # repackaging tk canvas capabilities (some tk3.6 assumptions) # # sources $HOME/.tkdrawrc if it exists # # version of 960603, throopw@sheol.org (Wayne Throop) # 960903 move to new http site, fixes to group ops, better save options # 960215 misc bug-fixes, added move-again operation, group-dup operation # 950913 added mini-tutorial, more error recovery, ergonomic tweaks # 950912 added help, better error recovery, placement markers # 950909 initial publication # # design remarks # # The general operation is managed via two widgets, a canvas and a text # area, in separate top level windows. At any moment, the text window # contains tcl commands to create an object on the canvas, or to # manipulate the options of the whole drawing itself. Mixed text and # gestural editing on this text, followed by clicking on an "apply" # button, allows for composition of images from most canvas objects, and # access to all their settable options. # # On the canvas, there are three gestural idioms. First, leftclick # inserts the mouse x,y position into the text pane, accumulating # coordinate pairs for the (presumed) canvas object under construction. # Second, centerclick finds the nearest object on the canvas, and sets # the text pane to its description, ready for editing. Third, dragging # an area on the canvas inserts the starting and ending x,y mouse # positions into the text pane, and follows the motion with a rubberband # box and diagonal line, for clarity. # # The "menu bar" is a mixed menu and button bar. The most common # editing actions (moving an object to the end of the draw list [top], # duplicating an object [dup], moving an object [mov], deleting an # object [del] and applying the current window [apply] are all buttons. # The rarer file and drawing maniuplations are combined into the [file] # menu, other edit operations into the [ed] menu, and the numerous # creation operations are combined into the [new] menu. Basically, this # is a tradeoff between preempted screen real estate and convenience. # # The [mov] operation is perhaps the most obscure. It assumes that two # new x,y coordinates (for example, by a drag on the canvas) have been # entered to the end of the coordinates in the text window. It then # adds the offset implied by this drag into the object's position, and # applies the text. # # General-purpose grouping is not implemented, but groups of items in # the drawlist can be selected by rectangular canvas area, and # duplicated, deleted, or moved together in a single operation. # # General operation is then moderately ergonomic; to create a new item, # pull it down from the "new" menu, then click or drag out its position # on the canvas, and finally apply it. You can enter options such as # line widths, colors, and fonts directly, or (by editing it after it is # created) the system will fill in all the possibilities to prompt you. # Manipulating objects already created is also simply; simply point at # them and click with the middle button, make any changes needed (by # [top], [dup], or [mov], or by filling in or changing the displayed # options in the text screen), and apply the changes. # # A tool with greater "screen flash" might have custom forms for each # canvas object instead of a simple, flat text description. But the # flat text description allows for a *lot* of flexibility and simplicity # that flashier alternatives do not, and also allows for simple direct # type-in of configuration, printing, or other miscenaneous commands as # direct tcl commands. Also, saving intermediate information in a # smooth relativly modeless manner (as in the [mov] operation) becomes # straightforward. And further, saved configurations or objects from # anywhere on the X screen can be easily pasted in as a unit, meaning # that canvas objects can be snipped out, saved onto the clipboard or a # simple text editor, moved from drawing to drawing, and so on, all "for # free" because of the active text paradigm. Maybe you had to be there, # but *I* like it. # data for each item stored in global array # $i($n.type) # $i($n.coords) # $i($n.itemconfig} # data for the drawing as a whole is stored in the same array # $i(filename), $i(config), $i(next), $i(comments) # squeeze default size of tk4.0 buttons a bit... # option add *Menubutton*padX 1 # option add *Menubutton*padY 1 # option add *Button*padX 1 # option add *Button*padY 1 global i proc c {args} {eval .tkdraw.canvas $args} proc tkdraw-error {s} { catch {toplevel .tkdraw-errors} catch {pack [text .tkdraw-errors.text]} catch {wm iconify .tkdraw-errors wm deiconify .tkdraw-errors} set p [.tkdraw-errors.text index end] .tkdraw-errors.text insert end $s\n .tkdraw-errors.text yview $p } proc tkdraw-help {s} { catch {toplevel .tkdraw-help} catch {pack [text .tkdraw-help.text]} catch {wm iconify .tkdraw-help wm deiconify .tkdraw-help} .tkdraw-help.text delete 1.0 end .tkdraw-help.text insert end $s\n } # utilitiy routines for drawings as a whole # initialization presumes a canvas already packed, but sets everything # else to standard parameters proc new-opts {{f {}}} { global argv i catch {unset i} set i(filename) [ if [string length $f]==0 {set f tkdraw.out} {set f} ] set i(next) 1 set i(config) [set l "" foreach x [c configure] { if [string length [lindex $x 3]] { append l [list [list [lindex $x 0] [lindex $x 4]]]\n }} set l ] set i(comments) {} edit-options draw-canvas } proc draw-canvas {} { global i foreach x [.tkdraw.canvas find all] { .tkdraw.canvas delete $x } if [catch {eval c config [join $i(config)]} s] { tkdraw-error [set s "" append s "error in canvas configuration:\n" append s "set i(config) {$i(config)}" ] } foreach x [lsort [array names i]] { if [regexp {^(i[0-9]*)\.type} $x n n] { if [catch {eval ".tkdraw.canvas create" \ " $i($n.type) $i($n.coords) $i($n.itemconfig) -tags $n"} err ] { tkdraw-error [ set s "" append s "$n: error in processing drawlist:\n" append s "$err\n" append s "set-type $n $i($n.type)\n" append s "set-coords $n $i($n.coords)\n" append s "set i($n.itemconfig) {" append s "$i($n.itemconfig)" append s "}\ndraw-canvas" ] } }} } proc read-drawing {{add 0} {filename {}}} { global i if [string length $filename] {set i(filename) $filename} set fn $i(filename) set next $i(next) if !$add {unset i} set i(filename) $fn foreach item [eval "exec cat $i(filename)"] { if $add&&[regexp {^i0*([1-9][0-9]*)\.([^ ]*) } $item dummy idx key] { set n [format i%6.6d [expr $next+$idx]] set i($n.$key) [lrange $item 1 end] } { set i([lindex $item 0]) [lrange $item 1 end] } } if $add { set i(next) [expr $i(next)+$next] } edit-options draw-canvas } proc write-drawing {} { global i exec cat > $i(filename) << [ foreach item [array names i] { append o [list "$item $i($item)"]\n } set o ] } proc write-ps {} { global i set f $i(filename) regexp {^(.*)\.[^\.]*$} $f dummy f exec cat > $f.ps << [c postscript] } proc write-gif {} { global i set f $i(filename) regexp {^(.*)\.[^\.]*$} $f dummy f exec /usr/bin/X11/xwd -id [winfo id .tkdraw] | xwdtoppm | ppmtogif > $f.gif } proc write-lpr {} { exec lpr << [c postscript] } proc edit-options {} { global i .text delete 1.0 end .text insert 1.0 [join [list \ "set i(filename) [list $i(filename)]" \ "set i(config) {\n" ] \n] foreach x $i(config) { .text insert end [list $x]\n } .text insert end [join [list \ "}" \ "set i(next) [list $i(next)]" \ "set i(comments) [list [if ![catch {set i(comments)} s] {set s}]]" \ "draw-canvas" ] \n]\n } # utility routines for item creation and editing proc set-type {n args} { global i set i($n.type) $args } proc set-coords {n args} { global i set i($n.coords) $args } proc set-itemconfig {n config} { global i set i($n.itemconfig) [join $config] } # apply (that is, execute the tcl text) currently in the text window proc apply {} { global i c delete pointmark if [catch {eval [.text get 1.0 end]} s] { tkdraw-error $s } } # utility routines for manipulating items in the drawing proc new-item {args} { global i set n i[format %6.6d $i(next)] set i(next) [expr $i(next)+1] .text delete 1.0 end .text insert 1.0 [join [list \ "set-type $n $args" \ "set-coords $n" \ "set-itemconfig $n {}" \ "draw-canvas" \ "edit-item-byname $n" ] \n]\n } proc show-item-names {} { global i catch {toplevel .tkdraw_items} catch {destroy [set w .tkdraw_items.list]} pack [frame $w] foreach item [lsort [array names i]] { if [regexp {^(i[0-9]*)\.type$} $item dummy n] { pack [button $w.$n -text $n.$i($n.type) -command \ [list edit-item-byname $n]] -side top } } } proc edit-item-byname {n} { global i .text delete 1.0 end .text insert 1.0 [join [list \ "set-type $n [.tkdraw.canvas type $n]" \ "set-coords $n [.tkdraw.canvas coords $n]" \ "set-itemconfig $n {" ] \n]\n foreach x [.tkdraw.canvas itemconfig $n] { .text insert end [list [list [lindex $x 0] [lindex $x 4]]]\n } .text insert end "}\ndraw-canvas" } proc edit-item {x y} { global i set n [lindex [.tkdraw.canvas gettags \ [.tkdraw.canvas find closest $x $y]] 0] .text delete 1.0 end .text insert 1.0 [join [list \ "set-type $n [.tkdraw.canvas type $n]" \ "set-coords $n [.tkdraw.canvas coords $n]" \ "set-itemconfig $n {" ] \n]\n foreach x [.tkdraw.canvas itemconfig $n] { .text insert end [list [list [lindex $x 0] [lindex $x 4]]]\n } .text insert end "}\ndraw-canvas" } proc delete-item {} { global i if ![regexp {^set-type} [.text get 1.0 end]] { error "edit buffer does not contain a drawlist item" } set n [lindex [.text get 1.0 end] 1] unset i($n.type) unset i($n.coords) unset i($n.itemconfig) draw-canvas } proc top-item {} { global i if ![regexp {^set-type} [.text get 1.0 end]] { error "edit buffer does not contain a drawlist item" } set s [.text get 1.0 end] delete-item set n i[format %6.6d $i(next)] set i(next) [expr $i(next)+1] .text delete 1.0 end .text insert 1.0 [regsub -all [lindex $s 1] $s $n s; set s] apply } proc dup-item {} { global i if ![regexp {^set-type} [.text get 1.0 end]] { error "edit buffer does not contain a drawlist item" } set s [.text get 1.0 end] set n i[format %6.6d $i(next)] set i(next) [expr $i(next)+1] .text delete 1.0 end .text insert 1.0 [regsub -all [lindex $s 1] $s $n s; set s] apply } proc mov-item {} { if ![regexp {^set-type} [.text get 1.0 end]] { error "edit buffer does not contain a drawlist item" } set s [.text get 2.0 2.0lineend] regexp \ {^(.*) ([0-9-][0-9]*) ([0-9-][0-9]*) ([0-9-][0-9]*) ([0-9-][0-9]*)$} \ [lrange $s 2 end] dummy coords x1 y1 x2 y2 set dx [expr $x2-$x1] set dy [expr $y2-$y1] .text delete 2.0 2.0lineend .text insert 2.0 [ set l "[lrange $s 0 1]" while {[regexp {^([0-9-][0-9\.]*) ([0-9-][0-9\.]*) *(.*)$} \ $coords dummy x y coords] } { append l " [expr $x+$dx] [expr $y+$dy]" } set l ] apply } proc grid-item {} { if ![regexp {^set-type} [.text get 1.0 end]] { error "edit buffer does not contain a drawlist item" } set s [.text get 2.0 2.0lineend] set coords [lrange $s 2 end] .text delete 2.0 2.0lineend .text insert 2.0 [ set l "[lrange $s 0 1]" while {[regexp {^([0-9-][0-9\.]*) ([0-9-][0-9\.]*) *(.*)$} \ $coords dummy x y coords] } { append l " [expr 10*int($x/10)] [expr 10*int($y/10)]" } set l ] apply } proc group-pre {t {h {}}} { .text delete 1.0 end .text insert 1.0 "# $h\n$t-group\n" } proc group-pre-match {x1 y1 x2 y2} { global i foreach item [c find enclosed $x1 $y1 $x2 $y2] { if ![catch {set i([set n [lindex [c gettags $item] 0]].type)}] { lappend l $n } } return $l } proc mov-group {x1 y1 x2 y2 mx1 my1 mx2 my2} { foreach n [group-pre-match $x1 $y1 $x2 $y2] { edit-item-byname $n .text insert 2.0lineend " $mx1 $my1 $mx2 $my2" mov-item } } proc dup-group {x1 y1 x2 y2 mx1 my1 mx2 my2} { foreach n [group-pre-match $x1 $y1 $x2 $y2] { edit-item-byname $n dup-item .text insert 2.0lineend " $mx1 $my1 $mx2 $my2" mov-item } } proc del-group {x1 y1 x2 y2} { foreach n [group-pre-match $x1 $y1 $x2 $y2] { edit-item-byname $n delete-item } } proc get-text {} { return [.text get 1.0 end] } proc set-text {t} { .text delete 1.0 end .text insert 1.0 $t } proc scale-item {} { set n [lindex [.text get 2.0 2.0lineend] 1] .text delete 1.0 end .text insert 1.0 [format "%s\n%s\n%s\n" \ {# scale NAME X Y SX SY} \ "c scale $n" \ "set i($n.coords) \[c coords $n\]" ] } proc scale-group {px py scalex scaley x1 y1 x2 y2} { global i draw-canvas foreach n [group-pre-match $x1 $y1 $x2 $y2] { c scale $n $px $py $scalex $scaley set i($n.coords) [c coords $n] } } proc edit-options {} { global i .text delete 1.0 end .text insert 1.0 [join [list \ "set i(filename) [list $i(filename)]" \ "set i(config) {\n" ] \n] foreach x $i(config) { .text insert end [list $x]\n } .text insert end [join [list \ "}" \ "set i(next) [list $i(next)]" \ "set i(comments) [list [if ![catch {set i(comments)} s] {set s}]]" \ "draw-canvas" ] \n]\n } # utility routines to make pointer selection more visible, # crosshairs are placed on marked points proc pointmark {x y} { c create text $x $y -text X -tag pointmark .text insert {2.0 lineend} " $x $y" } # a box and a diagonal line follow dragging motions on the canvas proc rubberband-end {x y} { if [string length [c find withtag rubberband]] { .text insert {2.0 lineend} " $x $y" } } proc rubberband {x y} { regexp {^.* ([0-9][0-9]*) ([0-9][0-9]*)$} \ [.text get 2.0 2.0lineend] dummy x1 y1 c delete rubberband c create rectangle $x1 $y1 $x $y -tags rubberband c create line $x1 $y1 $x $y -tags rubberband } # window layout pack [frame .panel] -side top -fill x pack [text .text -width 40] -side top pack [menubutton .panel.file -text file -menu .panel.file.menu \ -borderwidth 2 -relief raised] -side left menu .panel.file.menu .panel.file.menu add command -label options -command edit-options .panel.file.menu add command -label clear -command {.text delete 1.0 end} .panel.file.menu add command -label new -command new-opts .panel.file.menu add command -label read -command read-drawing .panel.file.menu add command -label include -command {read-drawing 1} .panel.file.menu add command -label write -command write-drawing .panel.file.menu add command -label write-ps -command write-ps .panel.file.menu add command -label write-gif -command write-gif .panel.file.menu add command -label print -command write-lpr .panel.file.menu add command -label quit -command {destroy .} pack [button .panel.tcl -text tcl -command { uplevel #0 [selection get]}] -side left pack [label .panel.l1 -text { }] -side left pack [menubutton .panel.ed -text ed -menu .panel.ed.menu \ -borderwidth 2 -relief raised] -side left menu .panel.ed.menu .panel.ed.menu add command -label drawlist -command show-item-names .panel.ed.menu add command -label delete-item -command delete-item .panel.ed.menu add command -label scale-item -command scale-item .panel.ed.menu add command -label grid -command grid-item .panel.ed.menu add command -label mov-group -command {group-pre mov} .panel.ed.menu add command -label dup-group -command {group-pre dup} .panel.ed.menu add command -label del-group -command {group-pre del} .panel.ed.menu add command -label scale-group -command \ {group-pre scale {scale-group Px Py Sx Sy [bounds]}} pack [button .panel.top -text top -command top-item] -side left pack [button .panel.dup -text dup -command dup-item] -side left pack [button .panel.mov -text mov -command mov-item] -side left pack [label .panel.l2 -text { }] -side left pack [menubutton .panel.new -text new -menu .panel.new.menu \ -borderwidth 2 -relief raised] -side left menu .panel.new.menu foreach x {line rectangle polygon oval arc bitmap text} { .panel.new.menu add command -label $x -command [list new-item $x] } pack [button .panel.apply -text apply -command apply] -side left pack [menubutton .panel.help -text help -menu .panel.help.menu \ -borderwidth 2 -relief raised] -side right menu .panel.help.menu foreach x {tutorial usage file-menu tcl-button ed-menu top-button dup-button mov-button new-menu apply-button canvas usage-hints } { .panel.help.menu add command -label $x -command help-$x } pack [canvas [toplevel .tkdraw].canvas -width 600 -height 600] # event bindings bind .tkdraw.canvas {pointmark %x %y} bind .tkdraw.canvas {edit-item %x %y} bind .tkdraw.canvas {rubberband %x %y} bind .tkdraw.canvas {rubberband-end %x %y} bind Text {%W mark set insert "insert - 1 chars"} bind Text {%W mark set insert "insert + 1 chars"} bind Text {%W mark set insert "insert - 1 lines"} bind Text {%W mark set insert "insert + 1 lines"} bind Text { %W mark set insert @%x,%y %W insert insert [selection get] } # final touch-up initializations new-opts [lindex $argv 0] catch {source $env(HOME)/.tkdrawrc} proc help-tutorial {} { tkdraw-help \ {To draw something, create its tcl description by pulling down the operation from the "new" menu, then choose where it is to be placed on the canvas by clicking on as many points on the canvas as needed by the given object (eg: a line needs two points or more, a rectangle exactly two, text or bitmaps only one, and so on), and finally use the "apply" button. You can then change the item's properties by editing in the text window, for example change the color, or the width, or the font of text, or the file assigned to a bitmap. To edit something already created, either click the middle mouse button on its image on canvas, or pull down the ed.drawlist and chose it from the resulting list of items. Then the item can be moved, duplicated, have its properties edited (and then applied), and so on. To save a drawing, choose the file.options, make sure the file you wish to save to is set (and "applied"), then choose file.write. Similarly for reading files previously saved.} } proc help-usage {} { tkdraw-help \ { tkdraw [filename] The tkdraw script is essentially an interface to the capabilities built in to tk canvas widgets, packaged as a drawing tool. The general method is to have two major windows, one a canvas, and one a text window showing the descripton of the canvas object currently being manipulated as a set of tcl commands. Gestural commands on the canvas to locate objects, shape objects, select objets, or move them, make the appropriate text edits to the tcl commands in the text window. Finally, a row of buttons supply miscelaneous menus and operations, such as interactions with the file system, access to objects by browsing the drawlist instead of the canvas, common edits such as duplication, movement, new object creation, and "apply" of changes. $HOME/.tkdrawrc is sourced at startup if it exists. } } proc help-file-menu {} { tkdraw-help \ {file.options Sets the edit text to the options for the drawing as a whole. The apply button must be used to commit changes made. file.clear Clears the edit text. Useful to do to avoid confusion, or to prepare to type in commands "by hand" to apply, eg, use file.clear, then type "exec lpr -Psome_printer << [c postscript]" and then the apply operation will print the current drawing to the some_printer postscript printer. file.new Clears the canvas, resets the draw list to empty. file.read Reads a file containing a drawing in tcl-list format, replacing the current drawing. file.include Reads a file containing a drawing in tcl-list format, adding it to the current drawing. file.write Writes the drawing to a file in tcl-list format. file.write-ps Writes the drawing to a file in postscript format. It choses the filename from the options settings, and replaces any extension with ".ps". file.write-gif Writes the drawing to a file in GIF format. It choses the filename from the options settings, and replaces any extension with ".gif". Uses xwd and PBM toolkit, and presumes they are on the $PATH. file.print Writes the drawing to "lpr" in postscript format. file.quit Terminates the tkdraw session. Note, there is no checking to ensure file write of any canvas changes. } } proc help-tcl-button {} { tkdraw-help \ {The tcl button executes the current X selection as a tcl script. This is useful for importing commands, customizations, or configuration options from other tools. } } proc help-ed-menu {} { tkdraw-help \ {ed.drawlist Pops up a top-level window containing the current drawlist, so that one can access any of the items for edit without necessarily knowing where it is on the canvas. ed.delete-item Deletes the item currently in the edit buffer from the canvas and the draw list (but leaves it in the text buffer). ed.scale-item Prepares to scale all coordinates of item in the edit buffer by a given factor; pick an origin, x & y scale factors, and apply ed.grid Edits the item currently in the edit buffer, moving all coordinates in the coordinate list to the next lower multiple of 10. Useful for precision alignment of points in an object for regular shape, or for aligning multiple objects. ed.mov-group ed.dup-group ed.del-group sets up a command to perform a "mov", "dup" or "del" operation on a group of items within a region of the screen; use by 1) [mov,dup,del]-group 2) select screen area (then select a move vector if doing a mov or dup) 3) apply } } proc help-top-button {} { tkdraw-help \ {Moves the item in the edit buffer to the end of the draw list. } } proc help-dup-button {} { tkdraw-help \ {Duplcates the item in the edit buffer to the end of the draw list. } } proc help-mov-button {} { tkdraw-help \ {Presumes that the last four entries in the coordinate line are movement endpoints, and edits the prior entries. Thus, to move an object, select it, drag from any point on the object to where you want that point to land, and do the mov operation. } } proc help-new-menu {} { tkdraw-help \ {The new menu allows choice of the standard tk canvas objects to create, namely line, rectangle, polygon, oval, arc, bitmap and text. } } proc help-apply-button {} { tkdraw-help \ {The apply button is used to commit changes to canvas. The top, dup, and mov buttons automatically apply, but other edits must be explicitly applied, or they will not take effect. } } proc help-canvas {} { tkdraw-help \ {There are three gestural idioms supported on the canvas. Each of them updates the text display. placement A single leftclick will add an x,y coordinate pair to the current object's coordinate list. Thus, to place a bitmap or text object on the canvas, create it from the new menu, specifiy its bitmap file or text content, click on the canvas, and execute the apply button. Placement clicks are marked with "X" as an aid in aligning placements before they are applied. Apply removes placement marks. rubberbanding A left drag on the canvas will add two x,y coordinate pairs to the current object's coordinate list, and during the drag, the current bounding box and its diagonal track mouse motions. Thus, to create a rectang, oval, or arc, simply creat it from the new menu, drag out its location on the canvas, and apply. Note that this is also used for moving objects. Simply select the object (see below), drag from where it is to where you want it, and apply. Multiple drags in a row only leave one extra set of coordinates per extra drag. This means that one can drag out multi-point lines, or polygon boundaries, with convenient placement marks for alignment, without inserting redundant points. selection A middleclick on the canvas will select the nearest object for editing. It is also useful if you need prompting for what options are available on particlar objects. For example, you can create a line with a plain style, then select the just-created line for editing, and the options for color, arrow style, edge rounding, spline curves, and so on, will all be presented. } } proc help-usage-hints {} { tkdraw-help \ {re-use Note that since there is an "include" option, different drawings can be combined. Simply selection file.options, choose a filename of an old drawing with some features you want to reuse, then choose file.include. Its drawing list will be added to that of the current drawing. Sometimes, however, it would be easier to snip out only one object or two, rather than including a compelex drawing and then deleting most of it. In that case, other possibilities might be easier. One possibility is to bring up another copy of tkdraw on a file that has an element you wish to snip. Then, select it for editing and select its text as the X selection. Finally, go back to the original tkdraw, select file.clear, past in the text with either ctrl-V or right-click, and hit "top" to insert it into the local drawing's draw list. Another possibility is to read the first drawing, save the text into another X window (say, an editor), read the new drawing, and use clear/paste/top to insert it. Finally, note that flipping to the drawing options, then applying, then chosing read or include, can be tedious. You can simply use file.clear, then type read-drawing 1 foo to read in file "foo". Note that "1" as the first argument includes the file; "0" can be used to overwrite the current drawing. grouping Tkdraw doesn't do general-purpose groups. It's application domain is small overhead transparencies, HTML pages, and so on, so the draw list usually doesn't get complicated enough to make it worthwhile in practice in its user community. But there are three specific operations that can act on groups of items selected by screen area. Also, because movement vectors are simply text strings, you can save the two x,y coordinate pairs from a drag into another window, and make them the current X selection. Then, you can simply choose a set of of objects you wish to move as a group in one by one, paste the coordinates in with a rightclick or ctrl-V, and select move. They will all retain their precise relative placement on the screen, but moved to a new location. Also, by storing a group of objects in a file, they can all be inserted into a drawing as a unit via file.include. macros If some arbitrary tcl commands need to be executed for some portion of a drawing, they could be stored in the "comments" field of the options, then simply selected and executed with the "tcl" button to expand them when the drawing is loaded. Eventually, adding a macro capability to the draw list will be a superior solution, and may also be a way to get grouping cleanly, but this is still tenative. } }