#!/bin/sh
# next line is a comment in tcl \
exec wish8.3 "$0" ${1+"$@"}

# a Dynamic Graph Editor
#
# John Ellson <ellson@lucent.com>

# package require Tk
package require Tkspline
package require Tcldgr
package require Tcldgl

set debug 0

# inital defult printer command
set printercommand "| lpr"

# define shape for nodes
if {![info exists shape]} {set shape oval}
if {![info exists boundary]} {set boundary [list -36 -18 36 18]}
if {![info exists font]} {set font [list helvetica 12]}

# initialize some globals so that they exist
set id {}
set tail {}
set x 0
set y 0
set oldx 0
set oldy 0
set file No_Name

# initialize zoom increment values
set z_in_fact 1.11
set z_out_fact [expr {1.0 / $z_in_fact}]
set z_depth 1.0
set z_font $font

#-------------------------------------------------------
# bitmaps

image create bitmap pencil -background [. cget -background] -data "
#define pencil_width 24
#define pencil_height 24
static char pencil_bits[] = {
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x00,0x00,0xe0,0x01,0x00,
 0x30,0x03,0x00,0x98,0x07,0x00,0xf0,0x0c,0x00,0x60,0x18,0x00,0xc0,0x30,0x00,
 0x80,0x61,0x00,0x00,0xc3,0x00,0x00,0x86,0x01,0x00,0x0c,0x03,0x00,0x18,0x06,
 0x00,0x30,0x07,0x00,0xe0,0x0f,0x00,0xc0,0x0f,0x00,0x80,0x1f,0x00,0x00,0x1e,
 0x00,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
"

image create bitmap pointer -background [. cget -background] -data "
#define pointer_width 24
#define pointer_height 24
static char pointer_bits[] = {
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x0e,
 0x00,0x80,0x07,0x00,0xe0,0x07,0x00,0xf8,0x03,0x00,0xfe,0x03,0x80,0xff,0x01,
 0xe0,0xff,0x01,0x00,0xfc,0x00,0x00,0xfe,0x00,0x00,0x7f,0x00,0x80,0x77,0x00,
 0xc0,0x33,0x00,0xe0,0x31,0x00,0xf0,0x10,0x00,0x78,0x10,0x00,0x3c,0x00,0x00,
 0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
"

image create bitmap magnify -background [. cget -background] -data "
#define magnify_width 24
#define magnify_height 24
static unsigned char magnigy_bits[] = {
 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x07,0x00,0x60,0x18,0x00,0x90,0x27,0x00,
 0x48,0x48,0x00,0x28,0x50,0x00,0x14,0xa0,0x00,0x14,0xa0,0x00,0x14,0xa0,0x00,
 0x14,0xa0,0x00,0x14,0xa0,0x00,0x28,0x50,0x00,0x48,0x48,0x00,0x90,0xe7,0x00,
 0x60,0xf8,0x01,0x80,0xe7,0x03,0x00,0xc0,0x07,0x00,0x80,0x0f,0x00,0x00,0x1f,
 0x00,0x00,0x1e,0x00,0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x00};
"

#-------------------------------------------------------

# repetitive action for zooming
proc zoom {c fact} {
    global repeat z_depth z_font font
    $c scale all 0 0 $fact $fact
    set z_depth [expr {$z_depth * $fact}]
    set z_font  [lreplace $font 1 1 [expr int([lindex $font 1] * $z_depth)]]
    $c itemconfigure text -font $z_font
    $c configure -scrollregion [$c bbox all]
    set repeat [after 100 "zoom $c $fact"]
}

proc zoom-reset {c} {
    global repeat z_depth z_font font
    set z_depth [expr 1.0/$z_depth]
    $c scale all 0 0 $z_depth $z_depth
    set z_depth 1.0
    set z_font $font
    $c itemconfigure text -font $z_font
    $c configure -scrollregion [$c bbox all]
}

proc balloon_help {w msg} {
    bind $w <Enter> "after 1000 \"balloon_aux %W [list $msg]\""
    bind $w <Leave> "after cancel \"balloon_aux %W [list $msg]\"
        after 100 {catch {destroy .balloon_help}}"
}

proc balloon_aux {w msg} {
    set t .balloon_help
    catch {destroy $t}
    toplevel $t
    wm overrideredirect $t 1
    pack [label $t.l -text $msg -relief groove -bd 1 -bg gold] -fill both
    set x [expr [winfo rootx $w]+6+[winfo width $w]/2]
    set y [expr [winfo rooty $w]+6+[winfo height $w]/2]
    wm geometry $t +$x\+$y
    bind $t <Enter> {after cancel {catch {destroy .balloon_help}}}
    bind $t <Leave> "catch {destroy .balloon_help}"
}

# find a graph object under the mouse of one of the types given in $args
proc find_obj {c x y args} {
    foreach item [concat current [$c find overlapping $x $y $x $y]] {
        set obj [lindex [$c gettags $item] 0]
        foreach type $args {
            # matching e.g. "N" with "dgN0"
            if {[string first dg$type $obj] == 0} {return $obj}
        }
    }
    return {}
}

# hilight an object in a particular color and tag it into
# the set of selected objects
proc set_selected {c obj color} {
    foreach part [$c find withtag $obj] {
        switch [$c type $part] {
            text {}
            line - polygon - oval {
                $c itemconfigure $part -fill $color
                $c addtag selected withtag $part
            }
        }
    }
}

proc clear_selected {c} {
    foreach part [$c find withtag selected] {
        switch [$c type $part] {
            text {}
            line {
                $c itemconfigure $part -fill black
            }
            polygon - oval {
                $c itemconfigure $part -fill white
            }
        }
    }
    $c dtag selected
}

# start a node or path selection
#    if started over a node then start path
proc select_start {c x y} {
    global oldx oldy shape boundary id tail z_depth selectedtail
    set x [$c canvasx $x]
    set y [$c canvasy $y]
    set obj [find_obj $c $x $y N]
    if {[string length $obj]} {

        # if selection start is not over an existing end node
        if {[lsearch [$c gettags $obj] selected] == -1
        || [string compare [$c itemcget $obj -fill] red]} {
            clear_selected $c
            set_selected $c $obj green
        } {

            # else extended selection
            set_selected $c $obj gold
        }
           set id [$c create line $x $y $x $y -fill gold -arrow last]
    } {

        # selection start is not over a node
        clear_selected $c
    }
    set oldx $x
    set oldy $y
    set tail $obj
}

# select path endpoints
proc select_motion {c x y} {
    global oldx oldy id tail
    set x [$c canvasx $x]
    set y [$c canvasy $y]
    if {$tail != {}} {
        $c coords $id $oldx $oldy $x $y
    }
}

# complete node or path selection
proc select_end {c g wx wy} {
    global id tail x y
    # x,y left in global for insert_node
    set x [$c canvasx $wx]
    set y [$c canvasy $wy]
    $c delete $id
    if {[string length $tail]} {
        set obj [find_obj $c $x $y N]
        if {[string equal $tail $obj]} {
            set obj {}
        }
        set i 0
        # if obj is {} then we get spanning tree
        # else we get shortest path
        foreach {n e m} [shortest_path $tail $obj] {
            set_selected $c $m gold
            set_selected $c $e gold
            incr i
        }
        # only if non-zero shortest path
        if {$i && [string length $obj]} {
            set_selected $c $obj red
        }
    }
}

# Breadth first search for the shortest path or, if $head is omitted,
# then for the spanning tree.
#
# In both cases the return value is a flat list of "node edge node" triplets
# one for each edge in the result.  The edge triplets are randomly ordered.
#
# This routine is pure Tcldg graph ops and is a good candidate
# for recoding in C
#
proc shortest_path {tail {head {}}} {
    set visited($tail) [list {} {}]
    set res {}
    for {set this $tail} {[llength $this]} {set this $next} {
        set next {}
        foreach n $this {
            foreach e [$n listoutedges] {
                set m [$e headof]
                if {! [info exists visited($m)]} {
                    set visited($m) [list $n $e]
                    lappend next $m
                }
                if {[string equal $m $head]} {
                    # return shortest path
                    for {set m $head} {[string compare $m $tail]} {set m $n} {
                        foreach {n e} $visited($m) {break}
                        lappend res $n $e $m
                    }
                    return $res
                }
            }
        }
    }
    if {[string equal $head {}]} {
        # return spanning tree
        foreach m [array names visited] {
            foreach {n e} $visited($m) {break}
            if {[string length $n]} {
                lappend res $n $e $m
            }
        }
        return $res
    }
    return {}
}

# start a node or edge insertion,
#    if started over a node then insert edge
proc edit_insert_start {c x y} {
    global oldx oldy shape boundary id tail z_depth
    set x [$c canvasx $x]
    set y [$c canvasy $y]
    set obj [find_obj $c $x $y N]
    if {[string length $obj]} {
        set id [$c create line $x $y $x $y -fill red -arrow last]
    } {
        set id [$c create $shape $boundary -fill red]
        $c scale $id 0 0 $z_depth $z_depth
        $c move $id $x $y
    }
    set oldx $x
    set oldy $y
    set tail $obj
}

# move node before completing insertion, or extend edge
proc edit_insert_motion {c x y} {
    global oldx oldy id tail
    set x [$c canvasx $x]
    set y [$c canvasy $y]
    if {$tail != {}} {
        $c coords $id $oldx $oldy $x $y
    } {
        $c move $id [expr $x-$oldx] [expr $y-$oldy]
        set oldx $x
        set oldy $y
    }
}

# complete node or edge insertion
proc edit_insert_end {c wx wy g} {
    global id tail x y
    # x,y left in global for insert_node
    set x [$c canvasx $wx]
    set y [$c canvasy $wy]
    $c delete $id
    if {$tail != {}} {
        set obj [find_obj $c $x $y N]
        if {[string length $obj]} {
            $tail addedge $obj
        }
    } {
        $g addnode
    }
}

# start a node movement
proc edit_move_start {c wx wy v} {
    global id 
    set x [$c canvasx $wx]
    set y [$c canvasy $wy]
    set obj [find_obj $c $x $y N]
    if {[string length $obj]} {
        set id $obj
    } {
        $c scan mark $wx $wy
        set id {}
    }
}

# move node 
proc edit_move_motion {c x y v} {
    global id 
    if {[string length $id]} {
        set x [$c canvasx $x]
        set y [$c canvasy $y]
        $v modify_node $id -at $x $y
        $c configure -scrollregion [$c bbox all]
    } {
        # NB. use gain=1 feature from tk8.3
        $c scan dragto $x $y 1
    }
}

# complete node movement
proc edit_move_end {c x y v} {
    global id 
    if {[string length $id]} {
        set x [$c canvasx $x]
        set y [$c canvasy $y]
        $v modify_node $id -at $x $y
        $c configure -scrollregion [$c bbox all]
    } {
        # NB. use gain=1 feature from tk8.3
        $c scan dragto $x $y 1
    }
}

# delete a node or edge
proc edit_delete {c x y} {
       set x [$c canvasx $x]
    set y [$c canvasy $y]
    set obj [find_obj $c $x $y N E]
    if {[string length $obj]} {$obj delete}
    $c configure -scrollregion [$c bbox all]
}

proc newnode {c n args} {
    global z_depth z_font shape boundary
    catch {$c delete $n}
    # The gray25 bitmap is builtin to wish
    $c create $shape $boundary -tags $n -fill white -activestipple gray25
    if {[catch {$n set label} label]
    || [string equal $label {\N}]} {
        set label [$n showname]
    }
    $c create text 0 0 -font $z_font -tags [list $n text] \
        -text $label -state disabled
    eval $c move $n $args
       $c scale $n 0 0 $z_depth $z_depth
}

proc newedge {c e args} {
    global z_depth
    catch {$c delete $e}
    # NB. use spline smoothing from dash patches and Tkspline
    eval $c create line $args -tags $e -activestipple gray25 \
        -smooth spline -arrow last
       $c scale $e 0 0 $z_depth $z_depth
}

proc setmode {c g mode} {
    switch $mode {
        select {
            $c configure -cursor {arrow black}

            # set canvas bindings
            # - Button 1.  node and edge selection
            bind $c <1> "select_start $c %x %y"
            bind $c <Shift-1> {}
            bind $c <B1-Motion> "select_motion $c %x %y"
            bind $c <B1-ButtonRelease> "select_end $c $g %x %y"
        
            # - Button 2.  panning
            bind $c <2> "$c scan mark %x %y"
            bind $c <B2-Motion> "$c scan dragto %x %y 1"
            bind $c <B2-ButtonRelease> {}
        
            # - Button 3.  
            bind $c <3> {}
            bind $c <B3-Motion> {}
            bind $c <B3-ButtonRelease> {}
        }
        edit {
            $c configure -cursor {pencil black}
            clear_selected $c

            # set canvas bindings
            # - Button 1.  node and edge creation
            bind $c <1> "edit_insert_start $c %x %y"
            bind $c <Shift-1> {}
            bind $c <B1-Motion> "edit_insert_motion $c %x %y"
            bind $c <B1-ButtonRelease> "edit_insert_end $c %x %y $g"
        
            # - Button 2.  canvas and node movement
            bind $c <2> "edit_move_start $c %x %y \$v"
            bind $c <B2-Motion> "edit_move_motion $c %x %y \$v"
            bind $c <B2-ButtonRelease> "edit_move_end $c %x %y \$v"
        
            # - Button 3.  node and edge deletion
            bind $c <3> "edit_delete $c %x %y"
            bind $c <B3-Motion> {}
            bind $c <B3-ButtonRelease> {}
        }
        zoom {
            $c configure -cursor {target black}
            clear_selected $c

            # set canvas bindings
            # - Button 1.  zoom in
            bind $c <1> "zoom $c \$z_in_fact"
            bind $c <Shift-1> {}
            bind $c <B1-Motion> {}
            bind $c <B1-ButtonRelease> "catch {after cancel \$repeat}"
        
            # - Button 2.  reset zoom
            bind $c <2> "zoom-reset $c"
            bind $c <B2-Motion> {}
            bind $c <B2-ButtonRelease> {}
        
            # - Button 3.  zoom out
            bind $c <3> "zoom $c \$z_out_fact"
            bind $c <B3-Motion> {}
            bind $c <B3-ButtonRelease> "catch {after cancel \$repeat}"
        }
    }
}

proc insert_node {v n} {
    global shape boundary x y z_depth

    # if there is a pos value in the graph node, then use that, else
    # use the current position of mouse as the placement hint
    # NB. canvas coordinates have inverted Y value, so y in pos needs inverting

    if {! [catch {split [$n set pos] ,} pos] && [llength $pos] == 2} {
        foreach {x y} $pos {break}
        set y -$y
    }
    $v insert_node $n -shape $shape -boundary $boundary \
        -at [expr $x / $z_depth] [expr $y / $z_depth]
}

proc insert_edge {v e t h} {
    global z_depth

    # if there is a pos value in the graph node, then use that, else
    # use the current position of mouse as the placement hint
    # NB. canvas coordinates have inverted Y value, so y in pos needs inverting

    if {[catch {split [$e set pos]} pos] || [llength $pos] < 4} {
        $v insert_edge $e $t $h -shape spline
    } {
        set end 0
        set start 0
        foreach point $pos {
            set point [split $point ,]
            if {[llength $point] == 3} {
                foreach {endc x y} $point {break}
                if {[string compare $endc e]} {
                    set start 1
                    set startxy [list [expr $x / $z_depth] [expr -$y / $z_depth]]
                } {
                    set end 1
                    set endxy [list [expr $x / $z_depth] [expr -$y / $z_depth]]
                }
                continue
            } {
                foreach {x y} $point {break}
            }
            lappend coords [expr $x / $z_depth] [expr -$y / $z_depth]
        }
        if {$end} {
            set l [llength $coords]
            foreach {x y} $endxy {break}
            set coords [lreplace $coords [expr $l -2] end $x $y]
        }
        if {$start} {
            foreach {x y} $startxy {break}
            set coords [lreplace $coords 0 1 $x $y]
        }
        $v insert_edge $e $t $h -shape spline -coordinates $coords
    }
}

# not working yet
proc newengine {f g engine} {
    global v debug
    if {[string equal $f .]} {set f {}}
    if {[info exists v]} {
	$v delete
    }
    set v [dglayout -engine $engine -yseparation 36]
    if {$debug} {
        foreach b [$v bind] {$v bind $b "+puts \[list %l $b %n %e %A %a]"}
    }

    # connect layout events to the canvas
    $v bind insert_node "+newnode $f.canvas %n %P"
    $v bind insert_edge "+newedge $f.canvas %e %P"
    $v bind modify_node "+newnode $f.canvas %n %P"
    $v bind modify_edge "+newedge $f.canvas %e %P"
    $v bind delete_node "+$f.canvas delete %n"
    $v bind delete_edge "+$f.canvas delete %e"

    $v bind batch "+
        if {! %a} {
            $f.canvas configure -scrollregion \[$f.canvas bbox all\]
            update
        }
    "

    # redraw graph in new layout
    foreach n [$g listnodes] {
    	insert_node $v $n
    }
    foreach e [$g listedges] {
	insert_edge $v $e [$e tail] [$e head]
    }
}

# create a new layout of a specific type on a given graph
proc newlayout {f g} {
    if {[string equal $f .]} {set f {}}
    global debug boundary z_in_fact z_out_fact mode$f
    set mode$f edit

    radiobutton $f.select -indicator off -variable mode$f -image pointer \
        -value select -command "setmode $f.canvas $g \$mode$f"
    radiobutton $f.edit -indicator off -variable mode$f -image pencil \
        -value edit -command "setmode $f.canvas $g \$mode$f"
    radiobutton $f.zoom -indicator off -variable mode$f -image magnify \
        -value zoom -command "setmode $f.canvas $g \$mode$f"
    canvas $f.canvas -background lightblue -highlightthickness 1 \
        -xscrollcommand "$f.hscroll set" -yscrollcommand "$f.vscroll set"
    scrollbar $f.vscroll -command "$f.canvas yview" -width 10
    scrollbar $f.hscroll -orient horiz -command "$f.canvas xview" -width 10
    
    # arrange widgets
    grid $f.select $f.canvas $f.vscroll -sticky nwe
    grid $f.edit -sticky nwe
    grid $f.zoom -sticky nwe
    grid $f.canvas $f.vscroll -rowspan 4 -sticky nsew
    grid x $f.hscroll x -sticky nsew
    grid columnconfigure . 1 -weight 1
    grid rowconfigure . 3 -weight 1

    # set initial mode
    setmode $f.canvas $g [set mode$f]

    balloon_help $f.edit "edit graph"
    balloon_help $f.zoom "zoom +/0/-"
    balloon_help $f.select "select objects"

    # connect graph events to the layout.
    $g bind insert_node "+insert_node \$v %n"
    $g bind insert_edge "+insert_edge \$v %e %t %h"

# FIXME monitor graph for changes in label attribute
#    $g bind modify_node "+ "
#    $g bind modify_edge "+ "

    $g bind delete_node "+\$v delete_node %n"
    $g bind delete_edge "+\$v delete_edge %e"

    $g bind batch "+if {\$batch} {\$v batch %a}"
}

proc newgraph {g} {
    $g batch 1
    foreach s [$g listsubgraphs] {$s delete}
    foreach n [$g listnodes] {$n delete}
    $g batch 0
}

proc loadgraph {g} {
    global x y file
    set x 0
    set y 0
    set file [tk_getOpenFile -defaultextension .dot \
        -filetypes [list [list dot .dot]]]
    if {[string length $file]} {
        reallyloadgraph $g $file
    }
}

proc reallyloadgraph {g file} {
    if {[catch {open $file r} f]} {
        puts stderr "failure to open file \"$file\" for read"
    } {
        $g concatfile $f
        close $f
    }
}

proc savegraph {g} {
    global file
    set file [tk_getSaveFile -defaultextension .dot -initialfile $file \
        -filetypes [list [list dot .dot]]]
    if {[string length $file]} {
        if {[catch {open $file w} f]} {
            puts stderr "failure to open file \"$file\" for write"
        } {
            $g write $f
            close $f
        }
    }
}

proc printgraph {c} {
    set command [printdialog]
    set f [open $command w]
    puts $f [$c postscript]
    close $f
}

proc printdialog {} {
    global printercommand

    set w .printdialog
    catch {destroy $w}
    toplevel $w -class Dialog
    wm title $w "Printer Dialog"
    wm iconname $w Dialog
    wm protocol $w WM_DELETE_WINDOW {}
    wm transient $w .

    label $w.label -text "Printer command"
    entry $w.entry 
    $w.entry insert 0 $printercommand

    grid $w.label $w.entry -sticky nsew
    grid columnconfigure $w 2 -weight 1

    bind $w.entry <Return> "set printercommand \[$w.entry get]"

    # Withdraw the window, then update all the geometry information
    # so we know how big it wants to be, then center the window in the
    # display and de-iconify it.

    wm withdraw $w
    update idletasks
    set x [expr {[winfo screenwidth $w]/2 - [winfo reqwidth $w]/2 \
            - [winfo vrootx [winfo parent $w]]}]
    set y [expr {[winfo screenheight $w]/2 - [winfo reqheight $w]/2 \
            - [winfo vrooty [winfo parent $w]]}]
    wm geom $w +$x+$y
    wm deiconify $w

    # Set a grab and claim the focus too.

    set oldFocus [focus]
    set oldGrab [grab current $w]
    if {[string length $oldGrab]} {
        set grabStatus [grab status $oldGrab]
    }
    grab $w
    focus $w.entry

    # Wait for the user to respond, then restore the focus and
    # return the index of the selected button.  Restore the focus
    # before deleting the window, since otherwise the window manager
    # may take the focus away so we can't redirect it.  Finally,
    # restore any grab that was in effect.

    tkwait variable printercommand
    catch {focus $oldFocus}
    destroy $w
    if {[string length $oldGrab]} {
        if {[string equal $grabStatus global]} {
            grab -global $oldGrab
        } else {
            grab $oldGrab
        }
    }
    return $printercommand
}

# create a graph
set g [dgnew digraph]
if {$debug} {
    foreach b [$g bind] {$g bind $b "+puts \[list %g $b %n %e %A %a]"}
}

menu .menu -tearoff 0

.menu add cascade -menu .menu.file -label File -underline 0
menu .menu.file -tearoff 0
.menu.file add command -label New -command "newgraph $g"
.menu.file add command -label Open... -command "loadgraph $g"
.menu.file add command -label Save... -command "savegraph $g"

# FIXME - shouldn't need to know .canvas here
.menu.file add command -label Print... -command "printgraph .canvas"
.menu.file add separator
.menu.file add command -label Quit -command exit

.menu add cascade -menu .menu.options -label Options -underline 0
menu .menu.options -tearoff 0
.menu.options add cascade -menu .menu.options.batch -label Batching -underline 0 
.menu.options add cascade -menu .menu.options.engine -label Engine -underline 0 

menu .menu.options.batch -tearoff 0
.menu.options.batch add radio -label Off -variable batch -value 0
.menu.options.batch add radio -label On -variable batch -value 1
.menu.options.batch invoke 1

menu .menu.options.engine -tearoff 0
foreach eng {GeoGraph OrthoGrid FDPGraph DynaDag} {
    set eng [string tolower $eng]
    .menu.options.engine add radio \
	-label $eng -variable engine -value $eng -command "newengine . $g \$engine"
}

.menu add cascade -menu .menu.help -label Help -underline 0
menu .menu.help -tearoff 0
.menu.help add command -label "About Dge" -command {
    after idle {.help.msg configure -wraplength 4i -font {Times 14}}
    tk_dialog .help "dge :- about" {Dynamic Graph Editor

John Ellson <ellson@lucent.com>
Stephen North <north@research.att.com>} {} 0 OK
}
.menu.help add command -label "Select Mode" -command {
    after idle {.help.msg configure -wraplength 4i -font {Times 14}}
    tk_dialog .help "dge :- select mode" {
Double-click Button-1 over a node to show the spanning tree from that node.

Click-drag-release Button-1 from one node to another shows a shortest path between those nodes.
} {} 0 OK
}
.menu.help add command -label "Edit Mode" -command {
    after idle {.help.msg configure -wraplength 4i -font {Times 14}}
    tk_dialog .help "dge :- edit mode" {Edit Mode

Button-1 inserts nodes and edges
Button-2 moves nodes
Button-3 deletes nodes
} {} 0 OK
}
.menu.help add command -label "Zoom Mode" -command {
    after idle {.help.msg configure -wraplength 4i -font {Times 14}}
    tk_dialog .help "dge :- zoom mode" {Zoom Mode

Button-1 zooms in (larger)
Button-2 reset zoom
Button-3 zooms out (smaller)
} {} 0 OK
}
. configure -menu .menu

# create a layout
newlayout . $g

# initialize menu and create a new layout from newengine invocation
.menu.options.engine invoke dynadag

# load file from command line if any
if {$argc} {reallyloadgraph $g [lindex $argv 0]}
