#!/usr/bin/env wish
set svnid {$Id$}
#-----------------------------------------------------------------------------------#
# 
# DesireData
#   Copyright (c) 2004 by Mathieu Bouchard
#   Copyright (c) 2005,2006,2007 by Mathieu Bouchard and Chun Lee
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.

# See file ../COPYING.desire-client.txt for further informations on licensing terms.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# Note that this is not under the same license as the rest of PureData.
# Even the DesireData server-side modifications stay on the same license
# as the rest of PureData.
#
#-----------------------------------------------------------------------------------#

# this command rebuilds the package index: echo pkg_mkIndex . | tclsh

set debug 0 ;# DON'T TOUCH THIS, make yourself a debug.tcl instead!

if {$tcl_version < 8.5} {puts "Please upgrade to Tcl/Tk 8.5... Thank You.\n(your version is $tcl_version)"; exit 84}

if {[catch {winfo children .}]} {set tk 0} {set tk 1}

set argh0 [file normalize [file join [pwd] $argv0]]
set auto_path [concat . \
	[list [file join [file dirname [file dirname $argh0]] lib/pd/bin]] \
	/usr/share/tcltk/tcllib1.10/profiler \
	/usr/lib/tclx8.4 \
	$auto_path]

package require poe
if {$tk} {package require bgerror}

catch {package require Tclx}
#if {[catch {source /home/matju/src/svn-pd/desiredata/src/profile_dd.tcl}]} {error_dump}
if {[file exists debug.tcl]} {source debug.tcl}

proc which {file} {
	global env
	foreach dir [split $::env(PATH) ":"] {
		if {[file exists $dir/$file]} {return $dir/$file}
	}
	return ""
}

#-----------------------------------------------------------------------------------#
# some list processing functions and some math too
# these could become another library like objective.tcl
# if they become substantial enough

# min finds the smallest of two values
# max finds the biggest of two values
# [clip $v $min $max] does [max $min [min $max $v]]
proc min {x y} {expr {$x<$y?$x:$y}}
proc max {x y} {expr {$x>$y?$x:$y}}
proc clip {x min max} {
	if {$x<$min} {return $min}
	if {$x>$max} {return $max}
	return $x
}

# set several variables from elements of a list
# WARNING: for @-variables, use [list @a @b @c] instead of {@a @b @c}
proc mset {vars list} {
	uplevel 1 "foreach {$vars} {$list} {break}"
}

# add or subtract two lists
proc l+   {   al bl} {set r {}; foreach a $al b $bl {lappend r [expr {$a+$b}]}; return $r}
proc l-   {   al bl} {set r {}; foreach a $al b $bl {lappend r [expr {$a-$b}]}; return $r}
# halve a list
proc l/2  {   al   } {set r {}; foreach a $al       {lappend r [expr {$a/2 }]}; return $r}

# like l+ or l- but for any infix supported by expr
proc lzip {op al bl} {
	set r {}
	set e "\$a $op \$b"
	foreach a $al b $bl {lappend r [expr $e]}
	return $r
}

# do an operation between all elements of a list and a second argument
proc lmap {op al b } {
	set r {}
	set e "\$a $op \$b"
	foreach a $al       {lappend r [expr $e]}
	return $r
}

# sum and product of a list, like math's capital Sigma and capital Pi.
proc lsum  {al} {set r 0; foreach a $al {set r [expr {$r+$a}]}; return $r}
proc lprod {al} {set r 1; foreach a $al {set r [expr {$r*$a}]}; return $r}

# all elements from end to beginning
proc lreverse {list} {
	set r {}
	for {set i [expr {[llength $list]-1}]} {$i>=0} {incr i -1} {lappend r [lindex $list $i]}
	return $r
}

# list substraction is like set substraction but order-preserving
# this is the same algorithm as Ruby's - operation on Arrays
proc lwithout {a b} {
	set r {}
	foreach x $b {set c($x) {}}
	foreach x $a {if {![info exists c($x)]} {lappend r $x}}
	return $r
}

proc lintersection {a b} {
	set r {}
	foreach x $b {set c($x) {}}
	foreach x $a {if {[info exists c($x)]} {lappend r $x}}
	return $r
}

# removes duplicates from a list, but it must be already sorted.
proc luniq {a} {
	set last [lindex $a 0]
	set r [list $last]
	set i 0
	foreach x $a {
		if {$i && [string compare $last $x]} {lappend r $x}
		incr i; set last $x
	}
	return $r
}

# one-dimensional intervals (left-closed, right-open); not much in use at the moment, not that they wouldn't deserve to!
# (slightly buggy)
proc inside      {x x0 x1} {return [expr {$x>=$x0 && $x<$x1}]}
proc overlap {y0 y1 x0 x1} {return [expr {[inside $y0 $x0 $x1] || [inside $y1 $x0 $x1]}]}

proc distance {point1 point2} {
	set off [l- $point1 $point2]
	return [expr {sqrt([lsum [lzip * $off $off]])}]
}

proc rect_centre {rect} {
	mset {x1 y1 x2 y2} $rect
	return [list [expr {($x1+$x2)/2}] [expr {($y1+$y2)/2}]]
}

proc lmake {start end} {for {set i $start} {$i<=$end} {incr i} {lappend l $i}; return $l}
#-----------------------------------------------------------------------------------#
set callback_list {}

proc append_callback {mode when def} {
	global callback_list
	dict set callback_list $mode $when $def
}

proc remove_callback {mode} {
	global callback_list
	set callback_list [dict remove $callback_list $mode]
}

proc modes_callback {self def {args}} {
	global callback_list
	set i 0
	dict for {mode callbacks} $callback_list {
		foreach {when call} $callbacks {
			if {$def == $when} {eval $self $call $args; incr i}
		}
	}
	if {!$i} {return 0} else {return 1}
}

#-----------------------------------------------------------------------------------#
# Observer pattern
# there's no class for "observer".
# it's anything that has def $myclass notice {args} {...} in which args indicate
# attributes that have changed, or is an empty list if an unspecified number of
# attributes (maybe all) have changed.

class_new Observable {}
def Observable init {args} {
	eval [concat [list super] $args]
	set @subscribers {}
}
def Observable subscribe {observer} {
	set i [lsearch $@subscribers $observer]
	if {$i<0} {lappend @subscribers $observer}
}
def Observable unsubscribe {observer} {
	set i [lsearch $@subscribers $observer]
	if {$i>=0} {set @subscribers [lreplace $@subscribers $i $i]}
}

if {$have_expand} {
  #def Observable changed              {args} {
  #  puts "Observable changed $self called from [info level [expr [info level]-2]]"
  #  foreach x $@subscribers {$x notice   $self {expand}$args]}
  #}
  def Observable changed              {args} {foreach x $@subscribers {$x notice   $self {expand}$args}}
  def Observable child_changed {origin args} {foreach x $@subscribers {$x notice $origin {expand}$args}}
} else {
  def Observable changed              {args} {foreach x $@subscribers {$x notice   $self {*}$args}}
  def Observable child_changed {origin args} {foreach x $@subscribers {$x notice $origin {*}$args}}
}
def Observable subscribers {} {return $@subscribers}

#-----------------------------------------------------------------------------------#
set poolset(foo) bar
array unset poolset foo

class_new Manager {Thing}

def Manager init {} {
	set @q {}
	$self call
}

def Manager call {} {
	#if {[llength $@q]} {post "client queue %d" [llength $@q]}
	for {set i 0} {$i < [llength $@q]} {incr i} {
		set o [lindex $@q $i]
		unset ::poolset($o)
		if {[info exists _($o:_class)]} {
			if {[catch {$o draw_maybe}]} {puts [error_dump]}
		} else {
			puts "  tries to draw ZOMBIE $o"
		}
		if {$i == [expr {[llength $@q]-1}]} {set @q {}}
	}
	after 25 [list $self call]
}

def Manager notice {origin args} {
	if {[info exists ::poolset($origin)]} {
		# post %s "def Manager notice: double dirty"
		# nothing for now
	} {
		set ::poolset($origin) {-1}
		lappend @q $origin
	}
	#post "Manager notice: queue length is now %d" [llength $@q]
}

set serial 0
proc serial {n obj} {
	if {$n >= $::serial} {error "object creation serial number is in the future"}
	eval [concat $::replyset($n) [list $obj]]
	array unset ::replyset $n
}

proc philtre {atoms} {
	set r {}
	foreach atom $atoms {lappend r [regsub -all {([;,\\ ])} $atom {\\\1}]}
	return [join $r]
}

# you pass the 2nd argument if and only if the message creates an object (or pretends to).
# this happens with #N canvas, and those methods of #X:
#   obj, msg, floatatom, symbolatom, text, connect, text_setto, array.
# this does NOT happen with #X coords/restore/pop.
proc netsend {message {callback ""}} {
	#if {$message == ""} {error "empty message... surely a mistake"}
	if {$::sock == ""} {error "connection to server needed for doing this"}
	if {$callback != ""} {
		set ::replyset($::serial) $callback
		set message [concat [lrange $message 0 0] [list with_reply $::serial] [lrange $message 1 end]]
		incr ::serial
	}
	set text "[philtre $message];"
	if {$::debug} {puts "[VTcyan]<- $text[VTgrey]"}
	puts $::sock $text
}

#-----------------------------------------------------------------------------------#
# This is not a real Hash, just the same interface as a Ruby/Python/Perl Hash... or quite like Tcl arrays themselves
class_new Hash {Thing}

def Hash   init {args} {      super; foreach {k v} $args {$self set $k $v}}
def Hash reinit {args} {$self clear; foreach {k v} $args {$self set $k $v}}
def Hash set {k v} {set ::hash($self:$k) $v}
def Hash exists {k} {info exists ::hash($self:$k)}
def Hash get {k}   {set ::hash($self:$k)}
def Hash size {} {llength [$self keys]}
def Hash unset {k} {unset ::hash($self:$k)}
def Hash list {} {set r {}; foreach k [$self keys] {lappend r $k [$self get $k]}; return $r}
def Hash keys {} {
	set r {}
	set n [string length $self:]
	foreach k [array names ::hash $self:*] {lappend r [string range $k $n end]}
	return $r
}
def Hash values {} {
	set r {}
	foreach k [array names ::hash $self:*] {lappend r $::hash($k)}
	return $r
}
def Hash clear {} {foreach k [$self keys] {$self unset $k}}
def Hash delete {} {$self clear; super}

def Hash search {v} {
	foreach k [$self keys] {if {[$self get $k] == $v} {return $k}}
	return -1 ;# this is not correct as -1 could be a Hash key, though not in its current context of use...
}

if 0 {
	set h [Hash new foo bar 1 2 3 4]
	$h set hello world
	puts keys=[$h keys]
	puts values=[$h values]
	puts list=[$h list]
	$h unset foo
	puts list=[$h list]
	$h clear
	puts list=[$h list]
	foreach i {1 2 3 4} {puts "exists $i : [$h exists $i]"}
}

class_new Selection {Hash}
def Selection set {k v} {super $k $v; $v selected?= 1}
def Selection unset {k} {
	#set v [$self get $k]; puts "$v ::: [$v class]"
	if {[$self exists $k]} {[$self get $k] selected?= 0}
	super $k
}
#-----------------------------------------------------------------------------------#
# abstract class: subclass must def {value value= <<}
class_new Clipboard {Observable Thing}
def Clipboard init   {{value ""}} {super; $self value= $value; set @copy_count 0}

# uses system clipboard
class_new Clipboard1 {Clipboard}
def Clipboard1 value= {value} {clipboard clear; clipboard append $value; $self changed}
def Clipboard1 << {value}     {                 clipboard append $value; $self changed}
def Clipboard1 value {} {clipboard get}

# uses string buffer (not system clipboard)
class_new Clipboard2 {Clipboard}
def Clipboard2 value= {value} {set    @value $value; $self changed}
def Clipboard2 << {value}     {append @value $value; $self changed}
def Clipboard2 value {} {return $@value}

if {$tk} {
  set clipboard [Clipboard1 new]
} else {
  set clipboard [Clipboard2 new]
}

#-----------------------------------------------------------------------------------#
class_new EventHistory {Observable Thing}

def EventHistory init {} {super; set @list {}}
def EventHistory add {e} {lappend @list $e; $self changed add $e}
def EventHistory list {{formatted 1}} {
	if {!$formatted} {return $@list}
	set r {}
	foreach event $@list {
		mset {type W x y mod K k} $event
		lappend r [format "%-13s %9s %4d %4d %4d %4d %s" $type $K $k $x $y $mod $W]
	}
	return $r
}
set ::event_history [EventHistory new]

#-----------------------------------------------------------------------------------#
class_new CommandHistory {Observable Thing}

def CommandHistory init {} {
	super
	set @undo_stack {}
	set @redo_stack {}
}

def CommandHistory can_undo? {} {return [expr {[llength @undo_stack] > 0}]}
def CommandHistory can_redo? {} {return [expr {[llength @redo_stack] > 0}]}
def CommandHistory next_undo_name {} {return stuff}
def CommandHistory next_redo_name {} {return stuff}
def CommandHistory undo_stack {} {return $@undo_stack}
def CommandHistory redo_stack {} {return $@redo_stack}

# overload this if you want to control how many levels
# of undo may be kept.
# keep in mind that undo information is kept hierarchically.
def CommandHistory add {message} {
	lappend @undo_stack [list do $message [lrange [info level -3] 1 end]]
	set @redo_stack {}
	$self changed
}

def CommandHistory can't {} {
	lappend @undo_stack [list can't {} [lrange [info level -3] 1 end]]
	set @redo_stack {}
	$self changed
}

# runs the restore procedure for the last item in the root undo queue.
def CommandHistory undo {} {
	if {![$self can_perform? [lindex $@undo_stack end]]} {error "Can't undo this!"}
	set backup $@undo_stack
	set @undo_stack $@redo_stack
	set @redo_stack {}
	#set err [catch {$self perform [lindex $backup end]}]; if {$err} {set err $::errorInfo}
	$self atomically [list atomic_undo] {
		$self perform [lindex $backup end]
	}
	set @redo_stack $@undo_stack
	set @undo_stack [lrange $backup 0 end-1]
	$self changed
	#if {$err} {post %s $err; error "undo: $err"}
}

def CommandHistory redo {} {
	if {![$self can_perform? [lindex $@undo_stack end]]} {error "Can't redo this!"}
	set backup $@redo_stack
	set @redo_stack {}
	set err [catch {$self perform [lindex $backup end]}]; if {$err} {set err $::errorInfo}
	$self atomically [list atomic_redo] {
		$self perform [lindex $backup end]
	}
	set @redo_stack [lrange $backup 0 end-1]
	$self changed
	#if {$err} {post %s $err; error "redo: $err"}
}

def CommandHistory can_perform? {action} {
	switch -- [lindex $action 0] {
	  do      {return 1}
	  can't   {return 0}
	  default {
	    foreach x [lrange $action 1 end] {
	      if {![$self can_perform? $x]} {return 0}
	    }
	    return 1
	  }
	}
}

def CommandHistory perform {action} {
	switch -- [lindex $action 0] {
	  do      {eval [lindex $action 1]}
	  can't   {error "can't undo this!"}
	  default {foreach x [lreverse [lindex $action 1]] {$self perform $x}}
	}
}

def CommandHistory atomically {what code} {
	set ubackup @undo_stack; set @undo_stack {}
	set rbackup @redo_stack; set @redo_stack {}
	uplevel 2 $code
	set atom $@undo_stack
	set @undo_stack $ubackup
	set @redo_stack $rbackup
	lappend @undo_stack [list $what $atom [lrange [info level -3] 1 end]]
	$self changed
}

def CommandHistory list {} {
	set r {}
	set hist [concat [$self undo_stack] [list "You Are Here"] [lreverse [$self redo_stack]]]
	set i 0
	foreach e $hist {lappend r "$i: $e"; incr i}
	return $r
}

set command_history [CommandHistory new]

#-----------------------------------------------------------------------------------#
class_new History {Thing}

def History init {size} {
	set @size $size
	set @hist {{}}
	set @histi -1
}

def History histi= {val} {set @histi $val}
def History histi {} {return $@histi}

def History set_hist {idx stuff} {set @hist [lreplace $@hist $idx $idx $stuff]}

def History prepend {stuff} {
	set @hist [linsert $@hist 1 $stuff]
	if {[llength $@hist] >= $@size} {set @hist [lrange $@hist 0 [expr {$@size-1}]]}
}

def History traverse {incr} {
	set @histi [expr {$@histi + $incr}]
	set mod [min [llength $@hist] [expr {$@size+1}]]
	if {$@histi >=$mod} {set @histi [expr $@histi%$mod]}
	if {$@histi < 0} {set @histi [expr ($@histi+$mod)%$mod]}
	return [lindex $@hist $@histi]
}

History new_as obj_hist 5
#-----------------------------------------------------------------------------------#
# this is the beginning of the more application-dependent part.

switch $tcl_platform(os) {
  Darwin {set OS osx}
  default {set OS $tcl_platform(platform)}
}

if {$tk} {
	option add *foreground #000000
	option add *font {Helvetica -12}
	foreach tkclass {Menu Button Checkbutton Radiobutton Entry Text Spinbox Scrollbar Canvas} {
		option add *$tkclass*borderWidth 1
		option add *$tkclass*activeBorderWidth 1
	}
	foreach tkclass {CheckButton RadioButton} {
		option add *$tkclass*selectColor #dd3000
	}
	foreach tkclass {Entry Text} {
		option add *$tkclass*background #b0c4d8
		option add *$tkclass*selectBackground #6088b0
	}
	option add *__tk__messagebox*Canvas*borderWidth 0
	foreach tkclass {Listbox} {
		option add *$tkclass*background #c4d8b0
		option add *$tkclass*selectBackground #88b060
	}
	foreach tkclass {Label} {
		#option add *$tkclass*background #909090
	}
	# very small icons:
	foreach {name w h values} {
		icon_empty      7 7 "0,0,0,0,0,0,0"
		icon_plus       7 7 "8,8,8,127,8,8,8"
		icon_minus      7 7 "0,0,0,127,0,0,0"
		icon_close      7 7 "99,119,62,28,62,119,99"
		icon_wedge_up   7 5 "8,28,62,127,0"
		icon_wedge_down 7 5 "0,127,62,28,8"
		icon_up         7 7 "8,28,62,127,28,28,28"
		icon_down       7 7 "28,28,28,127,62,28,8"
		icon_right      7 7 "8,24,63,127,63,24,8"
		icon_left       7 7 "8,12,126,127,126,12,8"
	} {
		image create bitmap $name -data "#define z_width $w\n#define z_height $h
			static unsigned char z_bits[] = { $values };"
	}


	set main [. cget -class]
	set ::current_window .
	bind $main    <FocusIn> {set ::current_window %W}
	bind Toplevel <FocusIn> {set ::current_window %W}
	proc tk_messageBox2 {args} {tk_messageBox -parent $::current_window {*}$args}

	# Also we have to get rid of tab's changing the focus.
	bind all <Key-Tab> ""
	#bind all <Key-Shift-Tab> ""
	bind all <<PrevWindow>> ""
	set mods {{} 0 Shift- 1 Control- 4 Shift-Control- 5 Alt- 8 Shift-Alt- 9 Control-Alt- 12 Shift-Control-Alt- 13}
	foreach type {KeyPress KeyRelease} {
	    foreach {subtype mod} $mods {
		bind all <$subtype$type> "$::event_history add \[list $type %W %x %y $mod %K %k\]"
	    }
	}
	foreach type {ButtonPress ButtonRelease} {
	    foreach {subtype mod} $mods {
		bind all <$subtype$type> "$::event_history add \[list $type %W %x %y $mod %b %b\]"
	    }
	}
}

proc modekey {k mode} {
  set s ""
  if {$mode&1} {append s Shift-}
  if {$mode&4} {append s Control-}
  if {$mode&8} {append s Alt-}
  if {[regexp {[0-9]} $k]} {set k Key-$k}
  return $s$k
}

proc modeclick {k mode event} {
  set s ""
  if {$mode&1} {append s Shift-}
  if {$mode&4} {append s Control-}
  if {$mode&8} {append s Alt-}
  if {[regexp {[0-9]} $k]} {set k $event-$k}
  return $s$k
}

# there are two palettes of 30 colours used in Pd
# when placed in a 3*10 grid, the difference is that
# the left corner of 3*3 (the greys) are transposed (matrixwise)
# here is the one used in the swatch color selector:
set preset_colors {
	fcfcfc e0e0e0 bcbcbc fce0e0 fce0c0 fcfcc8 d8fcd8 d8fcfc dce4fc f8d8fc
	a0a0a0 7c7c7c 606060 fc2828 fcac44 e8e828 14e814 28f4f4 3c50fc f430f0
	404040 202020 000000 8c0808 583000 782814 285014 004450 001488 580050
}

switch $::OS {
  osx     {set pd_tearoff 0}
  default {set pd_tearoff 1}
}

proc guess_lang {} {
  set lang C
  if {[info exist ::env(LC_ALL)]} {set lang $::env(LC_ALL)}
  if {[info exist ::env(LANG)]}   {set lang $::env(LANG)}
  set lang [lindex [split $lang {[_.]}] 0]
  return $lang
}

#temporary
set leet 0

proc say {k args} {
  global text
  if {[llength $args]} {
    set text($k) [lindex $args 0]
  } else {
    if {[info exist text($k)]} {
        if {$::leet} {
		return [string map -nocase {a 4 e 3 t 7 s 5 i 1 o 0 g 9} $text($k)]
	} else {
		return $text($k)
	}
    } else {return "{{$k}}"}
  }
}

proc can_say {k args} {
  return [info exist ::text($k)]
}

proc say_namespace {k code} {uplevel 1 $code}
proc say_category  {text} {}

switch -- [lindex [file split $argh0] end] {
  desire.tk {set cmdline(server) [file join [file dirname $argh0] pd]}
  default {set cmdline(server) [file join [file dirname [file dirname $argh0]] bin/pd]}
}

set cmdline(rcfilename) ~/.pdrc
set cmdline(ddrcfilename) ~/.ddrc
set cmdline(console) 1000
if {[file exists ../icons/mode_edit.gif]} {
	set cmdline(icons) ../icons
} else {
	set cmdline(icons) [file join [file dirname [file dirname $argh0]] lib/pd/icons]
}

#-----------------------------------------------------------------------------------#
set accels {}
proc read_client_prefs_from {filename} {
	global cmdline look key accels
	set ::accels {}
	puts "reading from $filename"
	set fd [open $filename]
	set contents [read $fd]
	close $fd
	foreach {category category_data} $contents {
		foreach {class class_data} $category_data {
			foreach {var val} $class_data {set ${category}($class:$var) $val}
		}
	}
	foreach k [array names key] {
	    if {[llength $key($k)]} {
		if {![dict exists $accels $key($k)]} {
			dict set accels $key($k) $k
		} else {
			dict lappend accels $key($k) $k
		}
	    }
	}
}
proc read_ddrc {} { ;# load defaults then load .ddrc
	if {[file exists "defaults.ddrc"]} {
		read_client_prefs_from "defaults.ddrc"
	} else {
		read_client_prefs_from [file join [file dirname [file dirname $::argh0]] "lib/pd/bin/defaults.ddrc"]
	}
	if {[file exists $::cmdline(ddrcfilename)]} {
		read_client_prefs_from $::cmdline(ddrcfilename)
	}
}
read_ddrc

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

set cmdline(port) 0
set cmdline(gdb) 0
set cmdline(gdbconsole) 1
set cmdline(valgrind) 0
if {$look(View:language) eq "auto"} {
	set language [guess_lang]
} else {
	set language $look(View:language)
}

set files_to_open {}

proc cmdline_help {} {
  puts "DesireData commandline options:
  -serverargs (for future use)
      -server   select the executable for the pd server
         -gdb   run pd server through gdb
   -manualgdb   run gdb in the terminal
    -valgrind   run pd server through valgrind
  -novalgrind   ... or don't
  -safemode     run desiredata with all default settings
  -dzinc        use zinc emulation"
}

for {set i 0} {$i < $argc} {incr i} {
  global cmdline files_to_open
  set o [lindex $argv $i]
  switch -regexp -- $o {
          ^-port\$ {incr i; set cmdline(port) [lindex $argv $i]}
    ^-serverargs\$ {error "not supported yet"}
    ^-server\$  {incr i; set cmdline(server)  [lindex $argv $i]}
           ^-gdb\$ {set cmdline(gdb) 1}
     ^-manualgdb\$ {set cmdline(gdbconsole) 0}
      ^-valgrind\$ {set cmdline(valgrind) 1}
    ^-novalgrind\$ {set cmdline(valgrind) 0}
    ^-safemode\$ {set cmdline(safemode) 1}
    ^-dzinc\$ {set cmdline(dzinc) 1}
    ^(-h|-help|--help)\$ {cmdline_help; exit 1}
    ^- {puts "ERROR: command line argument: unknown $o"}
    default {lappend files_to_open [lindex $argv $i]}
  }
}

#set cmdline(server) \"$cmdline(server)\"
set encoding ""
if {$language == "C"} {set language en}
proc localedir {x} {file join [file dirname [file dirname $::argh0]] lib/pd/bin/$x}
if {[regexp {desire\.tk$} $argh0]} {source locale/index.tcl} {source [localedir locale/index.tcl]}
puts "locale/index.tcl vs [localedir locale/index.tcl]"
set langentry [figure_out_language $language]
set encoding [lindex $langentry 0]
set langfile locale/[lindex $langentry 1].tcl
if {[regexp {desire\.tk$} $argh0]} {
	if {$encoding != ""} {source -encoding $encoding $langfile} else {source $langfile}
} else {
	if {$encoding != ""} {source -encoding $encoding [localedir $langfile]} else {source [localedir $langfile]}
}

if {[info exists ::cmdline(safemode)]} {read_client_prefs_from "defaults.ddrc"}
if {[info exists ::cmdline(dzinc)]} {package require dzinc}

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

#!@#$ is this still valid?
set look(Box:extrapix) [switch $::OS {
        osx     {concat 2}
        default {concat 1}}]

#font is defined as Thing for now, as the completion needs to get to these ones.

#!@#$ View->???
#set look(View:tooltip) 1

#!@#$ View->TextBox ?
#set look(View:minobjwidth) 21

#!@#$ View->ObjectBox
#set look(View:fg) #000000
#set look(View:bg) #ffffff
#set look(View:frame1) #99cccc
#set look(View:frame2) #668888
#set look(View:frame3) #000000

set zoom(canned) [list 25 33 50 75 100 125 150 200 250 300 400]
set scale_amount 1.1
################## set up main window #########################

class_new Console {View}

def Console osx_scroll {axis diff} {$@c.1 yview scroll [expr {-2-abs($diff)/$diff}]}

def Console init {c} {
	set @c $c
	frame $c
	text $c.1 -width 72 -height 20 -yscrollcommand "$c.2 set" -font [$self look font]
	scrollbar $c.2 -command "$c.1 yview"
	pack $c.1 -side left -fill both -expand yes
	pack $c.2 -side left -fill    y -expand no
	pack $c -fill both -expand yes
	$c.2 set 0.0 1.0
	switch $::OS {osx {bind $c.1 <MouseWheel> [list $self osx_scroll %D]}}
	set @lines 0
}

def Console widget {} {return $@c}

def Console post_string {x} {
	set oldpos [lindex [$@c.2 get] 1]
	$@c.1 insert end $x
	regsub -all "\n" $x "" y
	set n [expr {[string length $x]-[string length $y]}]
	incr @lines $n
	set max $::cmdline(console)
	if {$@lines >= $max} {$@c.1 delete 1.0 [expr {$@lines-$max+1}].0; set @lines $max}
	if {$oldpos > 0.9999} {$@c.1 see end}
}

#class_new Client {Menuable View}
class_new Client {Menuable Thing}

set ctrls_audio_on 0
set ctrls_meter_on 0

def Client window {} {return .}

def Client init_binds {} {
    bind . <Control-Key>       {$main ctrlkey %x %y %K %A 0}
    bind . <Control-Shift-Key> {$main ctrlkey %x %y %K %A 1}
    switch $::OS {
      osx {
        bind . <Mod1-Key>       {$main ctrlkey %x %y %K %A 0}
        bind . <Mod1-Shift-Key> {$main ctrlkey %x %y %K %A 1}
      }
    }
#    bind . <Motion> {.debug.1 configure -text "widget = %W"}
}

# miller uses this nowadays (matju fished it in pd-cvs for 0.40). we don't use it for now.
# remember to fix all quoting problems, which in the end may or may not involve the following proc.
proc pdtk_unspace {x} {
  set y [string map {" " "_" ";" "" "," "" "{" "" "}" "" "\\" ""} $x]
  if {$y == ""} {set y "empty"}
  concat $y
}

proc pdtk_pd_meters {indb outdb inclip outclip} {
    foreach {z clip db} [list in $inclip $indb out $outclip $outdb] {
	.controls.$z.1.mtr coords m 0 0 $db 0
	.controls.$z.1.clip configure -background [if {$clip==1} {concat red} {concat black}]
    }
}

proc pd_startup {version apilist midiapilist args} {
	set ::pd_version $version
	set ::pd_apilist $apilist
	set ::pd_midiapilist $midiapilist
	foreach api $apilist {
		lappend ::pd_apilist2 "-[string tolower [lindex $api 0]]"
	}
	set version [regsub "^DesireData " $::pd_version ""]
	post "DesireData server version $version"
}

def Client init_controls {} {
    menu .mbar
    pack [frame .controls] -side top -fill x
    foreach t {file window help} {
	.mbar add cascade -label [say $t] -menu [menu .mbar.$t -tearoff $::pd_tearoff]
    }
    .mbar.window configure -postcommand "$self fix_window_menu"
    foreach {z fill} {in #0060ff out #00ff60} {
	set f .controls.$z
	frame $f
	frame $f.1 -borderwidth 2 -relief groove
	canvas $f.1.mtr -width 100 -height 10 -bg #222222
	    $f.1.mtr create line [list 0 0 0 0] -width 24 -fill $fill -tags m
	canvas $f.1.clip -width 5 -height 10 -bg #222222
	pack $f.1.mtr $f.1.clip -side left
	pack [label $f.2 -text [say $z]:] $f.1 -side left
	pack $f -side left -pady 0 -padx 0
    }
    foreach {w x y z} {
	    audiobutton audio  ctrls_audio_on {netsend [list pd dsp    $ctrls_audio_on]}
	    meterbutton meters ctrls_meter_on {netsend [list pd meters $ctrls_meter_on]}
    } {
	pack [checkbutton .controls.$w -text [say $x] -variable $y -anchor w -command $z] -side left
    }
    button .controls.clear -text [say console_clear] -command {.log.1 delete 0.0 end}         -padx 2 -pady 0
    button .controls.dio   -text [say io_errors]     -command {netsend [list pd audiostatus]} -padx 2 -pady 0
    pack .controls.clear .controls.dio -side right
    if {$::debug} {
	    frame .debug
	    pack [label .debug.1 -anchor w -text ""] -side left
	    pack [entry .debug.3 -textvariable ::serial -width 5] -side right
	    pack [label .debug.2 -text "obj.serial: " -justify right] -side right
	    pack .debug -side bottom -fill x
    }
    if {$::cmdline(console)} {set ::console [Console new .log]}
    . configure -menu .mbar
    wm title . "DesireData"
    catch {wm iconphoto . icon_pd_32}
    if {[regexp {\d\d\d\d-\d\d-\d\d} $::svnid date]} {
      regsub -all "/" $date "." date
      regexp {desire.tk (\d+)} $::svnid blah revision
      set version "svn#$revision ($date)"
    } {set version "(unknown svn version)"}
    set ::pd_version_client $version
    post "DesireData client version %s" $version
    post "Running from %s" [info script]
    post "Using Tcl %s and Tk %s" $::tcl_patchLevel $::tk_patchLevel
}

proc pdtk_pd_dsp {value} {
    global ctrls_audio_on
    set ctrls_audio_on $value
}

proc pdtk_pd_dio {red} {
    .controls.dio configure -background red -activebackground [if {$red==1} {list red} {list lightgrey}]
}

############### set up global variables ################################

set pd_opendir [pwd]

############### set up socket ##########################################
set sock {}
set sock_lobby {}

proc poll_sock {} {
	global sock sock_lobby cmdline
	if {[llength $sock]==0} {return}
	while {1} {
		set cmd [gets $sock]
		if {[eof $sock]} {
			if {!$cmdline(gdb)} {
				tk_messageBox2 -message "connection ended by server.\n(crash? try: desire -gdb)" -type ok
			}
			set sock {}
			return
		}
		if {[fblocked $sock]} {break}
		if {$::debug} {if {[string first pdtk_post $cmd]!=0} {puts "[VTmagenta]-> $cmd[VTgrey]"}}
		append sock_lobby "\n$cmd"
		if {[catch {eval $sock_lobby}]} {
			switch -regexp -- $::errorInfo { "^missing close-brace" {
				#puts "waiting for the end of: [string range $sock_lobby 0 40]"
				continue
			}}
			error_dump
		}
		set sock_lobby {}
	}
	flush $sock
	after 25 poll_sock
}

set server_pid 0
proc poll_gdb {} {
	global gdb
	while {1} {
		set line [gets $gdb]
		if {$line=="" || [fblocked $gdb]} {break} ;# which way should i check for no input?
		if {[eof $gdb]} {return}
		regsub {^\(gdb\) ?} $line {} line
		if {[regexp {^\[Thread debug} $line]} {continue}
		if {[regexp {^\[New Thread.*LWP (\d+)} $line dummy pid]} {
			if {!$::server_pid} {
				set ::server_pid $pid
				post "server pid=$pid"
				continue
			}
		}
		if {[regexp {^Reading symbols from} $line]} {continue}
		if {[regexp {^Using host libthread_db} $line]} {continue}
		if {[regexp {^Starting program:} $line]} {continue}
		if {[regexp {^Program received signal (\w+), (.*)\.} $line bogus sig1 sig2]} {
			set where ""
			# can anyone figure out why a long backtrace won't be slurped in this case?
			set timeout [expr {[clock seconds]+2}]
			#fconfigure $gdb -blocking 1 -buffering none
			while {![eof $gdb] && [clock seconds] < $timeout} {
				set line [gets $gdb]
				if {$line eq ""} {continue} ;# busy-wait
				regsub {^\(gdb\) ?} $line {} line
				append where "$line\n"
				#puts "where size = [string length $where]"
			}
			OopsDialogue new $sig1 $sig2 $where
		}
		post "\[gdb\] %s" $line
	}
	after 100 poll_gdb
}

proc pd_connect {} {
	global sock
	if {[catch {set sock [socket 127.0.0.1 13666]}]} {
		post "can't connect... wait a second"
		after 1000 pd_connect
		return
	}
	post "Connected to server"
	fconfigure $sock -blocking 0 -buffering line
	netsend [list pd init]
	poll_sock
	foreach f $::files_to_open {
		set ff [file split [file normalize $f]]
		set ffl [llength $ff]
		set file [lindex $ff [expr $ffl-1]]
		set dir [join [lrange $ff 0 [expr $ffl-2]] [file separator]]
		netsend [join [list pd open $file $dir]]
	}
}

set server_port 13666
after 1000 pd_connect

after 0 {
  if {$cmdline(port)} {
	set server_port $cmdline(port)
	# and then do nothing...
  } elseif {$cmdline(valgrind)} {
	#exec valgrind --tool=memcheck $cmdline(server) -guiport $server_port &
	exec valgrind --tool=memcheck --gen-suppressions=all --suppressions=valgrind3.supp $cmdline(server) -guiport $server_port &
  } else {
    if {$cmdline(gdb)} {
	if {$cmdline(console) && $cmdline(gdbconsole)} {
	    set gdb [open "| gdb --quiet 2&>1" w+]
	    fconfigure $gdb -blocking 0 -buffering none
	    puts $gdb "file \"$cmdline(server)\"" ;# bad quoting, sorry
	    puts $gdb "set width 242"
	    puts $gdb "run -guiport $server_port"
	    puts $gdb "where"
	    puts $gdb "quit"
	    flush $gdb
	    after 0 poll_gdb
	} else {
	    exec gdb --args $cmdline(server) -guiport $server_port &
	    #exec gdb --tui --args $cmdline(server) -guiport $server_port &
	}
    } else {
	exec $cmdline(server) -guiport $server_port &
    }
  }
}

################ utility functions #########################

proc enquote {x} {
    set foo [string map {"," "" ";" "" "\"" ""} $x]
    return [string map {" " "\\ " "{" "" "}" ""} $foo]
}

proc pdtk_watchdog {} {netsend [list pd ping]; after 2000 {pdtk_watchdog}}

proc accel_munge {acc} {
    switch $::OS {
      osx {
        set tmp [string toupper [string map {Ctrl Meta} $acc] end]
	if [string is upper [string index $acc end]] {
	    return Shift+$tmp
	} else {
	    return $tmp
	}
      }
      default {return $acc}
    }
}

# a menuable must be a View
# and it must have a window so that the Menuable methods work
# it could be renamed to Windowed
class_new Menuable {}
def Menuable init {args} {
	eval [concat [list super] $args]
	set @accel {}
	set @menubar .$self.m
}

# this doesn't have to do with menus, only with toplevel windows.
def Menuable raise {} {
	set w [$self window]
	set w $w.c
	raise $w
	focus -force $w
}

set untitled_number 1
set untitled_folder [pwd]

# just a dummy proc
proc none {args} {}

def Client new_file {} {
	global untitled_number untitled_folder
	netsend [list pd filename Untitled-$untitled_number $untitled_folder]
	netsend [list #N canvas]
	netsend [list #X pop 1]
	incr untitled_number
}


set patch_filetypes {
	{"pd files"  ".pd"}
	{"max files" ".pat"}
	{"all files" "*"}
}

set image_filetypes {
	{"image files" ".gif .png"}
	{"gif files"  ".gif"}
	{"png files" ".png"}
	{"all files" "*"}
}

catch {tk_getOpenFile -load-once} ;#only works with tcltk 8.5
catch {
	set ::tk::dialog::file::showHiddenBtn 1
	set ::tk::dialog::file::showHiddenVar 0
}

def Client open_file {} {
    global pd_opendir patch_filetypes
    set filename [tk_getOpenFile -parent $::current_window -defaultextension .pd -filetypes $patch_filetypes -initialdir $pd_opendir]
    if {$filename != ""} {$self open_file_really $filename}
}

def Client open_file_really {filename} {
    set i [string last / $filename]
    set folder [string range $filename 0 [expr $i-1]]
    set ::pd_opendir $folder
    set basename [string range $filename [expr $i+1] end]
    if {[string last .pd $filename] >= 0} {
	    netsend [list pd open $basename $folder]
    }
}

def Client send_message {} {
    toplevel .sendpanel
    set e .sendpanel.entry
    pack [entry $e -textvariable send_textvar] -side bottom -fill both -ipadx 100
    $e select from 0
    $e select adjust end
    bind $e <KeyPress-Return> {netsend $send_textvar; after 50 {destroy .sendpanel}}
    focus $e
}

def Client quit {} {
	set answer [tk_messageBox2 -message "Do you really wish to quit?" -type yesno -icon question]
	switch -- $answer {yes {netsend [list pd quit]; exit}}
}

def Client abort_server {} {
	set answer [tk_messageBox2 -message "Do you really wish to abort?" -type yesno -icon question]
	switch -- $answer {yes {exec kill -ABRT $::server_pid}}
}

def Client server_prefs {} {ServerPrefsDialogue new_as pdrc}
def Client client_prefs {} {ClientPrefsDialogue new_as ddrc}

proc menu_pop_pd {} {raise .}

def Menuable populate_menu {menu list} {
	global key
	if {[string index $menu 0] != "."} {set menu $@menubar.$menu}
	foreach name $list {
		if {$name == ""} {$menu add separator; continue}
		set k ""
		if {[info exists key($@_class:$name)]} {
			if {[string length $key($@_class:$name)]} {set k $key($@_class:$name)}
		}
		$menu add command -label [say $name] -command "$self $name" -accelerator [accel_munge $k]
	}
}

def Client init_menus {} {
    #removed paths after send_message
    $self populate_menu file {
        new_file open_file {}
	server_prefs client_prefs send_message {}
	audio_on audio_off {}
	abort_server quit}
    $self populate_menu help {
	about documentation class_browser do_what_i_mean {}
	test_audio_and_midi load_meter latency_meter {}
	clipboard_view command_history_view event_history_view keyboard_view client_class_tree}
}

def Client init {} {
  super
  set @menubar .mbar
  $self init_controls
  $self init_binds
  $self init_menus
  # it's necessary to raise the window on OSX
  switch $::OS { osx {raise .; wm iconify .; after 100 {wm deiconify .}}}
  after 0 {
	  Listener new .tcl [say "tcl_console"] tcl_eval
	  Listener new .pd  [say "pd_console"]  pd_eval
  }
  #wm geometry .[$self keyboard_view] -0+0
  #wm geometry .[$self event_history_view] -0-0
}

proc post {args} {
	set s "[eval [linsert $args 0 format]]\n"
	#	set s "[info level -1]: $s"
	if {$::cmdline(console)} {$::console post_string $s} else {puts stderr $s}
}
proc pdtk_post {s} {
	if {$::cmdline(console)} {$::console post_string $s} else {puts stderr $s}
}

def Menuable eval% {code} {
	regsub -all %W $code $self code
	uplevel [info level] $code
}

def Menuable getkey {k} {
	global accels
	if {[dict exists $accels $k]} {
		set vars [dict get $accels $k]
		foreach var $vars {
			#mset {class key} [split [dict get $accels $k] ":"]
			mset {class key} [split $var ":"]
			#if {$class != $@_class} {return ""} else {return $key}
			if {$class == $@_class} {return $key}
		}
	}
}

def Menuable ctrlkey {x y key iso shift} {
	set key [if {$shift} {string toupper $key} {string tolower $key}]
	set key "Ctrl+$key"
	set cmd [$self getkey $key]
	if {![string length $cmd]} {
		switch [$self class]:$key {
			#explicitly listed here to do nothing
			Client:Ctrl+c {}
			Client:Ctrl+v {}
			default {post "unknown key $key"}
		}
	} else {$self eval% "%W $cmd"}
}

def Menuable altkey {x y key iso shift} {
    set key [if {$shift} {string toupper $key} {string tolower $key}]
    set key "Alt+$key"
    set cmd [$self getkey $key]
    if {[string length $cmd]} {$self eval% "%W $cmd"} else {post "unknown key $key"}
}

#-----------------------------------------------------------------------------------#
set pd_apilist "{ALSA 1}"
set pd_apilist2 "default"

#-----------------------------------------------------------------------------------#
#fixme: actually, is it ok that View<Menuable ? --matju
class_new View {Menuable Observable Thing}

# normally ninlets/noutlets should be in class "Box", but server-side isn't smart enough
def View  pdclass= {v}    {set @pdclass $v}
def View  pdclass  {} {return $@pdclass}
def View  ninlets= {v}    {set  @ninlets $v}
def View  ninlets  {} {return  $@ninlets}
def View noutlets= {v}    {set @noutlets $v}
def View noutlets  {} {return $@noutlets}
def View   click {x y f target} {}
def View unclick {x y f target} {}
def View  motion {x y f target} {}

def View subpatch {} {if {[info exists @subpatch]} {return 1} else {return 0}}

def View look_cache {k} {
	global look look_cache
	foreach super [$@_class ancestors] {
		if {[info exists look($super:$k)]} {
			set look_cache($@_class:$k) $super
			return $super
		}
	}
}
def View look {k} {
	if {![info exists ::look_cache($@_class:$k)]} {$self look_cache $k}
	return   $::look($::look_cache($@_class:$k):$k)
}

def View init {} {
	super
	set @selected? 0
	set @index 0xDEADBEEF
	set @ninlets 1  ;# should be in Box init
	set @noutlets 0 ;# should be in Box init
	set @canvas ""
#	set @inside_box 42 ;# temporary fix
	set @ioselect {}
}

def View classtags {} {return {foo}}

set item {
	set canvas [$self get_canvas] 
	if {$canvas == ""} {return}
	set c [$self cwidget]
	set zoom [$canvas zoom]
	set coords [lmap * $coords $zoom]
	set find [lsearch $args "-width"]
	if {$find >= 0} {
		incr find
		set w [lindex $args $find]
		lset args $find [format %.0f [expr {$w*$zoom}]]
	}
	set find [lsearch $args "-font"]
	if {$find >= 0} {
		incr find
		set fs [lindex [lindex $args $find] 1]
		lset args $find 1 [format %.0f [expr {$fs*$zoom}]]
	}
	#set find [lsearch $args "-fill"]
	#if {$find >= 0} {
	#	incr find
	#	set fill [lindex $args $find]
	#	lset args $find [randomise_color $fill]
	#}
	set tags {}
	foreach s $suffixes {lappend tags "$self$s"}
	set ss [lindex $tags 0]
	lappend tags $self
	set tags [concat $tags [$self classtags]]
}
append item [correct_splat {
	if {![llength [$c gettags $ss]]} {
		$c create $type $coords -tags $tags {*}$args
	} {
		$c itemconfigure $ss {*}$args
		$c coords        $ss {*}$coords
	}
}]
def View item {suffixes type coords args} $item

def View item_delete {{suffix all}} {
	if {$@canvas == ""} {return}
	set c [$@canvas widget]
	if {![winfo exists $c]} {
		set canvas [$@canvas get_canvas]
		if {$canvas == ""} {return}
		set c [[$@canvas get_canvas] widget]
		if {![winfo exists $c]} {return}
	}
	switch -- $suffix {
		all     {$c delete $self}
		default {$c delete $self$suffix}}
}

def View draw {} {}

def View delete {} {$self erase; super}
def View erase {} {$self item_delete}
def View selected?  {}  {return $@selected?}
def View selected?= {x} {set @selected? $x; $self changed} ;# this is for use by the Selection class only
def View edit? {} {if {[info exists @edit]} {return $@edit} else {return 0}}
def View select {state} {
	set ostate [$self selected?]
	set @selected? $state
	if {$state!=$ostate} {$self changed}
}

# give topleft point of an object in the canvas it's rendered in.
# this includes GOP and excludes zoom.
# inheritance problem: this doesn't work for Wire, which doesn't store its positions
def View xy {} {
	if {$@canvas == ""} {return [list $@x1 $@y1]}
	set type [$@canvas type]
	if {$type == "gopabs" || $type == "gopsub"} {
		mset {xmargin ymargin} [$@canvas margin]
		mset {x y} [$@canvas xy]
		set x1 [expr {($@x1-$xmargin)+$x}]
		set y1 [expr {($@y1-$ymargin)+$y}]
		#if $self's gop is opened
		if {[regexp {^.x[0-9a-f]{6,8}.c} [focus] f]} {
			if {$f == ".$@canvas.c"} {set x1 $@x1; set y1 $@y1}
		}
		#if {[focus] != "." && [focus] == ".$@canvas.c"} {set x1 $@x1; set y1 $@y1}
		return [list $x1 $y1]
	}
	return [list $@x1 $@y1]
}

def View canvas  {}  {return $@canvas}
def View canvas= {c} {
	set @canvas $c
	# should "subscribe" call "changed"? (or pretend to?)
	$self subscribe $c
	$self changed
	$self outside_of_the_box
}

def View visible {} {if {[info exists @inside_box]} {return $@inside_box} {return -1}}

# this returns the canvas actually exists/drawn
# see also def Canvas get_canvas
def View get_canvas {} {
	set canvas $@canvas
	if {$canvas == ""} {return ""}
	#while {![$canvas havewindow]} {set canvas [$canvas canvas]}
	while {![winfo exists [$canvas widget]]} {set canvas [$canvas canvas]}
	return $canvas
}
# this will return the top level gop if gop is nested
def View get_parent_gop {canvas} {
	set obj $self
	while {$canvas != [$obj canvas]} {set obj [$obj canvas]}
	return $obj
}

def View outside_of_the_box {} {
	if {$@canvas eq ""} {return}
	# always hide these things
	if {[$self class] == "Wire"}       {set @inside_box 0; return}
	if {[$self class] == "ObjectBox"}  {set @inside_box 0; return}
	if {[$self class] == "MessageBox"} {set @inside_box 0; return}
	if {[$self class] == "Comment"}    {set @inside_box 0; return}
	if {[$self class] == "Array"} {$@canvas visibles+= $self; set @inside_box 1; return}
	set x1 $@x1; set y1 $@y1
	if {[$@canvas gop]} {
		set mess [$@canvas get_mess]
		set pixwidth [lindex $mess 4]
		set pixheight [lindex $mess 5]
		if {[llength $mess] == 6} {
			set xmargin 0; set ymargin 0
		} else {
			set xmargin [lindex $mess 6]; set ymargin [lindex $mess 7]
		}
		if {$x1 < $pixwidth +$xmargin && $x1 > $xmargin && \
		    $y1 < $pixheight+$ymargin && $y1 > $ymargin} {
			set @inside_box 1
			$@canvas visibles+= $self
		} else {
			set @inside_box 0
			$@canvas visibles-= $self
		}
	} else {set @inside_box 1}
}

def View draw_maybe {} {
	if {$@canvas == "" && [winfo exists .$self.c]} {$self draw; return}
	if {[$self class] == "Canvas" && $@canvas == ""} {return}
	if {$@inside_box} {
		#if {[$@canvas mapped] && ![$@canvas abs] && [$self gop_check]} {$self draw}
		if {[$@canvas mapped] && [$self gop_check]} {$self draw}
	} else {
		# for drawing opened gop
		if {[winfo exists .$@canvas.c]} {$self draw}
	}
}
#this checks if $self can be seen, ie nested gop.
def View gop_check {} {
	set canvases $@canvas
	while {[lindex $canvases end] != ""} {lappend canvases [[lindex $canvases end] canvas]}
	set canvases [lreplace $canvases end-1 end]; # all canvases
	foreach canvas $canvases {
		# if a canvas is not a gop and its window does not exists
		if {![$canvas gop] && ![winfo exists [$canvas widget]]} {return 0; break}
	}
	return 1
}

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

class_new Canvas {Menuable ObjectBox}

def Canvas close {} {
	after cancel $@motion_after_id
	if {$@subpatch} {
		#can't wait till @mapped get updated thru proc change
		if {$@gop} {foreach x [$@objects values] {$x outside_of_the_box}}
		$self save_geometry
		$self copy_times= 0
		netsend [list .$self vis 0]
		#netsend [list .$self close]
		return
	}
	if {$@gop} {
		foreach x [$@objects values] {$x outside_of_the_box}
		netsend [list .$self close]
		return
	}
	switch [tk_messageBox2 -message [say save_changes?] -icon question -type yesnocancel -default cancel] {
		yes    {$self save; netsend [list .$self close]}
		no     {            netsend [list .$self close]}
		cancel {}
	}
}

def Canvas save_geometry {} {
	set geometry [wm geometry .$self]
	set cw [winfo width [$self widget]]; set ch [winfo height [$self widget]]
	foreach {size x y} [split $geometry "+"] {mset {w h} [split $size "x"]; set x1 $x; set y1 $y}
	set x2 [expr $x1+$cw]; set y2 [expr $y1+$ch]
	netsend [list .$self bounds $x1 $y1 $x2 $y2]
}

def Canvas save {} {
	if {$@subpatch} {return [$@canvas save]}
	$self checkgeometry
	set c [$self widget]
	if {![regexp {^Untitled-[0-9]} $@name]} {
		$self save_geometry
		netsend [list .$self savetofile $@name $@folder]
	} else {
		$self save_as
	}
}

def Canvas save_as {} {
	$self checkgeometry
	set filename [tk_getSaveFile -parent $::current_window -filetypes $::patch_filetypes]
	if {$filename != ""} {
		set @file   [string range $filename [expr [string last / $filename]+1] end]
		set @folder [string range $filename 0 [expr [string last / $filename]-1]]
		$self save_geometry
		puts "save $@file dir to $@folder"
		netsend [list .$self savetofile $@file $@folder]
	}
}

def Canvas print {} {
    set filename [tk_getSaveFile -parent $::current_window -initialfile pd.ps -defaultextension .ps -filetypes { {{postscript} {.ps}} }]
    if {$filename != ""} {[$self widget] postscript -file $filename}
}
def Canvas quit {}         {$::main quit}
def Canvas abort_server {} {$::main abort_server}

proc wonder {} {tk_messageBox2 -message [say ask_cool] -type yesno -icon question}

def Canvas eval% {code} {
	mset {x y} $@curpos
	regsub -all %X $code $x code
	regsub -all %Y $code $y code
	super $code
}

def Client documentation {} {
    set filename [tk_getOpenFile -parent $::current_window -defaultextension .pd -filetypes { {{documentation} {.pd .txt .htm}} } -initialdir $::docdir]
    if {$filename != ""} {
        if {[string first .txt $filename] >= 0} {
            menu_opentext $filename
        } elseif {[string first .htm $filename] >= 0} {
            menu_openhtml $filename
        } else {
            set i [string last / $filename]
            set help_directory [string range $filename 0 [expr $i-1]]
            set basename       [string range $filename   [expr $i+1] end]
            netsend [list pd open [enquote $basename] [enquote $help_directory]]
        }
    }
}

def Canvas     new_file {} {$::main  new_file}
def Canvas    open_file {} {$::main open_file}
def Canvas send_message {} {$::main send_message}
def Client test_audio_and_midi {} {menu_doc_open doc/7.stuff/tools testtone.pd  }
def Client          load_meter {} {menu_doc_open doc/7.stuff/tools load-meter.pd}
def Client       latency_meter {} {menu_doc_open doc/7.stuff/tools latency.pd   }
def Client about {} {AboutDialogue new}
def Client class_browser {} {Browser new_as browser browser 0 0 ""}
def Client audio_on  {} {netsend [list pd dsp 1]}
def Client audio_off {} {netsend [list pd dsp 0]}
def Client        keyboard_view {} {    KeyboardDialogue new $::event_history}
def Client       clipboard_view {} {   ClipboardDialogue new $::clipboard}
def Client command_history_view {} {        ListDialogue new $::command_history [say command_history_view]}
def Client   event_history_view {} {EventHistoryDialogue new $::event_history}
def Client do_what_i_mean {} {wonder}

set pd_prefix [file dirname [file dirname [which pd]]]
set pd_guidir ${pd_prefix}/lib/pd
set doc_number 1
set updir [file dirname [file dirname $argh0]]
if {[file exists $updir/lib/pd/doc]} {
	set docdir $updir/lib/pd/doc
} else {
	set docdir $updir/doc
}

proc menu_opentext {filename} {
    set w [format ".help%d" $::doc_number]
    toplevel $w
    wm title $w $filename
    frame $w.1
    frame $w.2
    pack [text $w.1.text -relief raised -bd 2 -yscrollcommand "$w.1.scroll set"] -side left -fill both -expand 1
    pack [scrollbar $w.1.scroll -command "$w.1.text yview"] -side right -fill y
    pack [button $w.2.close -text [say close] -command "destroy $w"] -side right
    pack $w.2 -side bottom -fill    x -expand 0
    pack $w.1 -side bottom -fill both -expand 1
    set f [open $filename]
    while {![eof $f]} {
    	set bigstring [read $f 1000]
	regsub -all PD_BASEDIR $bigstring $::pd_guidir bigstring
    	regsub -all PD_VERSION $bigstring $::pd_version bigstring
    	$w.1.text insert end $bigstring
    }
    $w.1.text configure -state disabled
    close $f
    incr ::doc_number
    return $w
}

proc menu_doc_open {subdir basename} {
    set dirname $::pd_guidir/$subdir
    if {[string first .txt $basename] >= 0} {
    	return [menu_opentext $dirname/$basename]
    } else {
    	netsend [list pd open $basename $dirname]
    }
}

#-----------------------------------------------------------------------------------#
def Canvas editmode {} {return $@editmode}
def Canvas editmode= {mode} {
    if {$mode == $@editmode} {return}
    if {!$mode} {$self deselect_all}
    $self redraw ;# why this???
    set @editmode $mode; $self changed editmode
#    catch {.$self.bbar.edit configure -image icon_mode_$mode}
    if {$@mapped} {
      if {$mode} {set im icon_mode_edit} else {set im icon_mode_run}
      [$self window].bbar.edit configure -image $im
      if {[$self look hairstate] && !$@editmode} {$@crosshair erase}
	    if {[$self look gridstate]} {
		    if {$@editmode} {$@grid draw} else {$@grid erase}
	    }
    }
    # comment's look depends on the value of @editmode
    foreach child [$@objects values] {if {[[$child class] <= Comment]} {$child changed}}
    #!@#$ should update the checkbox in the editmenu
}

def Canvas editmodeswitch {} {$self editmode= [expr !$@editmode]}

def Canvas window {} {
	#if {$@gop && $@canvas != ""} {return [$@canvas window]}
	return .$self
}
def Canvas widget {} {return .$self.c}
def View  cwidget {} {return .[$self get_canvas].c}

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

def Canvas atomically {proc} {$@history atomically $proc}
def Canvas undo {} {$@history undo}
def Canvas redo {} {$@history redo}

def Canvas init {mess} {
    set @mapped 0
    set @gop 0
    set @goprect ""
    set @abs 0
    set @name ""
    set @folder "???"
    set @file ""
    super {#X obj 666 666 pd} ;# bogus
    $self reinit $mess
    set @zoom 1.0 ;# must be a float, not int
    set @action none
    set @objects [Hash new]; set @objectsel [Selection new]; set @visibles {}
    set   @wires [Hash new]; set   @wiresel [Selection new]

    set @focus ""
    set @curpos {30 30}
    set @bbox {0 0 100 100}
    set @dehighlight {}
#    if {$@mapped} {$self init_window} ;#!@#$ @mapped can't possibly be 1 at this point
    set @history $::command_history
    $self subscribe $::manager
    $self changed
    #$self canvas= $self ;#!@#$ EEVIL
    set @coords 0
    set @jump 0
    set @keynav_iocount 0  ;# the io select count
    set @keynav_port 0     ;# which in/outlet is selected
    set @keynav 0          ;# the list of objects that has io selected
    set @keynav_iosel 0    ;# last object that is io selected
    set @keynav_iosel_o {} ;# list of objects that has outlet selected
    set @keynav_iosel_i {} ;# list of objects that has inlet selected
    set @iosel_deselect 0  ;# if selected should be deselected by clicking at empty space
    set @keynav_current 0
    set @keynav_last_obj 0
    set @keynav_last_wire 0
    set @keynav_tab_sel "wire"
    set @keynav_shift 0
    set @copy_count 0
    set @findbar ""
    set @find_string ""
    set @iohilite {-1 0 0 0 0}
    set @keyprefix 0
    set @coords {0 0 1 1} ;# default #X coords line
    set @pixsize {0 0}
    set @margin {0 0}
    set @macro_q {}
    set @macro_delay 200
    set @blinky ""
    set @editmode 0
    set @show_id 0
    set @motion_queue {}
}

def Canvas reinit {mess} {
  switch -- [lindex $mess 0] {
    "#N" {
	# those four are not to be confused with other @variables of similar names.
	set @canvas_pos [lrange $mess 2 3]
	set @canvas_size [lrange $mess 4 5]
	set args [lrange $mess 6 end]
	switch [llength $args] {
	  1 {
		set @subpatch 0
		mset [list @fontsize] $args
		set @name ""
		set @mapped 1
	  }
	  2 {
		  set @subpatch 1
		  mset [list @name @mapped] $args
		  set @fontsize "what?"
	  }
	  default {error "wrong number of arguments (expecting 5 or 6, got [expr 4+[llength $args]])"}
        }
    }
    "#X" {
      switch -- [lindex $mess 1] {
        obj {}
	restore {
	  set @x1 [lindex $mess 2]
	  set @y1 [lindex $mess 3]
	  set args [lrange $mess 4 end]
	  $self text= [lrange $mess 4 end]
		if {!$@subpatch && [llength $args] != 0} {set @abs 1}
		if {$@mapped && !$@gop} {
			#if {!$@subpatch && $@text != ""} {set @abs 1; return}
			#if {![winfo exists .$self.c]} {$self init_window}
		}
	}
	coords {
	  set @coords  [lrange $mess 2 5]
	  set @pixsize [lrange $mess 6 7]
	  switch [llength $mess] {
	    8  {set @gop 0}
	    9  {set @gop [lindex $mess 8]}
	    11 {
		set @gop [lindex $mess 8]
		set @margin [lrange $mess 9 10]
	    }
	    default {error "what???"}
          }
	  if {$@gop} {set @mapped 1}
	}
      }
    }
    "" {return}
    default {error "huh? mess=$mess"}
  }
}

# doesn't this look like Canvas deconstruct ?
def Canvas get_mess {} {
	return [concat $@coords $@pixsize $@margin]
}


def Canvas margin {} {return $@margin}
def Canvas gop {} {return $@gop}
def Canvas hidtext {} {return $@hidetext}
def Canvas abs {} {return $@abs}
#def Canvas abs {} {if {!$@subpatch} {return 1} else {return 0}}
def Canvas subpatch {} {return $@subpatch}
def Canvas get_dimen {} {return $@canvas_size}

def Canvas gop_rect {} {
	mset {pxs pys} $@pixsize
	mset {mx my} $@margin
	set rect [list $mx $my [expr $mx+$pxs] [expr $my+$pys]]
	if {$@goprect == ""} {
		set @goprect [GopRect new $self $rect]
	} elseif {!$@editmode} {$@goprect delete; set @goprect ""; return}
	$@goprect draw

}

rename toplevel toplevel_orig
proc toplevel {name args} {
    eval [concat [list toplevel_orig $name] $args]
    catch {wm iconphoto $name icon_pd_32}
}

# should be called once and only from init
def Canvas init_window {} {
    lappend ::window_list $self
    set win .$self
    set c [$self widget]
    if {$::tcl_platform(platform) == "macintosh"} {
	    toplevel $win -menu $win.m
    } else {
	    if {[$self look menubar]} {toplevel $win -menu $win.m} else {toplevel $win -menu ""}
    }
    set @menubar $win.m
    $self init_menus
    # turn buttonbar on/off
    set @buttonbar [ButtonBar new $self]
    if {[$self look buttonbar]} {pack [$@buttonbar widget] -side top -fill x -expand no}
    set @statusbar [StatusBar new $self]
    # turn statusbar on/off
    if {[$self look statusbar]} {pack [$@statusbar widget] -side bottom -fill x}
    set w [expr [lindex $@canvas_size 0]-4];# dd canvas is 4 pixel out with pd canvas? 
    set h [expr [lindex $@canvas_size 1]-4]
    pack [canvas $c -width $w -height $h -background white] -side left -expand 1 -fill both
    set @yscroll $win.yscroll; set @xscroll $win.xscroll
    $self init_scrollbars
    wm minsize $win 1 1
    wm geometry $win +[lindex $@canvas_pos 0]+[lindex $@canvas_pos 1]
    wm protocol $win WM_DELETE_WINDOW "$self close"
    focus $c
    $self new_binds
    $self update_title
    $self motion_update
    set @runcommand [Runcommand new .$self "command" canvas_eval]
    set @crosshair [Crosshair new $self]
    set @active [Active new $self]
    set @sense [Sense new $self]
    set @grid [Grid new $self]
}

def Canvas activate_menubar= {val} {if {$val} {.$self configure -menu $@menubar} {.$self configure -menu ""}}

def Canvas activate_buttonbar= {val} {
	if {$val} {
		pack [$@buttonbar widget] -side top -fill x -expand no -before [$self widget]
	} else {pack forget [$@buttonbar widget]}
}

def Canvas activate_statusbar= {val} {
	if {$val} {
		if {[winfo exists $@yscroll]} {set w .$self.yscroll} else {set w .$self.c}
		pack [$@statusbar widget] -side bottom -fill x -before $w
	} else {pack forget [$@statusbar widget]}
}

def Canvas activate_scrollbars= {val} {if {!$val} {$self init_scrollbars} {$self remove_scrollbars}}

def Canvas activate_grid= {val} {if {$val} {$@grid draw} {$@grid erase}}

def Canvas init_scrollbars {} {
	set win .$self
	set c [$self widget]
	if {[winfo exists $win.yscroll]} {return}
	set size [$c bbox foo]
	mset {xs ys} $@canvas_size
	pack [scrollbar $win.yscroll -command "$c yview"                   ] -side  right -fill y -before $c
	pack [scrollbar $win.xscroll -command "$c xview" -orient horizontal] -side bottom -fill x -before $c
	set xw $win.xscroll; set yw $win.yscroll
	$c configure -yscrollcommand "$self scroll_set $yw" -xscrollcommand "$self scroll_set $xw"
	after 0 [list $self adjust_scrollbars]
}

def Canvas remove_scrollbars {} {
	set win .$self
	set c [$self widget]
	if {![winfo exists $win.yscroll]} {return}
	#use destroy instead of pack forget so that it can be tested with winfo exists
	destroy $win.yscroll
	destroy $win.xscroll
	$c configure -yscrollcommand "" -xscrollcommand "" -scrollregion ""
}

def Canvas adjust_scrollbars {} {
	set c [$self widget]
	set size [$c bbox foo]
	if {[$self look scrollbar]} {$self auto_scrollbars}
	if {$size != ""} {
		mset {xmin ymin xmax ymax} {0 0 100 100}
		mset {x1 y1 x2 y2} $size
		if {$x2 > 100} {set xmax $x2}
		if {$y2 > 100} {set ymax $y2}
		set bbox [list $xmin $ymin $xmax $ymax]
		set oldbbox [$c cget -scrollregion]
		# it is very inefficient to call "configure" here
		if {"$oldbbox" != "$bbox"} {$c configure -scrollregion $bbox}
		set @bbox $bbox
	}
}

def Canvas auto_scrollbars {} {
	set c [$self widget]
	if {[$c bbox foo] != ""} {
		mset {cx1 cy1 cx2 cy2} [$c bbox foo]
	}  else {
		set cx2 [lindex $@canvas_size 0]; set cy2 [lindex $@canvas_size 1]
	}
	set x2 [$c canvasx [winfo width $c]]
	set y2 [$c canvasy [winfo height $c]]
	if {$x2 == 1} {set x2 $cx2; set y2 $cy2}
	if {$cx2 <= $x2 && $cy2 <= $y2} {$self remove_scrollbars} {$self init_scrollbars}
}

def Canvas delete_window {} {
	set wl {}
	foreach w $::window_list {if {$w != $self} {lappend wl $w}}
	set ::window_list $wl
	destroy .$self
}

def Canvas delete {} {
	$self delete_window
	super
}

def Canvas focus {} {return $@focus}
def Canvas focus= {o} {set @focus $o}
def Canvas history {} {return $@history}

#-----------------------------------------------------------------------------------#
def Canvas find {} {
	if {[info exists ::_(findmodel:_class)]} {
		focus .$self.find.find
		findmodel reinit
	} else {
		FindModel new_as findmodel $self
		FindView new $self
		focus .$self.find.find
	}
}
def Canvas find_again {} {
	if {[info exists ::_(findmodel:_class)]} {findmodel remove_info;findmodel search_recursive}
}

def Canvas find_last_error {} {netsend [list pd finderror]}

def Canvas bind {eventtype selector args} {
    set c [$self widget]
    #bind $c $eventtype [concat [list $self $selector] $args \; $self statusbar_draw %x %y]
    #bind $c $eventtype "puts \[time {[concat [list $self $selector] $args \; $self statusbar_draw %x %y]}\]"
    #bind $c $eventtype "puts \[time {[concat [list $self $selector] $args]}\]"
	if {[$self look statusbar]} {
		bind $c $eventtype [concat [list $self $selector] $args \; $self statusbar_draw %x %y]
	} else {
		bind $c $eventtype [concat [list $self $selector] $args]
	}
}

def Canvas new_binds {} {
    # mouse buttons
    $self bind <Button>                   click_wrap %x %y %b 0
    $self bind <Shift-Button>             click_wrap %x %y %b 1
    $self bind <Control-Button>           click_wrap %x %y %b 2
    $self bind <Control-Shift-Button>     click_wrap %x %y %b 3
    $self bind <Alt-Button>               click_wrap %x %y %b 4
    $self bind <Alt-Shift-Button>         click_wrap %x %y %b 5
    $self bind <Alt-Control-Button>       click_wrap %x %y %b 6
    $self bind <Alt-Control-Shift-Button> click_wrap %x %y %b 7
    switch $::OS {
      osx {
        $self bind <Button-2>       click_wrap %x %y %b 8
        $self bind <Control-Button> click_wrap %x %y 3 8
      }
      default {
        $self bind <Button-3>       click_wrap %x %y %b 8
      }
    }
    switch $::OS { unix {
	$self bind <Button-4>       scroll y -1
    	$self bind <Button-5>       scroll y +1
	$self bind <Shift-Button-4> scroll x -1
    	$self bind <Shift-Button-5> scroll x +1
    } default {
	$self bind <MouseWheel>       scroll y %D
	$self bind <Shift-MouseWheel> scroll x %D
    }}
    $self bind <ButtonRelease>     	 unclick_wrap %x %y %b 0
    $self bind <Shift-ButtonRelease>     unclick_wrap %x %y %b 1

    # keyboard
    $self bind <Control-Key>       ctrlkey %x %y %K %A 0
    $self bind <Control-Shift-Key> ctrlkey %x %y %K %A 1
    $self bind <Alt-Key>           altkey  %x %y %K %A 0
    $self bind <Alt-Shift-Key>     altkey  %x %y %K %A 1
    switch $::OS {
      unix {
	$self bind <Mod1-Key>       altkey %x %y %K %A 0
	$self bind <Mod1-Shift-Key> altkey %x %y %K %A 1
	$self bind <Mod4-Key>       altkey %x %y %K %A 0
	$self bind <Mod4-Shift-Key> altkey %x %y %K %A 1
      }
      osx {
	$self bind <Mod1-Key>       ctrlkey %x %y %K %A 0
	$self bind <Mod1-Shift-Key> ctrlkey %x %y %K %A 1
      }
    }
    $self bind <Key>               key_wrap   %x %y %K %A 0
    $self bind <Shift-Key>         key_wrap   %x %y %K %A 1
    $self bind <KeyRelease>        keyup_wrap %x %y %K %A 0
    $self bind <Shift-KeyRelease>  keyup_wrap %x %y %K %A 1
    $self bind <Control-KeyRelease>  keyup_wrap %x %y %K %A 0
    $self bind <Motion>     motion_wrap %x %y 0
    $self bind <Alt-Motion> motion_wrap %x %y 4
    $self bind <Control-Motion> motion_wrap %x %y 9
    #$self bind <Map>        map
    #$self bind <Unmap>      unmap
    $self bind <Leave>      leave
    $self bind <Configure>  configure %h %w
}

def Canvas configure {h w} {
	if {[$self look gridstate]} {
		$@grid update $h $w
		if {[winfo exists .$self.yscroll]} {return}; # scrollbar will update grid already
		$@grid draw
	}
}

#def Canvas   map {} {}
#def Canvas unmap {} {}
def Canvas leave {} {$@crosshair erase}

def Canvas scroll {axis diff} {
	set c [$self widget]
	$c [list $axis]view scroll $diff units
	if {[$self look hairstate] && $@editmode} {
		# this should be changed so that we don't need to recompute x,y here.
		set x [expr [$c canvasx [expr [winfo pointerx $c] - [winfo rootx $c]]]/$@zoom]
		set y [expr [$c canvasy [expr [winfo pointery $c] - [winfo rooty $c]]]/$@zoom]
		set target [$self identify_target $x $y 0]
		$@crosshair data= $x $y $target
		$@crosshair draw
	} else {
		$@crosshair erase
	}
}

# this allows the grid to update when scroll
def Canvas scroll_set {w v1 v2} {if {[$self look gridstate] && $@editmode} {$@grid draw}; $w set $v1 $v2}

def Canvas reload {} {netsend [list .$self reupload]}
def Canvas redraw {} {
    $self changed
    foreach x [$@objects values] {$x changed}
    foreach x [  $@wires values] {$x changed}
}

#patch editing commandline shortcuts
def Canvas o {x y {name ""}} {
	set c [$self widget]
	if {[$self snap_grid]} {set off [expr [$self look grid_size]/2]} {set off 0}
	set @curpos [list [expr [$c canvasx $x]+$off] [expr [$c canvasy $y]+$off]]
	$self new_object obj $name
}

def Canvas c {from outlet to inlet} {
	set out_objs [$self parse_idx $from]
	set in_objs [$self parse_idx $to]
	foreach out $out_objs {
		foreach in $in_objs {
			$self connect [list $out $outlet $in $inlet]
		}
	}
}

def Canvas pc {from outlet to inlet} {
	set out_objs [$self parse_idx $from]
	set in_objs [$self parse_idx $to]
	if {[llength $out_objs] != [llength $in_objs]} {return "No can do :("}
	for {set i 0} {$i < [llength $out_objs]} {incr i} {
		$self connect [list [lindex $out_objs $i] $outlet [lindex $in_objs $i] $inlet]
	}
}

def Canvas s {selection} {
	set objs [$self parse_idx $selection]
	foreach obj $objs {$self selection+= [$@objects get $obj]}
}

def Canvas s+ {selection} {
	set objs [$self parse_idx $selection]; set ids {}
	foreach obj $objs {
		set v [$@objects get $obj]; lappend ids $v
		$self selection+= $v
	}
	$self selection_wire= [$self implicit_wires $ids]
}

def Canvas sw {from outlet to inlet} {
	set out_objs [$self parse_idx $from]
	set in_objs [$self parse_idx $to]
	foreach out $out_objs {
		foreach in $in_objs {
			set id [$self wire_idx [list $out $outlet $in $inlet]]
			if {$id>=0} {$self selection_wire+= [$@wires get $id]}
		}
	}
}

def Canvas parse_idx {val} {
	set objs {}
	foreach obj [split $val ","] {
		if {[regexp {\d+-\d+} $obj range]} {
			set l [split $range "-"]
			set objs [concat $objs [lmake [lindex $l 0] [lindex $l 1]]]
			continue
		}
		lappend objs $obj
	}
	return $objs
}

def Canvas xy_snap {x y} {
	if {[$self look snap_grid]} {
		set grid [$self look grid_size]
		set x [expr floor($x/$grid)*$grid]
		set y [expr floor($y/$grid)*$grid]
	}
	return [list $x $y]

}

def Canvas new_object {sel args} {
	$self editmode= 1
	$self deselect_all
	mset {x y} $@curpos
	if {[$self look snap_grid]} {
		set grid [$self look grid_size]
		set x [expr floor($x/$grid)*$grid]
		set y [expr floor($y/$grid)*$grid]
	}
	switch -- $sel {
		obj {    set goto [list $self new_object_edit]}
		msg {    set goto [list $self new_object_edit]}
		text {   set goto [list $self new_object_edit]}
		default {set goto [list $self new_object_callback]}
	}
	netsend [concat [list .$self $sel $x $y] $args] $goto
}

def Canvas new_wire_callback {wire} {}

def Canvas new_object_callback {obj} {
	$self add_to_obj_history $obj
	set @keynav_last_obj $obj
	$self selection+= $obj
	if {$@keynav} {$self update_Active $obj}
}

def Canvas new_object_copyselect {obj} {
	$self selection+= $obj
	#set @action "move"
	#$self click_on_object $obj 0
}

def Canvas new_wire_select {wire} {$self selection_wire+= $wire}

def Canvas new_object_edit     {obj} {
	if {[$obj class] == "NumBox"} {return}
	$obj edit
}

def Canvas add_to_obj_history {obj} {
	if {![[$obj class] <= ObjectBox]} {return}
	obj_hist prepend [$obj text]
}


def Canvas insertxy {} {return [list $@insert_x $@insert_y]}

def Canvas insert_object {} {$self do_insert_obj "none" "none"}

def Canvas get_canvas {} {
	if {![info exists @gop]} {
		if {[info exists @subpatch]} {if {$@subpatch} {return [$self canvas]}}
		if {[info exists @abs]} {if {$@abs} {return [$self canvas]}}
		if {[winfo exists .$self.c]} {return $self}
	}
	#if {[info exists @subpatch]} {if {$@subpatch} {return [$self canvas]}}
	return [super]
}

def Canvas get_topcanvas {} {
	set canvas $@canvas
	if {$@canvas == ""} {return $self}
	while {[$canvas canvas] != ""} {set canvas [$canvas canvas]}
	return $canvas
}

def Canvas do_insert_obj {x y} {
	if {$x == "none" && $y == "none"} {
		if {[$@wiresel size] != 1} {return}
		set wire [$@wiresel values]
		puts "insert object for wire $wire"
		mset {from outlet to inlet} [$wire report]
		set c [$self widget]
		set iowidth [$self look iowidth]
		mset {ox1 oy1 ox2 oy2} [lmap / [$c bbox ${from}o${outlet}] [$self zoom]]
		mset {ix1 iy1 ix2 iy2} [lmap / [$c bbox   ${to}i${inlet} ] [$self zoom]]
		set x1 [expr $ox1 + $iowidth/2]; set y1 [expr ($oy1+$oy2)/2]
		set x2 [expr $ix1 + $iowidth/2]; set y2 [expr ($iy1+$iy2)/2]
		set x [expr $x1 + ($x2-$x1)/2]
		set y [expr $y1 + ($y2-$y1)/2]
	}
	netsend [list .$self obj $x $y] [list $self new_object_edit]
	set @action insert
}

def Canvas new_object_insert_wire {obj} {
	set wire [$self selection_wire]
	$self selection_wire-= $wire
	mset {from outlet to inlet} [$wire report]
	$self disconnect [$wire connects]
	set @keynav 0; $@active hide; set @keynav_tab_sel "object"
	set from_idx [$@objects search $from]
	set   to_idx [$@objects search $to]
	set obj3_idx [$@objects search $obj]
	$self connect [list $from_idx $outlet $obj3_idx 0] [list $self keynav_current=]
	$self connect [list $obj3_idx 0 $to_idx $inlet]
	$self action= none
}

def Canvas chain_object {} {
	if {[$@objectsel size] != 1} {return}
	set o [$@objectsel values]
	mset {x1 y1 x2 y2} [$o bbox]
	set grid [$self look grid_size]
	if {[$self look snap_grid]} {set y [expr floor(($y2+$grid)/$grid)*$grid]} {set y [expr $y2+10]}
	netsend [list .$self obj $x1 $y] [list $self new_object_edit]
	set @action chain_obj
}
def Canvas new_object_chain_wire {obj} {
	obj_hist prepend [$obj text]
	set from_idx [$@objects search [lindex [$self selection] 0]]
	$self deselect_all
	set to_idx [$@objects search $obj]
	$self connect [list $from_idx 0 $to_idx 0]
	$self action= none
	$self selection= $obj
}

def Canvas objects {} {return $@objects}
#def Canvas wires {} {return $@wires}
def Canvas selection        {} {$@objectsel values}
def Canvas selection=       {objs} {$@objectsel clear; $self selection+= $objs}
def Canvas selection+=      {objs} {foreach obj $objs {$@objectsel set [$obj index] $obj}}
def Canvas selection-=      {objs} {foreach obj $objs {set k [$obj index]; if {[$@objectsel exists $k]} {$@objectsel unset $k}}}
def Canvas selection_wire   {} {$@wiresel values}
def Canvas selection_wire=  {objs} {$@wiresel clear; $self selection_wire+= $objs}
def Canvas selection_wire+= {objs} {foreach obj $objs {$@wiresel set [$obj index] $obj}}
def Canvas selection_wire-= {objs} {foreach obj $objs {set k [$obj index]; if {[  $@wiresel exists $k]} {  $@wiresel unset $k}}}

def Canvas Object  {} {$self new_object obj}
def Canvas Message {} {$self new_object msg}
def Canvas Number  {} {$self new_object floatatom}
def Canvas Symbol  {} {$self new_object symbolatom}
def Canvas Comment {} {$self new_object text}

#!@#$ these 9 should be harmonised with the class list instead of having special names
def Canvas bng     {} {$self new_object obj bng}
def Canvas tgl     {} {$self new_object obj tgl}
def Canvas nbx     {} {$self new_object obj nbx}
def Canvas vsl     {} {$self new_object obj vsl}
def Canvas hsl     {} {$self new_object obj hsl}
def Canvas vradio  {} {$self new_object obj vradio}
def Canvas hradio  {} {$self new_object obj hradio}
def Canvas vu      {} {$self new_object obj vu}
def Canvas cnv     {} {$self new_object obj cnv}

def Canvas Graph   {} {$self editmode= 1; netsend [list .x$self graph    ]}
def Canvas Array   {} {$self editmode= 1; netsend [list .x$self menuarray]}

def Canvas init_menus {} {
    set name .$self
    set m $name.m
    menu $m
    #removed Paths after send_message
    foreach x {file edit find view put window help} {menu $m.$x -tearoff $::pd_tearoff}
    $self populate_menu file {new_file open_file {} send_message {} close save save_as print {} abort_server quit}
    $self populate_menu edit {undo redo {} cut copy paste duplicate select_all subpatcherize {} tidy_up {}}
    $self populate_menu find {find find_again find_last_error}
    #!@#$ those shortcuts should be looked up in the key table!!!
    $m.view add checkbutton -label [say visual_diff] -selectcolor grey0 -command [list $self visual_diff] \
    	-accelerator [accel_munge "Ctrl+E"] -indicatoron 1
    $self populate_menu view {get_elapsed {} reload redraw}
    $self populate_menu put {Object Message Number Symbol Comment {} bng tgl nbx vsl hsl vradio hradio vu cnv {} Graph Array}
    $self populate_menu window {{}}
    $m.edit add checkbutton -label [say edit_mode] -selectcolor grey0 -command [list $self editmodeswitch] \
    	-accelerator [accel_munge "Ctrl+e"] -indicatoron 1
    $m.edit   configure -postcommand "$self fix_edit_menu"
    $m.window configure -postcommand "$self fix_window_menu"
    foreach x {file edit view find put window help} {
      if {$x=="help"} {
	# help menu is in the wrong place in patch windows because it's taken from .mbar ???
	$m add cascade -label [say $x] -menu .mbar.$x
      } {
	$m add cascade -label [say $x] -menu $m.$x
      }
    }
}

# corrects edit menu, enabling or disabling undo/redo
# LATER also cut/copy/paste
def Canvas fix_edit_menu {} {
    set e .$self.m.edit
    switch $::OS {osx {set i 0} default {set i 1}}
    set t [say undo]
    if {[$@history can_undo?]} {
	$e entryconfigure $i -state normal   -label "$t [$@history next_undo_name]"
    } else {
	$e entryconfigure $i -state disabled -label "[say cannot] $t"
    }
    incr i
    set t [say redo]
    if {[$@history can_redo?]} {
	$e entryconfigure $i -state normal   -label "$t [$@history next_redo_name]"
    } else {
	$e entryconfigure $i -state disabled -label "[say cannot] $t"
    }
}

def Menuable fix_window_menu {} {
    set menu $@menubar.window
    $menu delete 0 end
    foreach w $::window_list {$menu add command -label [wm title [$w window]] -command "$w raise"}
#   {"parentwindow" {menu_windowparent} ""}
}

#-----------------------------------------------------------------------------------#
#this just tells whether an object is part of the selection, that is, what usually makes objects turn blue.
def Canvas selection_include? {member} {expr [$@objectsel search $member]>=0}

def Canvas type {} {
	if {$@subpatch && !$@gop} {return "sub"}
	if {$@subpatch && $@gop} {return "gopsub"}
	if {$@abs && !$@gop} {return "abs"}
	if {$@abs && $@gop} {return "gopabs"}
	if {!$@subpatch && !$@abs && !$@gop} {return "toplevel"}
}

def Canvas draw {} {
	if {$@gop} {
		if {[$self gop_check]} {$self all_changed}
		#if the focus is not in the opened gop
		if {[regexp {^.x[0-9a-f]{6,8}.c} [focus] f]} {
			if {$@canvas != ""&& $f != [$self widget]} {super;return}
		} elseif {$@canvas != "" && [focus] != [$self widget]} {super;return}
	} elseif {$@subpatch || $@abs} {super}
	if {!$@mapped} {return} else {if {![winfo exists [$self widget]]} {return}}
	$self check_findbar
	if {$@editmode} {set bg [$self look bgedit]} else {set bg [$self look bgrun]}
	[$self widget] configure -background $bg
	$self adjust_scrollbars
	if {$@gop} {$self gop_rect}
}

def Canvas popup_properties {} {CanvasPropertiesDialogue new $self}

def Canvas gop_target {id} {
	while {[$id canvas] != $self} {set id [$id canvas]}; return $id
}

#-----------------------------------------------------------------------------------#
class_new MacroRect {View}

def MacroRect init {} {super; $self data= 0 0 0 0 blue}

def MacroRect data= {x1 y1 x2 y2 col} {
	set @x1 $x1; set @y1 $y1
	set @x2 $x2; set @y2 $y2
	set @col $col
}

def MacroRect flash {x1 y1 x2 y2 col} {
	$self data= $x1 $y1 $x2 $y2 $col
	$self draw
	after 500 $self erase
}

def MacroRect draw {} {
	set @canvas [string range [focus] 1 [string first . [focus] 1]-1]
	$self item MACRO rect [list $@x1 $@y1 $@x2 $@y2] -outline $@col
}

class_new Macro {EventHistory}
def Macro init {} {
	$::event_history subscribe $self
	set @idx 0
	set @state 0
	set @list {}
	set @ref_list {}
	set @delay 200
	set @offset_x 0
	set @offset_y 0
	set @rect [MacroRect new]
}
def Macro state= {val} {
	set @state $val
	if {$val} {
		set @ref_list {}
		set @list {}
		post %s "start recording macro..."
	} else {
		post %s "end..."
	}
}
def Macro state {} {return $@state}
def Macro dump {} {set i 0; foreach step $@list {post %s "step $i -> $step"; incr i}}
def Macro idx= {val} {set @idx $val}
def Macro idx {} {return $@idx}
def Macro delay {} {return $@delay}
def Macro append_ref {mess} {lappend @ref_list $mess}
def Macro ref_list {} {puts "$@ref_list";return $@ref_list}
def Canvas ref_list {} {$::macro ref_list}


def Macro notice {args} {
	if {$@state} {
		set mess [lindex $args 2]
		switch [lindex $args 1] {
			add {$self add $mess}
			default {}
		}
	}
}

def Macro add {mess} {
	mset {event widget x y mode k kval} $mess
	if {[regexp {^Control_} $k]} {return}
	if {[regexp {^Alt_} $k]} {return}
	if {[regexp {^Shift_} $k]} {return}
	if {$event == "KeyRelease"} {return}
	if {$event == "ButtonRelease"} {lappend @list [lreplace $mess 0 0 "Motion"]}
	if {$event == "KeyPress"     } {lappend @list [lreplace $mess 0 0 "Motion"]}
	if {$event == "ButtonPress"  } {lappend @list [lreplace $mess 0 0 "Motion"]}
	lappend @list $mess
}

def Macro test_playable {x y} {
	mset {macro_width macro_height} [$self calc_size]
	set c [string range [focus] 0 [string first . [focus] 1]+1]
	set cwidth [winfo width $c]; set cheight [winfo height $c]
	set cx1 [$c canvasx 0]; set cy1 [$c canvasy 0]
	set cx2 [$c canvasx $cwidth]; set cy2 [$c canvasy $cheight]
	set new_x [expr $x+$macro_width]; set new_y [expr $y+$macro_height]
	$@rect flash $x $y $new_x $new_y blue
	if {$new_x > $cx2 || $new_x < $cx1 || $new_y > $cy2 || $new_y < $cy1} {
		puts "Can't playback Macro:: outside of canvas area"
		return
	} else {$self idx= 0; $self offset $x $y; $self play [$self delay]}

}

def Macro offset {new_x new_y} {
	mset {event widget x y mode k kval} [lindex $@list 0]
	set @offset_x [expr $new_x-$x]
	set @offset_y [expr $new_y-$y]
}

def Macro play {delay} {
	if {$@idx == [llength $@list]} {return}
	#$self test_canvas_size
	set i 0
	set focus [string range [focus] 1 [string first . [focus] 2]-1]
	set step [lindex $@list $@idx]
	#puts "\t $step"
	mset {event widget x y mode k kval} $step
	switch $event {
		#KeyRelease {set name [modekey $k $mode]}
		KeyPress {set name [modekey $k $mode]}
		ButtonPress {set name $event-$k}
		ButtonRelease {set name $event-$k}
		Motion {set name $event}
		default {puts "Error: event $event should not have been here.."}
	}
	if {$@idx < [llength $@list]} {
		#after $delay [list $self run $event $name $k [expr $X+$@offset_x] [expr $Y+$@offset_y] \
		#		  [expr $x+$@offset_x] [expr $y+$@offset_y]]
		after $delay [list $self run $event $name $k $x $y]
	}
}

#this don't make sense when multiple window, and should be re implemeneted somehow
def Macro calc_size {} {
	set x1 66666; set x2 0
	set y1 66666; set y2 0
	foreach step $@list {
		mset {event widget x y mode k kval} $step
		set x1 [min $X $x1]; set x2 [max $x2 $X]
		set y1 [min $Y $y1]; set y2 [max $y2 $Y]
		
	}
	return [list [expr $x2-$x1] [expr $y2-$y1]]
}

def Macro test_canvas_size {} {
	set c [string range [focus] 0 [string first . [focus] 1]+1]
	set cwidth [winfo width $c]; set cheight [winfo height $c]
	set cx1 [$c canvasx 0]; set cy1 [$c canvasy 0]
	set cx2 [$c canvasx $cwidth]; set cy2 [$c canvasy $cheight]
	mset {x1 y1 x2 y2} [$self calc_size]
}

def Macro run {event name k x y} {
	set w [focus]
	incr @idx
	event generate $w <$name> -x $x -y $y
	if {$event=="KeyPress"} {event generate $w <KeyRelease-$k> -x $x -y $y}
	$self play $@delay
}

def Macro copy {} {
	clipboard clear
	selection clear
	set i 0
	foreach step $@list {
		if {$i == [expr [llength $@list]-1]} {set comma ""} else {set comma ","}
		mset {event widget x y mode k kval} $step
		set mess [list $event $x $y $mode $k]
		set t ${mess}$comma
		switch $event {
			KeyPress {clipboard append [lreplace $t 0 0 "key"]}
			ButtonPress {clipboard append [lreplace $t 0 0 "click"]}
			ButtonRelease {clipboard append [lreplace $t 0 0 "unclick"]}
		}
		incr i
	}
	selection handle -selection PRIMARY [focus] "$self getdata"
	selection own -command lost -selection PRIMARY
}

def Macro getdata {offset maxchar} {
	puts "clipboard ::: [clipboard get]"
	return [string range [format %s [clipboard get]] $offset [expr {$offset+$maxChars}]]
}

def Macro reset {} {set @idx 0}

set ::macro [Macro new]
set ::macro_state 0

def Canvas macro_toggle {} {if {![$::macro state]} {set ::macro_state 1} {$::macro state= 0; $::macro dump}}
#def Canvas macro_play {} {puts ">> $@click_at <<"; $::macro idx= 0; $::macro play [$::macro delay]}
def Canvas macro_play {} {
	mset {x y} $@click_at
	$::macro reset 
	$::macro play [$::macro delay]
	#$::macro test_playable $x $y
}
def Canvas keyevent {} {
	focus [$self widget]
	event generate [$self widget] <Control-Key-1>
}

def Canvas macro_copy {} {$::macro copy}

#-----------------------------------------------------------------------------------#
class_new TextBox {Box}

def TextBox init {mess} {
	super $mess
	set @edit 0
	set @x1 [lindex $mess 2]
	set @y1 [lindex $mess 3]
	set @text [$self remove_braces [join [lrange $mess 4 end]]]
	set @multi 0
	set @max_width 40
	# @textoffset is for offseting the text item/widget, ie, ObjectBox vs NumBox
	switch [$self class] {
		NumBox {set @textoffset [list 10 2]}
		default {set @textoffset [list 2 2]}
	}
}

def TextBox text= {text} {set @text [$self remove_braces [join $text]]}
def TextBox text {} {return $@text}
def TextBox filter_text {{for_edit 0}} {return $@text}

def TextBox draw {} {
	if {[$self class] == "Canvas"} {if {$@text == "graph"} {$self update_size; super; return}}
	# "TEXT" is the text label while "text" is the the input text field tk widget.
	# the text should be drawn before, so that update_size works at the right time.
	mset {x1 y1} [$self xy]
	set z [$@canvas zoom]
	if {$@edit} {
		$self draw_edit
	} else {
		set fw [font measure [$self look font] 0]
		set text [$self filter_text]
		if {$::leet} {set text [string map -nocase {a 4 e 3 t 7 s 5 i 1 o 0 g 9} $text]}
		$self item TEXT text [l+ $@textoffset [$self xy]] \
		    -font [$self look  font] -text $text \
		    -fill [$self look fg] -anchor nw -width [expr ($fw*$@max_width)-1]
		# set width with -1 because text item seem to be inclusive at wrap point
		# where as the text widget is exclusive
	}
	$self update_size
	super
}

def TextBox edit {} {
	if {$@edit} {return}; set @edit 1; $self changed edit
	if {[[$self class] <= AtomBox]} {set @clear 1}
}

def TextBox new_bind {} {
	set t [$self cwidget].${self}text
	bind $t <Key>            "$self key_input %W %x %y %K %A 0"
	bind $t <Control-Return> "$self key_input %W %x %y 10 %A 0"
	bind $t <Control-v>      "$self paste_resize"
	bind $t <Return>         "$self unedit"
	bind $t <Escape>         "$self unedit 0"
	bind $t <Up> "$self scroll_history +1"
	bind $t <Down> "$self scroll_history -1"
	bind $t <Control-p> "$self scroll_history +1"
	bind $t <Control-n> "$self scroll_history -1"	
	bind $t <Alt-BackSpace> "$self clear"
}

def TextBox draw_edit {} {
	set c [$self cwidget]
	switch $::tcl_platform(os) {
		Linux {$c configure -cursor crosshair}
		#need to find equivalent names for other os
	}
	if {[lsearch [$@canvas selection] $self] < 0} {$@canvas selection+= $self}
	set t $c.${self}text
	if {[winfo exists $t]} {return}
	set @edit 1
	set @tab_repeats 0
	obj_hist histi= 0
	set @selected? 1
	set z [$@canvas zoom]
	set font_height [font metrics [$self look font] -linespace]
	if {[$c bbox ${self}TEXT] != ""} {
		mset {ix1 iy1 ix2 iy2} [lmap / [$c bbox ${self}TEXT] $z]
		if {($iy2-$iy1)/$z > $font_height} {set @multi 1}
	} else {
		set ix1 0; set iy1 0
		set ix2 [font measure [$self look font] 0]
		set iy2 [font metrics [$self look font] -linespace]
	}
	$c delete ${self}TEXT
	set font_str [$self look font]
	set new_size [format %.0f [expr [lindex $font_str 1]*$z]]
	set font_str [lreplace $font_str 1 1 $new_size]
	foreach char [split $@text ""] {lappend l [scan $char %c]}
	mset {width height} [$self get_size [expr $ix2-$ix1] [expr $iy2-$iy1]]
	set insertbg [$self look fg]; set fg [$self look fg]
	if {[[$self class] <= AtomBox]} {set fg "red"; set insertbg [$self look bgedit]}
	text $t -width $width -height $height -relief flat -bg [$self look bgedit] -borderwidth 0 \
		-highlightthickness 0 -font $font_str -fg $fg -insertbackground $insertbg -wrap word
	$self new_bind
	$@canvas focus= $self
	$self item text window [l+ [lmap / $@textoffset $z] [$self xy]] -window $t -anchor nw -tags "${self}text $self text"
	$t insert 1.0 $@text
	$t configure -pady 0 -padx 0
	# dnd bindtarget $c.${self}text text/uri-list <Drop> {}
	# tkdnd::drop_target register $c.${self}text
	#set w .[$self get_canvas]
	#tkdnd::drop_target register $w *
	#bind $w <<Drop>>           {post %s "Drop           %e %W %X %Y %ST %TT %a %A %CST %CTT %t %T %b %D"}
	#bind $w <<Drop:DND_Files>> {post %s "Drop:DND_Files %e %W %X %Y %ST %TT %a %A %CST %CTT %t %T %b %D"}
	#bind $w <<DropEnter>>      {post %s "DropEnter      %e %W %X %Y %ST %TT %a %A %CST %CTT %t %T %b %D"}
	#bind $w <<DropPosition>>   {post %s "DropPosition   %e %W %X %Y %ST %TT %a %A %CST %CTT %t %T %b %D"}
	#bind $w <<DropLeave>>      {post %s "DropLeave      %e %W %X %Y %ST %TT %a %A %CST %CTT %t %T %b %D"}

	# "pd \"x[list ${self}] symbol \[ enquote %D \] ;\""
	$self resize
	focus $t
}

def TextBox resize {} {
	if {[[$self class] <= AtomBox]} {return}
	set c [$self cwidget]
	set t $c.${self}text
	#set z [$@canvas zoom]
	set pix_height [$t count -update -ypixels 1.0 end]
	set pix_width [font measure [$self look font] [$t get 1.0 end]]
	mset {width height} [$self get_size $pix_width $pix_height]
	$t configure -width [min $width $@max_width] -height $height -wrap word
}

def TextBox get_size {w h} {
	set c [$self cwidget]
	set t $c.${self}text
	set pix_height $h
	set pix_width $w
	set char_width [font measure [$self look font] 0]
	set line_height [font metrics [$self look font] -linespace]
	set round_chars [expr int(ceil($pix_width/$char_width.0))]
	if {$round_chars < $@max_width && !$@multi} {
		set round_lines 1
	} else {
		set @multi 1
		set round_chars $@max_width
		set round_lines [expr int(ceil($pix_height/$line_height))]
	}
	return [list $round_chars $round_lines]
}

def TextBox key_input {widget x y key iso shift} {
	after 0 "$self after_key $widget"
	set c [$@canvas widget]
	set t $c.${self}text
	if {[[$self class] <= AtomBox]} {if {$@clear} {$t delete 1.0 1.end; set @clear 0}}
	switch -- $key {
		Tab {
			if {[$self class] == "ObjectBox"} {
				$self propose_completions; $widget configure -state disabled
			}
		}
		10 {$t configure -height [expr [lindex [$t configure -height] 4] + 1]}
	}
}

def TextBox after_key {widget} {
	$widget configure -state normal; # for in case there is completion box
	$self resize
	$self changed
}

def TextBox paste_resize {} {
	if {[[$self class] <= AtomBox]} {return}
	set c [$self cwidget]
	set t $c.${self}text
	set fixed [font metrics [$self look font] -fixed]
	set text [clipboard get]
	if {$fixed} {
		set width [string length $text]
	} else {
		set textpix [font measure [$self look font] $text]
		set fwidth [font measure [$self look font] 0]
		set width [expr (($textpix+$fwidth-1)/$fwidth)+1]
	}
	set maxwidth 40
        mset {y1 y2} [$t yview]
        if {$width < $maxwidth} {set height 1} {set height [expr ceil($width/$maxwidth.0)]}
	$t configure -width [min $width $maxwidth] -height $height -wrap word
	after 0 [list $t mark set insert 1.end]
	$t mark set insert 1.0
	$self update_size
	$self changed
}

def TextBox scroll_history {incr} {
	if {[[$self class] <= MessageBox]} {return}
	set c [$self cwidget]
	set t $c.${self}text
	if {![obj_hist histi]} {obj_hist set_hist 0 [$t get 1.0 1.end]}
	$t delete 1.0 1.end
	set text [obj_hist traverse $incr]
	$t insert 1.0 $text
	$t configure -width [string length $text]
	$self update_size
	after 0 $self changed
	
}

def TextBox clear {} {
	set c [$self cwidget]
	set t $c.${self}text
	$t delete 1.0 1.end
}

def TextBox text {} {return $@text}

def TextBox update_size {} {
	if {[info exists @gop]} {if {$@gop} {mset [list @xs @ys] $@pixsize; return}}
	if {$@canvas == ""} {puts "update_size: this textbox has no canvas, try again later"; return}
	set c [$self cwidget]
	set t_widget $c.${self}text
	set t_item $c.${self}TEXT
	set w2 0; set h2 0
	set xpad 2; set ypad 3
	set z [$@canvas zoom]
	if {[winfo exists $t_widget]} {
		set textwidth [expr ([winfo reqwidth $t_widget]+$xpad)/$z]
		set height [expr ([winfo reqheight $t_widget]+$ypad)/$z]
	} else {
		mset {x1 y1 x2 y2} [[[$self canvas] widget] bbox ${self}TEXT]
		set textwidth [expr ($x2-$x1+$xpad)/$z]
		set height [expr ($y2-$y1+$ypad)/$z]
	}
	set iowidth [$self look iowidth]
	set topwidth    [expr {(2* $@ninlets-1)*$iowidth}]
	set bottomwidth [expr {(2*$@noutlets-1)*$iowidth}]
	set @xs [max [$self look minobjwidth] [max $bottomwidth [max $topwidth $textwidth]]]
	set @ys $height
}

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

class_new ObjectBox {TextBox}

def ObjectBox init {mess} {
	super $mess
	set @valid 0 ;# only ObjectBox needs a @valid. (removed all others)
	set @ninlets 0
	set @noutlets 0
	set @pdclass ""
}

def ObjectBox valid= {v}    {set @valid $v}
def ObjectBox valid  {} {return $@valid}
def Canvas objectsel {} {return $@objectsel}
def Canvas   wiresel {} {return   $@wiresel}

def ObjectBox draw_box {} {
	super
	set xya [$self bbox]
	mset {x1 y1 x2 y2} $xya
	# the 3d look could be done quicker using incr on x1 y1 x2 y2 like BlueBox does.
	#set xyb [l+ [list $x2 $y1 $x1 $y1 $x1 $y2] [list -1 +1 +1 +1 +1 -1]]
	#set xyc [l+ [list $x2 $y1 $x2 $y2 $x1 $y2] [list -1 +1 -1 -1 +1 -1]]
        if {[$self selected?]} {set fg [$self look selectframe]} {set fg [$self look frame3]}
	if {$@valid} {
		$self item BASE rectangle $xya -fill [$self look bg] -outline $fg -width 1
	} else {
		$self item BASE rectangle $xya -fill [$self look bg] -outline $fg -width 1 -dash {3 3}
	}
	#$self item BASE1 line      $xyb -fill [$self look frame1] -width 1
	#$self item BASE2 line      $xyc -fill [$self look frame2] -width 1
	#[$@canvas widget] lower ${self}BASE ${self}TEXT
	[[$self get_canvas] widget] lower ${self}BASE ${self}TEXT
	#[[$self get_canvas] widget] lower ${self}BASE
}

def ObjectBox draw {} {super; $self draw_io}

# this is called from the GUI; text= is reserved for server.
def TextBox setto {text} {
	[$@canvas history] add [list $self setto $@text]
	[$@canvas widget] configure -cursor {}
	set @text $text
	set l {}
	foreach char [split $@text ""] {lappend l [scan $char %c]}
	$@canvas selection-= [list $self]
	switch [$@canvas action] {
		insert        {set goto [list $@canvas new_object_insert_wire]}
		chain_obj     {set goto [list $@canvas new_object_chain_wire]}
		subpatcherize {set goto [list $@canvas new_object_subpatcherize_redraw]}
		default       {set goto [list $@canvas new_object_callback]}
	}
	netsend [concat [list .$@canvas text_setto $self] $l] $goto
}

def TextBox unedit {{accept 1}} {
	if {!$@edit} {return}
	set @edit 0
	$self changed edit
	set c [[$self get_canvas] widget]
	set t $c.${self}text
	if {$accept} {$self setto [$t get 1.0 "end - 1 chars"]}
	after 1 "destroy $t"
	if {[winfo exists .completion]} {$@action cancel}
	focus $c
	$@canvas focus= ""
}

#-----------------------------------------------------------------------------------#
def Canvas name {} {return $@name}
def Canvas folder {} {return $@folder}
def Canvas name=   {name}    {if {!$@mapped} {return}; set @name   $name  ; $self update_title}
def Canvas folder= {folder}  {if {!$@mapped} {return}; set @folder $folder; $self update_title}

def Canvas make_title {} {
	if {!$@mapped} {return}
	if {$@subpatch} {
		if {$@canvas == "" || 0==[string compare $@canvas $self]} {
			set t "(uh)"
		} else {
			set t [$@canvas make_title]
		}
		set t "subpatch '$@name' of $t"
	} else {
		#set t "$@name in $@folder"
		set t "$@name"
	}
	if {[$self modified?]} {append t "(*)"}
	return $t
}

def Canvas update_title {} {
	if {[winfo exists .$self]} {wm title .$self [$self make_title]}
}

# UNIMPLEMENTED: this should indicate whether the patch in pd is different from the last saved patch
def Canvas modified? {} {return 1}

def Canvas mapped {} {return $@mapped}
def Canvas mapped= {v} {set @mapped $v}

def Canvas havewindow= {flag} {
	set was [winfo exists .$self]
	if {$flag && !$was} {$self init_window; $self redraw}
	#if {$flag && $was && [$self gop]} {$self redraw}
	if {$flag && $was} {$self raise}
	if {!$flag && $was} {$self delete_window}
}

def Canvas visibles+= {child} {
	if {[lsearch $@visibles $child]  < 0} {lappend @visibles $child; $self changed visibles}
}
def Canvas visibles-= {child} {
	if {[lsearch $@visibles $child] >= 0} {set @visibles [lwithout $@visibles $child]; $self changed visibles}
}

def Canvas visibles {} {return $@visibles}

def Canvas all_changed {} {
	foreach x $@visibles {
		if {[$x class] == "Canvas"} {if {$@gop} {$x all_changed}}
		$x changed
	}
}

# this is a shim between the client's new-style indices and the server's remaining old-style indices
proc dex {h k} {lsearch [lsort -integer [$h keys]] $k}

# for undo; calls the server
def Canvas ins {i constructor} {
	set parts [pd_mess_split $constructor]
	set last [lindex $parts end]
	set parts [lrange $parts 0 end-1]
	foreach part $parts {netsend $part}
	netsend [concat [list .$self object_insert $i] $last]
	$@history add [list $self del $i]
}

def Canvas del {i} {
	set o [$@objects get $i]
	#this keynav should be better sorted out
	if {$o == $@keynav_current || $o == $@keynav_last_obj} {
		set @keynav_current 0
		set @keynav_last_obj 0
	}
	# this "if" might not be necessary... try deconstruct_to for everything.
	if {[$o class] != "Canvas"} {
		$@history add [list $self ins $i [$o deconstruct]]
	} else {
		set meuh [Clipboard2 new]
		$o deconstruct_to $meuh
		$@history add [list $self ins $i [$meuh value]]
		$meuh delete
	}
	netsend [list .$self object_delete $o]
}

def Canvas wires {} {return $@wires}

def Canvas delete_selection {} {
    $@history atomically [list delete_selection] {
	if {![$@objectsel size] && ![$@wiresel size]} {return}
	#this keynav should be better sorted out
	if {$@keynav} {
		set @keynav 0
		switch [$@keynav_current class] {
			Wire    {set @keynav_last_wire 0}
			default {set @keynav_last_obj  0}
		}
		set @keynav_current 0
		$@active hide
	}
	set del_wire {}
	foreach obj [$@objectsel values] {
		foreach wire [$obj wires2] {
			if {[$@wires search $wire] != -1 && [lsearch $del_wire $wire] < 0} {
				$self disconnect [$wire connects]
				lappend del_wire $wire
			}
		}
		$self del [$@objects search $obj]
	}
	foreach x [$@wiresel values] {
		if {[$@wires search $x] != -1 && [lsearch $del_wire $x] < 0} {
			$self disconnect [$x connects]
		}
	}
	$@objectsel clear
	$@wiresel   clear
    }
}

def View position= {xy1} {mset [list @x1 @y1] $xy1; $self changed x1 y1}
def View set_orig_xy {x y} {set @orig_x $x; set @orig_y $y}

def Canvas motion_wrap {x y f} {
	set c [$self widget]
	set x [expr [$c canvasx $x]/$@zoom]
	set y [expr [$c canvasy $y]/$@zoom]
	lappend @motion_queue [list $x $y $f]
	#$self motion $x $y $f [$self identify_target $x $y $f]
}

def Canvas motion_update {} {
	if {[llength $@motion_queue]} {
		mset {x y f} [lindex $@motion_queue end]; set @motion_queue {}
		$self motion $x $y $f [$self identify_target $x $y $f]
	}
	set @motion_after_id [after 50 "$self motion_update"]
}

def Canvas   click_wrap {x y b f} {
	set c [$self widget]
	set x [expr [$c canvasx $x]/$@zoom]
	set y [expr [$c canvasy $y]/$@zoom]
	set f [expr 1<<($b+7)|$f]
	$self click   $x $y $f [$self identify_target $x $y $f]
}
def Canvas unclick_wrap {x y b f} {
	set c [$self widget]
	set x [expr [$c canvasx $x]/$@zoom]
	set y [expr [$c canvasy $y]/$@zoom]
	set f [expr 1<<($b+7)|$f]
	$self unclick $x $y $f [$self identify_target $x $y $f]
}
def Canvas   key_wrap {x y key iso shift} {
	set c [$self widget]
	$self key   [expr [$c canvasx $x]/$@zoom] [expr [$c canvasy $y]/$@zoom] $key $iso $shift
}
def Canvas keyup_wrap {x y key iso shift} {
	set c [$self widget]
	$self keyup [expr [$c canvasx $x]/$@zoom] [expr [$c canvasy $y]/$@zoom] $key $iso $shift
}

proc lsearch_minimum {l} {
	set i 0
	set j 0
	set min [lindex $l 0]
	foreach o $l {
		if {$o < $min} {set i $j; set min $o}
		incr j
	}
	return $i
}

def Canvas quadrant {du dv array} {
	if {$@keynav_current == 0} {set @keynav_current [$@objects get [lindex [$@objects keys] 0]]}
	set foo {}
	set bar {}
	set pos [$@keynav_current xy]
	foreach o $array {
		mset {x y} [l- $pos [$o xy]]
		set u [expr $x+$y]
		set v [expr $x-$y]
		if {$u*$du>0 && $v*$dv>0} {lappend foo $o; lappend bar [distance $pos [$o xy]]}
	}
	if {![llength $bar]} {return $@keynav_current}
	set best [lindex $foo [lsearch_minimum $bar]]
	return $best
}

def Canvas motion {x y f target} {
	#modes_callback $self "motion" $x $y $f $target
	set c [$self widget]
	$self motion_checkhairtip $target $x $y
	eval $@dehighlight
	set @dehighlight {}
	set oldpos $@curpos
	set @curpos [list $x $y]
	# detects if the focus is not on the canvas itself in run mode, ie. numbox
	if {!$@editmode & [$self focus] != $self & [$self focus] != ""} {
		[$self focus] motion $x $y $f $target
	}
	mset {type id detail} $target
	switch $@action {
		edit {$self motion_edit $x $y $f}
		insert {}
		chain_obj {}
		imove      {$self motion_imove $oldpos $x $y; return}
		mouse_copy {$self motion_move  $oldpos $x $y; return}
		move       {$self motion_move  $oldpos $x $y; return}
		none {}
		default {$@action motion $x $y $f $target}
	}
	if {$@editmode} {$self motion_iohilite2 $x $y $f}
	if {$id == ""} {return}
}

proc remainder {val val2} {
	if {[expr {abs($val)}]} {return [expr {(abs($val)%$val2)*($val/abs($val))}]} else {return 0}
}

def Canvas motion_move {oldpos x y} {
	mset {ox oy} $oldpos
	if {$@keynav} {$@active draw}
	foreach obj [$@objectsel values] {
		#if {[[$obj class] <= Box]} {
			if {[$self look snap_grid]} {
				set grid [$self look grid_size]
				set ax  [expr {(int($x)/$grid)*$grid}]
				set ay  [expr {(int($y)/$grid)*$grid}]
				set oax [expr {(int($ox)/$grid)*$grid}]
				set oay [expr {(int($oy)/$grid)*$grid}]
				mset {x1 y1} [$obj xy]
				if {![expr {($ax-$oax)%$grid}]} {
					set xoff [remainder [expr {int($x1-$ax)}] $grid]
				} else {set xoff 0}
				if {![expr {($ay-$oay)%$grid}]} {
					set yoff [remainder [expr {int($y1-$ay)}] $grid]
				} else {set yoff 0}
				$obj move [expr ($ax-$oax)-$xoff] [expr ($ay-$oay)-$yoff]
			} else {
				$obj move [expr {$x-$ox}] [expr {$y-$oy}]
			}
		#} else {
		#	puts "Canvas motion warning: trying to move non-Box explicitly"
		#}
	}
}

def Canvas motion_imove {oldpos x y} {
	mset {ox oy} $oldpos
	if {$@keynav} {$@active draw}
	if {[$@objectsel size] == 1} {
		set obj [$@objectsel values]
		set  in_objs $obj
		set out_objs $obj
	} else {
		if {![llength $@keynav_iosel_i] || ![llength $@keynav_iosel_o]} {
			return
		} else {
			set obj [lindex $@keynav_iosel_i 0]
			set  in_objs $@keynav_iosel_i
			set out_objs $@keynav_iosel_o
		}
	}
	if {[[$obj class] <= Box]} {
		if {[$obj class] == "Canvas"} {
			if {[$obj gop]} {[$self widget] raise [list $obj [$obj visibles]]}
		} else {
			[$self widget] raise $obj
		}
		mset {type id detail} [$self identify_target $x $y 0]
		if {$type == "wire"} {
			mset {from outlet to inlet} [$id report]
			$self disconnect [$id connects]
			set from_idx [$@objects search $from]
			set   to_idx [$@objects search $to]
			foreach obj $in_objs {
				set obj3_idx [$@objects search $obj]
				if {![llength [$obj ioselect]]} {set port 0} else {set port [lindex [$obj ioselect] 0]}
				set w1 [list $from_idx $outlet $obj3_idx $port]
				if {[$@wires search $w1]<0} {$self connect $w1}
			}
			foreach obj $out_objs {
				set obj3_idx [$@objects search $obj]
				if {![llength [$obj ioselect]]} {set port 0} else {set port [lindex [$obj ioselect] 0]}
				set w2 [list $obj3_idx $port $to_idx $inlet]
				if {[$@wires search $w2]<0} {$self connect $w2}
			}
			set @action move
		}
		foreach obj [$@objectsel values] {$obj move [expr $x-$ox] [expr $y-$oy]}
	} else {
		puts "Canvas motion warning: trying to move non-Box explicitly"
	}
}

def Canvas motion_edit {x y f} {
	if {[distance [list $x $y] $@click_at] > 5} {
		foreach obj [$@objectsel values] {
			if {[[$obj class] <= Box]} {
				$obj backupxy= [$obj xy]
			} else {
				puts "Canvas motion warning: trying to backup coordinates of non-Box"
			}
		}
		if {$f == 9} {set @action imove} else {set @action move; $self motion_move $@click_at $x $y}
		mset {ox oy} $@click_at
	}
}

def Canvas motion_checkhairtip {target x y} {
	global tooltip
	if {[$self look hairstate] && $@editmode} {
		$@crosshair data= $x $y $target
		$@crosshair draw
	} else {
		$@crosshair erase
	}
	if {[$self look tooltip]} {
		if {$tooltip ne "" && ![$tooltip iserror] && [expr [distance [$tooltip curpos] [list $x $y]] > 10]} {
			$tooltip delete
			set tooltip ""
		}
	}
}

def Canvas motion_iohilite2 {x y f} {
	set c [$self widget]
	set io [$self identify_closestio $x $y $f]
	if {$io<0} {set @iohilite [list -1 0 0 0 0]; return}
	foreach item {i o} {
		set type_idx [string first $item $io]
		set type [string index $io $type_idx]
		set port [string range $io [expr $type_idx+1] end]
		set object [string range $io 0 [expr $type_idx-1]]
		if {$type_idx >= 0} {break}
	}
	mset {iox ioy} [lmap / [rect_centre [$c bbox $io]] $@zoom]
	set @iohilite [list $object $iox $ioy $type $port]
	$object hilite_io $type $iox $ioy
	set @dehighlight [list $c delete ${io}b]
}

def Canvas iohilite {} {return $@iohilite}

def Canvas motion_iohilite {target x y} {
	set c [$self widget]
	mset {type id detail} $target
	if {[llength $@keynav_iosel_i] || [llength $@keynav_iosel_o]} {return}
	if {$@editmode && [$id canvas] == $self} {
		switch $type {
			inlet  {set io i}
			outlet {set io o}
			default {return}
		}
		set port [$id hilite_io $io $x $y]
		set @dehighlight [list $c delete ${id}$io${port}b]
	}
}

#-----------------------------------------------------------------------------------#
# returns one of those five things:
#   object $id         : the  body of an object
#    inlet $id  $inlet : an  inlet of an object
#   outlet $id $outlet : an outlet of an object
#     wire $id         : a wire
#    label $id         : a label
#  nothing             : nothing
def Canvas identify_target {x y f} {
	set c [$self widget]
	set cx [expr $x*$@zoom]
	set cy [expr $y*$@zoom]
	set stack [$c find overlapping [expr $cx-2] [expr $cy-2] [expr $cx+2] [expr $cy+2]]
	# reversing the stack is necessary for some things
	# not reversing the stack is also necessary for some other things
	# we have to figure out something.
	set stack [lreverse $stack]
	set target ""
	foreach tag $stack {set target [$self target $x $y $f $tag]; if {[llength $target] > 1} {break}}
	if {[llength $target] > 1} {return $target} {return [list "nothing"]}
}

def Canvas target {x y f tag} {
	set c [$self widget]
	set cx [expr $x*$@zoom]
	set cy [expr $y*$@zoom]
	set tags [$c gettags $tag]
	if {[regexp {^[xo][0-9a-f]{6,8}} $tags id]} {
		# prior to Aug 15th, Wires had lower priority as all objects together
		# now it's same priority, so just stacking order (and it's prolly wrong)
		if {[$id classtags] == ""} {return [list "nothing"]}
		set class [$id class]
		if {[$self == $id]} {continue}
		if {[$class <= Wire]} {if {$@action != "imove"} {return [list "wire" $id]}}
		if {[$class <= Box]} {
			if {$@action == "imove"} {
				foreach tag [$self item_stack $x $y] {
					set tags2 [$c gettags $tag]
					if {[regexp {^[xo][0-9a-f]{6,8}} $tags2 id2]} {
						set class [$id2 class]
						if {[$class == Wire]} {return [list "wire" $id2]}
					}
				}
			}
			mset {x1 y1 x2 y2} [$id bbox]
			if {[regexp {^x[0-9a-f]{6,8}LABEL} $tags label]} {
				if {$x>$x1 && $x<$x2 && $y>$y1 && $y<$y2} {
					return [list "object" $id]
				} else {
					return [list "label" $id]
				}
			}
			set outs [$id noutlets]
			set  ins [$id  ninlets]
			if {$y>=$y2-6 && $outs} {
				set val [expr int(($x-$x1)*$outs/($x2-$x1))]
				if {$val==$outs} {incr val -1}
				return [list "outlet" $id $val]
			}
			if {$y< $y1+2 &&  $ins} {
				set val [expr int(($x-$x1)* $ins/($x2-$x1))]
				if {$val== $ins} {incr val -1}
				return [list  "inlet" $id $val]
			}
			return [list "object" $id]
		}
		#puts "skipped a $class"
	}
}

def Canvas item_stack {x y} {
	set c [$self widget]
	set cx [expr {$x*$@zoom}]
	set cy [expr {$y*$@zoom}]
	set sense [$self pointer_sense]
	set stack [$c find overlapping [expr {$cx-$sense}] [expr {$cy-$sense}]\
		                       [expr {$cx+$sense}] [expr {$cy+$sense}]]
	set stack [lreverse $stack]
	return $stack
}

def Canvas identify_closestio {x y f} {
	set c [$self widget]
	set ios {}
	set objs {}
	foreach tag [$self item_stack $x $y] {
		set tags [$c gettags $tag]
		if {[regexp {^[x][0-9a-f]{6,8}[oi][0-9]{1,3}} $tags io]} {
			foreach item {i o} {
				set type_idx [string first $item $io]
				set object [string range $io 0 [expr $type_idx-1]]
				if {$type_idx >= 0} {break}
			}
			if {[$object canvas] == $self} {lappend ios $io}
		}
	}
	if {![llength $ios]} {return -1}
	set mindist 66666
	set idx 0; set i 0
	foreach io $ios {
		set point2 [rect_centre [$c bbox $io]]
		set point1 [list $x $y]
		set dist [distance $point2 $point1]
		if {$dist < $mindist} {set mindist $dist; set idx $i}
		incr i
	}
	return [lindex $ios $idx]
}
def Canvas pointer_sense {} {return [$self look pointer_sense]}
def Canvas pointer_sense= {sense} {
	set ::look(Canvas:pointer_sense) $sense
	mset {x y} $@curpos
	$@sense flash $x $y $sense "red"
}

#-----------------------------------------------------------------------------------#
class_new StatusBar {View} ;# no, using View is wrong here. View is for tk canvas item collections.

def StatusBar widget {} {return .$@canvas.stat}

def StatusBar addw {a b text args} {
	set f [$self widget]
	if {$text!=""} {
		eval [concat [list pack [label $f.${a}_l -text $text -font {helvetica -10} -pady 0] -side left] $args]
	}
	label $f.$a -width $b -font {helvetica -10} -background #cccccc -foreground black -anchor w -pady 0
	pack $f.$a -side left
}

def StatusBar init {canvas} {
	super
	set @canvas $canvas
	set f [$self widget]
	frame $f -border 1 -relief ridge
	$self addw px     4 ""
	$self addw py     4 ""
	$self addw what   28 " " -fill x -expand yes
	$self addw action 12  " Action: "
	$self addw sel    3  " Sel: "
	$self addw focus  10 " Focus: "
}

def Canvas statusbar_draw {x y} {$@statusbar draw $x $y}
def Canvas action {} {return $@action}
def Canvas action= {action} {set @action $action}
def Canvas zoom {} {return $@zoom}

def StatusBar draw {x y} {
    if {$x == "??"} {return}
    set c [$@canvas widget]
    set f [$self widget]
    set zoom [$@canvas zoom]
    set x [expr [$c canvasx $x]/$zoom]
    set y [expr [$c canvasy $y]/$zoom]
    set target [$@canvas identify_target $x $y -1]
    mset {type id detail} $target
    set t $target
    switch -- $type {
      object {
	if {[info exists _($id:pdclass)]} {set class $_($id:pdclass)} {set class unknown}
	append t " \[$class\]"
      }
    }
    set action [$@canvas action]
    if {[regexp ^o $action]} {set action [$action class]}
    if {[string length [$@canvas focus]]} {set t "focus: [$@canvas focus]"}
    $f.px     configure -text [format "%4d" [expr round($x)]]
    $f.py     configure -text [format "%4d" [expr round($y)]]
    if {$::debug} {
      $f.what   configure -text [$c gettags [lindex [$c find overlapping [expr $x-2] [expr $y-2] [expr $x+2] [expr $y+2]] end]]
    } {
      $f.what   configure -text $t
    }
    $f.action configure -text $action
    $f.sel    configure -text [llength [$@canvas selection]]
    $f.focus  configure -text [$@canvas focus]
}

#-----------------------------------------------------------------------------------#
class_new FindModel {Thing}

def FindModel init {canvas} {
	set @orig_canvas $canvas
	set @find_string ""
	set @next_canvases $canvas
	set @last_canvas 0
	set @result ""
	set @results {}
	set @views {}
	set @recursive 1
	set @info ""
}

def FindModel reinit {} {
	set @next_canvases $@orig_canvas
	set @last_canvas 0
	set @result ""
	set @results {}
	$self remove_info
}
def FindModel remove_info {} {
	foreach view $@views {
		set f [$view widget]
		destroy $f.info_l; destroy $f.info
	}
}
def FindModel delete {} {foreach view $@views {destroy [$view widget]}; super}

def FindModel find_string= {s} {set @find_string $s}
def FindModel find_string {} {return $@find_string}
def FindModel result= {s} {
	$@orig_canvas deselect_all
	$@orig_canvas selection= $s
	set @result $s
}
def FindModel result {} {return $@result}
def FindModel views+ {view} {lappend @views $view}
def FindModel views {} {return $@views}
def FindModel end? {} {return $@end}
def FindModel end= {v} {set @end 0}

def FindModel search_recursive {} {
	if {![llength $@next_canvases]} {$self end; return}
        if {[llength $@results]} {$self result= [lindex $@results 0]; set @results [lreplace $@results 0 0];return}
	while {$@last_canvas < [llength $@next_canvases]} {
		set canvas [lindex $@next_canvases $@last_canvas]
		if {[$self cache_results $canvas]} {
			if {![winfo exists [$canvas widget]]} {$canvas popup_open} else {focus [$canvas widget]}
			$self result= [lindex $@results 0]
			set @results [lreplace $@results 0 0]
			incr @last_canvas
		} else {
			incr @last_canvas
			$self search_recursive
		}
		return
	}
	set old_canvases $@next_canvases
	set @next_canvases {}
	if {$@recursive} {
		foreach canvas $old_canvases {foreach x [$canvas get_childcanvas] {lappend @next_canvases $x}}
	} else {
		set @next_canvases {}
	}
	set @last_canvas 0
	$self search_recursive
}

def FindModel end {} {
	$self reinit
	$@orig_canvas deselect_all
	set @info " \"$@find_string\" not found"
	foreach view $@views {
		$view addw label "info" "info"
	}
}

def FindModel cache_results {canvas} {
	set @results {}
	foreach child [$@objects values] {
		if {[string first $@find_string [$child text] 0] >= 0} {
			lappend @results $child
		}
	}
	if {[llength $@results]} {return 1} else {return 0}
}

class_new FindView {FindModel} ;# no, using View is wrong here. View is for tk canvas item collections.
def FindView widget {} {return .$@canvas.find}
def FindView init {canvas} {
	findmodel views+ $self
	set @canvas $canvas
	set @break 0
	set f [$self widget]
	frame $f -border 1 -relief ridge
	$self addw button "close" ""
	$self addw text "find" "find"
	$self addw checkbutton "recursive" "recursive"
	if {[winfo exists .$@canvas.yscroll]} {set w .$@canvas.yscroll} else {set w .$@canvas.c}
	pack $f -side bottom -fill x -before $w
	set string [findmodel find_string]
	if {$string != ""} {$f.find insert 0 $string}
}

def FindView addw {type name label} {
	set f [$self widget]
	if {$label!=""} {
		eval [concat [list pack [label $f.${label}_l -text ${label}: -font {helvetica -10} -pady 0] -side left]]
	}
	switch $type {
		text {
			entry $f.$name -width 10 -relief flat -bg white -borderwidth 0 -highlightthickness 0
			bind $f.$name <Escape> "findmodel delete"
			bind $f.$name <Return> "$self find"
		}
		checkbutton {
			checkbutton $f.$name
			if {$name == "recursive"} {$f.$name configure -variable _(findmodel:recursive)}
		}
		button {
			button $f.$name -border 1 -command "findmodel delete" -image icon_close -width 9 -height 9
			if {$name == "close"} {bind $f.$name <Return> "findmodel delete"}
		}
		label {label $f.$name -textvariable _(findmodel:info) -font {helvetica -10} -pady 0}
	}
	pack $f.$name -side left
	bind $f.$name <Tab> "$self traversal %K %W forward"
}

def FindView find {} {
	set f [$self widget]
	findmodel find_string= [$f.find get]
	findmodel search_recursive
	focus .$@canvas.c
}

def FindView traversal {k w direction} {
	set f [$self widget]
	if {$w == "$f.recursive"} {set next $f.close} else {set next [tk_focusNext $w]}
	focus $next
}

def Canvas check_findbar {} {
	if {[info exists ::_(findmodel:_class)] && ![winfo exists .$self.find.find]} {FindView new $self}
}

def Canvas get_childcanvas {} {
	set canvases {}
	foreach child [$@objects values] {if {[$child class] == "Canvas"} {lappend canvases $child}}
	return $canvases
}

def Canvas runcommand {} {$@runcommand pack_prompt}

class_new Runcommand {Listener}
def Runcommand canvas {} {return $@canvas}

def Runcommand init {serf name command} {
	set @history [History new 20]
	set @serf ${serf}.run
	set @command $command
	set @expanded 0
	set @canvas [string trimleft $serf "."]
	set @defs {}
	set @completions {}
	set @comp_i 0
	set @comp_s "666"
	set @show_id 0
	$self defs
	set f $@serf
	frame $f -border 1 -relief ridge
	button $f.close -border 1 -command "$self unpack_prompt" -image icon_close -width 9 -height 9
	bind $f.close <Return> "$self unpack_prompt"
	pack $f.close -side left
	bind $f.close <Tab> "$self traversal %K %W forward"
	label $f.cmd_l -text Command: -font {helvetica -10} -pady 0
	pack $f.cmd_l -side left
	entry $f.entry -width 30 -relief flat -bg white -borderwidth 0 -highlightthickness 0
	bind $f.entry <Escape>    "$self unpack_prompt"
	bind $f.entry <Control-g> "$self unpack_prompt"
	bind $f.entry <Return>    "$self eval"
	bind $f.entry <Up>        "$self scroll_history +1"
	bind $f.entry <Down>      "$self scroll_history -1"
	bind $f.entry <Control-p> "$self scroll_history +1"
	bind $f.entry <Control-n> "$self scroll_history -1"
	bind $f.entry <Tab>       "$self completion +"
	switch $::tcl_platform(os) {
		Linux   {bind $f.entry <ISO_Left_Tab> "$self completion -"}
		default {bind $f.entry    <Shift-Tab> "$self completion -"}
	}
	pack $f.entry -side left -fill x -expand yes
	bind $f.entry <Control-Tab> "$self traversal %K %W forward"
}

def Runcommand pack_prompt {} {
	set f $@serf
	set @show_id [$@canvas show_id]
	if {!$@show_id} {$@canvas show_id= 1}
	if {[winfo exists .$@canvas.yscroll]} {set w .$@canvas.yscroll} else {set w .$@canvas.c}
	pack $f -side bottom -fill x -before $w
	focus $f.entry
}

def Runcommand unpack_prompt {} {
	if {!$@show_id} {$@canvas show_id= 0}
	pack forget $@serf
	focus [$@canvas widget]
}

def Runcommand traversal {k w direction} {
	set f $@serf
	if {$w == "$f.entry"} {set next $f.close} else {set next [tk_focusNext $w]}
	focus $next
}

def Runcommand eval {} {
	set f $@serf
	if {[winfo exists $f.completion]} {
		set l [string range $@comp 0 [expr [string first ":" $@comp 0]-1]]
		$self replace $l
		destroy $f.completion
		return
	}
	#$self unpack_prompt
	super
	$self unpack_prompt
}

def Runcommand defs {} {
	set name [$@canvas class]
	set len [string length ${name}_]
	foreach def [$name methods] {lappend @defs $def}
}

def Runcommand completion {which} {
	set f $@serf
	set text [$f.entry get]
	if {$text != $@comp_s} {
		set @comp_s $text
		set @completions {}
		set @comp_i 0
		foreach def $@defs {if {[regexp ^$@comp_s $def]} {lappend @completions $def}}
	}
	if {![llength $@completions]} return
	set def [lindex $@completions $@comp_i]
	set args [$@canvas args $def]
	if {[lindex $args 0] == "self"} {
		set args2 [lreplace $args 0 0]
	} else {
		set args2 $args
	}
	if {![llength $args2]} {set args2 "none"}
	set @comp [join [list $def ":" $args2]]
	if {$which == "+"} {
		set @comp_i [expr ($@comp_i+1)%[llength $@completions]]
	} else {
		set @comp_i [expr $@comp_i-1]
		if {$@comp_i<0} {set @comp_i [expr [llength $@completions]-1]}
	}
	if {![winfo exists $f.completion]} {
		label $f.completion -textvariable _($self:comp) -pady 0
		pack $f.completion -side right
	}
}

def Thing args {def} {
	set class [$self class]
	set ancestors [$class ancestors]
	set name ${class}_$def
	set n 0
	foreach class $ancestors {
		set name ${class}_$def
		if {[info exists ::__args($name)]} {break}
	}
	return $::__args($name)
}

def Canvas visible_rect {} {
	set c [$self widget]
	set height [winfo height $c]
	set width [winfo width $c]
	if {$width == 1 && $height == 1} {set height 300; set width 450}
	mset {l r} [$c xview]
	mset {t b} [$c yview]
	if {$l == $r} {set l 0; set r 1}
	if {$t == $b} {set t 0; set b 1}
	set w [expr  $width / ($r - $l)]
	set h [expr $height / ($b - $t)]
	mset {l2 r2} [lmap * [list $l $r] $w]
	mset {t2 b2} [lmap * [list $t $b] $h]
	return [list $l2 $t2 $r2 $b2]
}

def Canvas clipboard_coords {offset} {
	set in 0
	foreach mess [pd_mess_split [$::clipboard value]] {
		set type [lindex $mess 1]
		switch $type {
			canvas {set in 1} "" {} connect {}
			default {
				if {$type == "restore"} {set in 0}
				mset {x y} [lmap + [lrange $mess 2 3] $offset]
				if {!$in} {lappend xcoords $x; lappend ycoords $y}
			}
		}
	}
	return [list $xcoords $ycoords]
}

def Canvas paste_visible? {x1 y1 x2 y2 offset} {
	set in 0
	foreach mess [pd_mess_split [$::clipboard value]] {
		set type [lindex $mess 1]
		switch $type {
			canvas {set in 1} "" {} connect {}
			default {
				if {$type == "restore"} {set in 0}
				mset {x y} [lmap + [lrange $mess 2 3] $offset]
				if {!$in} {
					if {$x > $x2 || $x < $x1} {return 0}
					if {$y > $y2 || $y < $y1} {return 0}
				}
			}
		}
	}
	return 1
}

def Canvas copy_times= {c} {set @copy_count $c}
def Canvas copy_times {} {return $@copy_count}

def Canvas copy {} {
	if {![$@objectsel size]} {return}
	$clipboard value= ""
	$self copy_times= 1
	array unset ::obj_index_sel $self:*
	set j 0
	foreach i [lsort -integer [$@objectsel keys]] {
		set child [$@objectsel get $i]
		#set ::obj_index_sel($self:$child) [$@objectsel search $child]
		set ::obj_index_sel($self:$child) $j; incr j
		$child deconstruct_to $::clipboard
	}
	foreach wire [$@wiresel values] {
		if {[array names ::obj_index_sel $self:[$wire from]] == ""} {continue}
		if {[array names ::obj_index_sel $self:[$wire to  ]] == ""} {continue}
		$wire deconstruct_to $::clipboard $self
	}
}

def Canvas paste {} {
	if {[$self look snap_grid]} {set offset [$self look grid_size]} {set offset 15}
	$self do_paste [expr [$self copy_times] * $offset]
	$self copy_times= [expr [$self copy_times] + 1]
}

def Canvas do_paste {offset} {
	set in 0
	$self deselect_all
	netsend [list .$self "push"]
	foreach mess [pd_mess_split [$::clipboard value]] {
		set type [lindex $mess 1]
		if {$type == "restore"} {incr in -1}
		if {!$in} {set with [list $self new_object_copyselect]} else {set with ""}
		switch $type {
			"" {}
			canvas {incr in; netsend $mess}
			connect {
				if {$with != ""} {set with [list $self new_wire_select]}
				netsend $mess $with}
			default {netsend [$self paste_coords $mess $offset] $with}
		}
	}
	netsend [list #X pop 1]
}

def Canvas paste_coords {mess offset} {
	set x [lindex $mess 2]; set y [lindex $mess 3]
	mset {vx1 vy1 vx2 vy2} [$self visible_rect]
	mset {xcoords ycoords} [$self clipboard_coords $offset]
	set visible [$self paste_visible? $vx1 $vy1 $vx2 $vy2 $offset]
	set ref [lsearch $ycoords [lindex [lsort -real -increasing $ycoords] 0]]
	if {!$visible} {
		set xoff [expr ($vx2 - $vx1) * 0.25]
		set yoff [expr ($vy2 - $vy1) * 0.25]
		set x2 [expr [lindex $mess 2] - [lindex $xcoords $ref] + $vx1 + $xoff]
		set y2 [expr [lindex $mess 3] - [lindex $ycoords $ref] + $vy1 + $yoff]
		return [lreplace $mess 2 3 $x2 $y2]
	} else {
		puts "\t \t [expr $x+$offset] [expr $y+$offset] <<<<<"
		return [lreplace $mess 2 3 [expr $x+$offset] [expr $y+$offset]]
	}
}

def Canvas cut {} {
	$@history atomically [list cut] {
		$self copy
		$self delete_selection
	}
}

def Canvas duplicate {} {
	if {[$self look snap_grid]} {set off [$self look grid_size]} {set off 15}
	$self do_duplicate $off
}

def Canvas do_duplicate {offset} {
	global clipboard
	set backup $clipboard
	set clipboard [Clipboard2 new]
	$self copy
	$self do_paste $offset
	$clipboard delete
	set clipboard $backup
}

def Canvas mouse_copy {} {
	$self do_duplicate 0
}

def Canvas   select_all {} {
	$self editmode= 1
	eval [concat [list $@objectsel reinit] [$@objects list]]
	eval [concat [list   $@wiresel reinit] [  $@wires list]]
}
def Canvas deselect_all {} {
	#$self editmode= 1
	$@objectsel clear
	$@wiresel clear
}

def Canvas popup_help {} {$::main class_browser}

def Canvas popup_open {} {
	#$self init_window
	#set @mapped 1
	if {[winfo exists [$self widget]]} {raise .$self; return}
	netsend [list .$self vis 1]
	#$self init_window
	#$self redraw
}

def Canvas popup {id x y} {
	set p .$self.popup
	catch {destroy $p}
	menu $p -tearoff false
	if {$id == $self} {
		$self populate_menu $p {popup_properties popup_help}
	} elseif {[$id class] == "Wire"} {
		$id populate_menu $p {popup_insert}
	} else {
		$id populate_menu $p {popup_properties popup_open popup_help
			popup_clear_wires popup_remove_from_path popup_delete_from_path popup_copy_id}
	}
	tk_popup $p [expr $x-5] [expr $y-5]
}

def View popup_copy_id {} {
	clipboard clear
	clipboard append $self
}

def Canvas disconnect {wire} {
	set @keynav_tab_sel "wire"
	set id [$@wires get [$self wire_idx $wire]]
	if {$id == $@keynav_current || $id == $@keynav_last_wire} {
		set @keynav_current 0
		set @keynav_last_wire 0
	}
	mset {from outlet to inlet} $wire
	netsend [list .$self disconnect $from $outlet $to $inlet]
	$@history add [list $self    connect $wire]
}
def Canvas connect {wire {callback ""}} {
	mset {from outlet to inlet} $wire
	netsend [list .$self connect $from $outlet $to $inlet] $callback
	$@history add [list $self disconnect $wire]
}

def Canvas clear_wires_of {obj} {
    if {![llength [$obj ioselect]]} {
	set port 0; set type "none"
    } else {
	set port [lindex [$obj ioselect] 0]
	set type [lindex [$obj ioselect] 1]
    }
    foreach wire [$obj wires2] {
	mset {from outlet to inlet} [$wire report]
	switch $type {
	    i {if {  $to==$obj &&  $inlet==$port} {$self disconnect [$wire connects]; if { !$inlet} {lappend @auto_wire_from $from}}}
	    o {if {$from==$obj && $outlet==$port} {$self disconnect [$wire connects]; if {!$outlet} {lappend @auto_wire_to   $to  }}}
	    none {
		$self disconnect [$wire connects]
		if {$from==$obj && !$outlet} {lappend @auto_wire_to   $to  }
		if {  $to==$obj && ! $inlet} {lappend @auto_wire_from $from}
	    }
	}
    }
}

def Canvas clear_wires {} {
	set @auto_wire_to {}; set @auto_wire_from {}
	if {[$@objectsel size] == 1} {
		set objs [$@objectsel values]
	} else {
		if {![llength $@keynav_iosel_i] && ![llength $@keynav_iosel_o]} {return}
		set objs [list $@keynav_iosel_i $@keynav_iosel_o]
	}
	foreach obj $objs {$self clear_wires_of $obj}
}

def Canvas reconnect {} {
	foreach from $@auto_wire_from {
		set idx1 [$@objects search $from]
		foreach to $@auto_wire_to {
			set idx2 [$@objects search $to]
			set wire [list $idx1 0 $idx2 0]
			if {[$self wire_idx $wire] < 0} {$self connect $wire}
		}
	}
}

def Canvas delete_obj_from_path {} {$self clear_wires; $self reconnect;	$self delete_selection}
def Canvas remove_obj_from_path {} {$self clear_wires; $self reconnect}

def Canvas wire_idx {connects} {
	foreach {idx x} [$@wires list] {
		if {[string compare [join $connects] [join [$x connects]]] == 0} {return $idx}
	}
	return -1
}

def Canvas reconnect_brkwires {type brk_quads obj} {
	set k [$@objects search $obj]
	foreach quad $brk_quads {
		mset {from outlet to inlet} $quad
		set orig_outlet $outlet; set orig_inlet $inlet
		switch $type {
			i {set orig_obj $to; set to $k; set inlet 0}
			o {set orig_obj $from; set from $k; set outlet 0}
		}
		netsend [list .$self connect $from $outlet $to $inlet] 
	}
	if {$type == "i"} {
		netsend [list .$self connect $k 0 $orig_obj $orig_inlet]
	} else {
		netsend [list .$self connect $orig_obj $orig_outlet $k 0]
	}
	$self new_object_callback $obj
	
}

def Canvas expand_port {type idx port} {
	set obj [$@objects get $idx]
	mset {bx1 by1 bx2 by2} [$obj io_bbox $type $port]
	mset {ox1 oy1 ox2 oy2} [$obj bbox]
	mset ys [expr $oy2-$oy1]
	mset {brk_wires brk_quads} [$self broken_wires $type $idx $port $self]
	switch $type {
		i {mset {nx ny} [$self xy_snap $bx1 [expr $by1-25]]}
		o {mset {nx ny} [$self xy_snap $bx1 [expr $by1+$ys+25]]}
	}
	foreach quad $brk_quads {$self disconnect $quad}
	set reply [list $self reconnect_brkwires $type $brk_quads]
	netsend [concat [list .$self obj $nx $ny] t a] $reply
	
}

def Canvas outlet_expand {k outlet} {set reconnect [$self broken_wires o $k $inlet]}

def Canvas implicit_wires {objs} {
	set l {}; set h $@objects
	foreach obj $objs {
		set k [$h search $obj]
		for {set i 0} {$i < [$obj ninlets]} {incr i} {
			set ws [$self com_wires i $k $i]; if {[llength $ws]} {foreach w $ws {lappend l $w}}
		}
		for {set o 0} {$o < [$obj noutlets]} {incr o} {
			set ws [$self com_wires o $k $o]; if {[llength $ws]} {foreach w $ws {lappend l $w}}
		}
	}
	#return [lsort -integer -unique $l]
	return [lsort -unique $l]
}

def Canvas com_wires {type k port} {
	set h $@objectsel; set obj [$@objects get $k]; set wires [$obj wires2]; set l {}
	foreach wire $wires {
		mset {f2 o2 t2 i2} [$wire connects]
		if {$t2==$k && $i2==$port && $type=="i" && [$h exists $f2]} {lappend l $wire}
		if {$f2==$k && $o2==$port && $type=="o" && [$h exists $t2]} {lappend l $wire}
	}
	return $l
}

def Canvas broken_wires {type k port canvas} {
	set shash [$canvas objectsel]
	set obj [[$canvas objects] get $k]
	set wires [$obj wires2]; set brk_wires {}; set quads {}
	foreach wire $wires {
		mset {f2 o2 t2 i2} [$wire connects]
		if {$t2==$k && $i2==$port && $type=="i" && ![$shash exists $f2]} {
			lappend brk_wires $wire; lappend quads [$wire connects]
		}
		if {$f2==$k && $o2==$port && $type=="o" && ![$shash exists $t2]} {
			lappend brk_wires $wire; lappend quads [$wire connects]
		}
	}
	return [list $brk_wires $quads]
}


def Canvas selection_center {} {
	set x 0; set y 0
	foreach obj [$@objectsel values] {
		mset {x1 y1} [$obj xy]
		incr x $x1
		incr y $y1
	}
	set n [$@objectsel size]
	set x [expr $x / $n]
	set y [expr $y / $n]
	return [list $x $y]
}

# translate the key/idx in $@objects to key/idx in $@objectsel
def Canvas idx_map {idx} {
	set i 0; set obj [$@objects get $idx]
	foreach sobj [$@objectsel values] {if {$obj == $sobj} {return $i}; incr i}
	return -1
}

def Canvas subpatcherize_mkio {center iolist offset} {
	mset {x y} $center; set inx 0; set outx 0
	for {set i 0} {$i < [llength $iolist]} {incr i} {
		mset {type io port dsp} [lindex $iolist $i]
		if {$type == "i"} {
			if {$dsp} {set inlet "inlet~"} {set inlet "inlet"}
			netsend [list #X obj [expr ($inx+1)*100] 0 $inlet]
			netsend [list #X connect $offset 0 $io $port]
			incr inx
		} else {
			if {$dsp} {set outlet "outlet~"} {set outlet "outlet"}
			netsend [list #X obj [expr ($outx+1)*100] [expr $y*2] $outlet]
			netsend [list #X connect $io $port $offset 0]
			incr outx
		}
		incr offset
	}
}

def Canvas subpatcherize_iopos {orig io} {
	set tab {}; set result {}
	for {set x 0} {$x < [llength $orig]} {incr x} {
		mset {from k1 io1 dsp} [lindex $orig $x]; mset {k2 io2} [lindex $io $x]
		set obj1 [$@objects get $k1]
		mset {x1 y1 x2 y2} [$obj1 io_bbox $from $io1]; set x1 [expr int($x1)]
		lappend pos $x1; lappend tab [list $k1 $x1 [list $k2 $io2 $dsp]]
		#lappend pos $x1; lappend tab [list $k1 $x1 [list $k2 $io2]]
	}
	set tab [lsort -index 1 -real $tab]; set foo ""
	foreach item $tab {mset {k1 val foo2} $item; if {$foo2 != $foo} {lappend result $foo2}; set foo $foo2}
	return $result
}

def Canvas subpatcherize {} {
	set center [$self selection_center]
	set rewire_off [llength [$@objectsel values]]
	set ins {}; set outs {}; set toins {}; set fromouts {}; set broken {}; set iolist {}
	foreach obj [$@objectsel values] {
		for {set i 0} {$i < [$obj ninlets]} {incr i} {
			mset {brk_wires brk_quads} [$self broken_wires i [$@objects search $obj] $i $self]
			if {[llength $brk_wires]} {
				foreach wire $brk_quads {
					set out_obj_name [[$@objects get [lindex $wire 0]] text]
					if {[regexp {~} $out_obj_name]} {set dsp 1} {set dsp 0}
					lappend broken [concat i $wire $dsp]
				}
			}
		}
		for {set o 0} {$o < [$obj noutlets]} {incr o} {
			mset {brk_wires brk_quads} [$self broken_wires o [$@objects search $obj] $o $self]
			if {[llength $brk_wires]} {
				foreach wire $brk_quads {
					set out_obj_name [[$@objects get [lindex $wire 0]] text]
					if {[regexp {~} $out_obj_name]} {set dsp 1} {set dsp 0}
					lappend broken [concat o $wire $dsp]
				}
			}
		}
	}
	# $broken stores total number of broken connections, i= need [inlet] o = need [outlet]
	foreach c $broken {
		mset {type f o t i dsp} $c
		if {$type == "i"} {
			lappend ins  [list $t $i]; lappend    toins [list o $f $o $dsp]
		} else {
			lappend outs [list $f $o]; lappend fromouts [list i $t $i $dsp]
		}
	}
	# figures out the inlet/outlet positioning and num of in/outlet to create
	set ins  [$self subpatcherize_iopos    $toins  $ins]
	set outs [$self subpatcherize_iopos $fromouts $outs]
	# iolist stores in/outlets to be conected inside the subpatch
	foreach in   $ins {mset {idx p dsp}  $in; lappend iolist [list i [$self idx_map $idx] $p $dsp]}
	foreach out $outs {mset {idx p dsp} $out; lappend iolist [list o [$self idx_map $idx] $p $dsp]}
	puts "\t \t Cutting..............."
	$self cut
	puts "\t \t Push.................."
	netsend [list .$self "push"]
	netsend [list #N canvas 0 0 450 300 sub 0] [list $self subpatcherize_id]
	puts "\t \t Push clipboard........"
	foreach mess [pd_mess_split [$::clipboard value]] {netsend $mess}

	#creating in/outlets
	$self subpatcherize_mkio $center $iolist $rewire_off
	#netsend [list [concat #X restore $center pd sub]]
	netsend [concat #X restore $center pd sub]
	puts "\t \t Pop..................."
	netsend [list .$self "pop"] [list $self subpatcherize_rewire $broken $ins $outs]
}

def Canvas subpatcherize_id {id} {set @subpatcherize_id $id} 

def Canvas subpatcherize_rewire {wires inlist outlist bogus} {
	set obj $@subpatcherize_id
	foreach wire $wires {
		mset {type f o t i dsp} $wire
		if {$type == "i"} {
			set idx [lsearch  $inlist [list $t $i $dsp]]
			$self connect [list $f $o [$@objects search $obj] $idx]
		} else {
			set idx [lsearch $outlist [list $f $o $dsp]]
			$self connect [list [$@objects search $obj] $idx $t $i]
		}
	}
}

def Canvas end_action {} {
	switch -- $@action {
	  none    {post "ending action 'none' makes no sense"}
	  default {$@action delete; set @action "none"}
	}
}

proc shift?    {f} {return [expr $f&1]}
proc ctrl?     {f} {return [expr $f&2]}
proc alt?      {f} {return [expr $f&4]}
proc button_of {f} {
#	set f [expr $f>>8]
#	set b 1
#	while {[expr $f&1==0} {set f [expr $f>>1]}
#	return $f
	return [expr $f>>8]
}

class_new FutureWire {View}
def FutureWire init {canvas x y f target} {
	super
	set @canvas $canvas
	mset {type from port} $target
	switch $type {
	    outlet {set @from $from; set @outlet $port; set   @to ""   ; set @inlet ""   ; set port_name ${from}o${port}}
	    inlet  {set @from ""   ; set @outlet ""   ; set   @to $from; set @inlet $port; set port_name ${from}i${port}}
	}
	mset {x y} [lmap / [rect_centre [[$@canvas widget] bbox $port_name]] [$@canvas zoom]]
	set @x1 $x
	set @y1 $y
	$self motion $@x1 $@y1 $f $target
}
def FutureWire motion {x y f target} {
	set @x2 $x
	set @y2 $y
	mset [list type foo bar] $target
	$self draw
}
def FutureWire unclick {x y f target} {
	mset [list type foo bar] $target
	mset {obj iox ioy io idx} [$@canvas iohilite]
	if {$obj != -1} {
		switch $io {i {set type "inlet"} o {set type "outlet"}}
		set target [list $type $obj $idx]
	}
	switch $type {
		outlet {mset [list type @from @outlet] $target}
		inlet  {mset [list type   @to  @inlet] $target}
		default {}
	}
	set from_idx [[$@canvas objects] search $@from]
	set   to_idx [[$@canvas objects] search $@to]
	if {$from_idx >= 0 && $to_idx >= 0 && $@from != $@to} {
		$@canvas connect [list $from_idx $@outlet $to_idx $@inlet]
	}
	if {![shift? $f]} {$@canvas end_action}
}
def FutureWire draw {} {
	$self item WIRE line [xys $@x1 $@y1 $@x2 $@y2] -dash {4 4 4 4} -fill [$self look dash] -smooth yes 
}

class_new GopRect {View}

def GopRect init {canvas rect} {
	set @canvas $canvas
	set @rect $rect
}

def GopRect draw {} {
	$self item GOPRECT rectangle $@rect -outline [$self look fg]
}

class_new SelRect {View}
def SelRect init {canvas x y bf target} {
	super
	set @x1 $x
	set @y1 $y
	set @canvas $canvas
	$self motion $x $y 0 $target
}
def SelRect motion {x y f target} {
	set @x2 $x
	set @y2 $y
	$self draw
}
def SelRect unclick {x y f target} {
	$self motion $x $y 0 $target
	set sel {}
	set c [$@canvas widget]
	mset {x1 y1 x2 y2} [lmap * [list $@x1 $@y1 $@x2 $@y2] [$@canvas zoom]]
	set sel [$c find overlapping $x1 $y1 $x2 $y2]
	set selrect_index [lsearch $sel [$c find withtag selrect]]
	set sel [lreplace $sel $selrect_index $selrect_index]
	if {[llength $sel]} {
		set objects {}
		#set wires {}
		foreach tag $sel {
			if {[regexp {^[xo]?[0-9a-f]{6,8}} [$c gettags $tag] id]} {
				if {[$@canvas == $id]} {continue}
				if {[[$id class] <=  Box]} {lappend objects $id}
				#elseif {[[$id class] <= Wire]} {lappend wires   $id}
			}
		}
		set objects [lsort -unique $objects]
		#set wires   [lsort -unique $wires]
		set objects2 {}
		#so that objects in gop won't get selected...
		foreach obj $objects {if {[$obj canvas] == $@canvas} {lappend objects2 $obj}}
		$@canvas selection+= $objects2
		#$@canvas selection_wire+= $wires
	}
	$@canvas selection_wire+= [$@canvas implicit_wires $objects]
	set _($@canvas:keynav_tab_sel) "wire"
	$@canvas end_action
	
}
def SelRect draw {} {
	$self item RECT line [list $@x1 $@y1 $@x2 $@y1 $@x2 $@y2 $@x1 $@y2 $@x1 $@y1] \
	    -fill [$self look rect] -dash {3 3 3 3} -dashoffset 3 
}

def Canvas click {x y f target} {
	if {[winfo exists .completion]} {
		raise .completion
		focus .completion.comp
		return
	}
	mset {type id detail} $target
	set c [$self widget]
	focus $c
	set @click_at [list $x $y]
	if {$f&8} {if {$id == ""} {set id $self}; $self right_click $id $x $y; return}
	if {!$@editmode} {$self click_runmode $id $x $y $f $target; return}
	set in_selection [expr [$@objectsel search $id]>=0]
	switch $@action {
		mouse_copy {$self move_selection}
	}
	switch $type {
		outlet {}
		inlet {}
		object {$self click_on_object $id $f}
		wire {$self click_on_wire $id $f $x $y}
		nothing {
			#$self deselect_all
			if {[lindex $@iohilite 0] == -1} {$self click_on_nothing $f $target $x $y; return}
		}
		label {$self click_on_object $id $f}
		default {error "BORK: $type"}
	}
	if {$@iohilite != "" && $type != "wire"} {
		mset {obj iox ioy io idx} $@iohilite
		if {$obj < 0} {return}
		switch $io {
			i {set type "inlet"}
			o {set type "outlet"}
		}
		if {$@action == "none" && [$obj canvas] == $self} {
			set @action [FutureWire new $self $iox $ioy $f [list $type $obj $idx]]
		}
		return
	}
}

def Canvas click_runmode {id x y f target} {
	foreach obj [$self selection] {if {[[$obj class] <= AtomBox]} {$obj unedit}}
	$self deselect_all
	#if {$@focus != ""} {if {[[$@focus class] <= TextBox]} {$self selection-= $@focus;$@focus unedit}}
	if {[llength $id]} {
		if {[$id class] != "Canvas"} {
			$id click $x $y $f $target
		} else {
			if {[$id subpatch]} {
				if {![$id mapped]} {$id popup_open} elseif {![$id gop]} {raise .$id}
			}
		}
	}
}

def Canvas click_on_object {id f} {
	set c [$self widget]; set text $c.${id}text
	# so that if one clicks on the objectbox when editing the objectname, the focus won't get lost
	if {[winfo exists $text]} {focus $text; return}
	switch [expr $f&255] {
		0 {
			if {[$self action] == "mouse_copy"} {$self action= "none"; return}
			# if $id is the content of GOP
			if {[$id canvas] != $self} {
				set obj [$id get_parent_gop $self]
			} else {set obj $id}
			if {[$@objectsel search $obj] < 0 || [$@objectsel size] == 0} {
				$self deselect_all
				$self selection+= $obj
				set @action edit
			} else {set @action edit}
		}
		1 {
			if {[$@objectsel search $id] < 0} {
				$self selection+= $id
			} else {
				$self selection-= $id
			}
		}
		2 {
			if {![llength [$id wires2]]} {return}
			$self deselect_all
			$self selection+= $id
			$self remove_obj_from_path
		}
		3 {
			if {[$id canvas] != $self} {
				set obj [$id get_parent_gop $self]
			} else {set obj $id}
			if {![$@objectsel size]} {$self selection+= $obj}
			if {[$@objectsel search $obj] < 0} {
				$self deselect_all
				$self selection+= $obj
			}
			$self action= "mouse_copy"
		}
	}
}
def Canvas click_on_wire {id f x y} {
	set obj_selection [$self selection]
	if {[llength $obj_selection]} {$self selection-= $obj_selection}
	set @keynav_tab_sel "object"
	switch [expr $f&255] {
		0 {$self selection_wire= $id}
		1 {$self selection_wire+= $id}
		2 {
			set c [$self widget]
			set wire [$id connects]
			$self disconnect $wire
			set from [$@objects get [lindex $wire 0]]; set outlet [lindex $wire 1]
			set to   [$@objects get [lindex $wire 2]]; set  inlet [lindex $wire 3]
			set opos [lmap / [rect_centre [$c bbox ${from}o${outlet}]] [$self zoom]]
			set ipos [lmap / [rect_centre [$c bbox   ${to}i${inlet}]]  [$self zoom]]
			set  pos [list $x $y]
			if {[distance $pos $opos] > [distance $pos $ipos]} {
				mset {x1 y1} $ipos
				set target [list outlet $from $outlet]
			} else {
				mset {x1 y1} $opos
				set target [list inlet $to $inlet]
			}
			set @action [FutureWire new $self $x1 $y1 $f $target]
		}
	}
}

def Canvas click_on_nothing {f target x y} {
	# this cget check actually saves a full tk redraw
	if {[[$self widget] cget -cursor] != {}} {[$self widget] configure -cursor {}}
	if {$@focus != ""} {if {[[$@focus class] <= TextBox]} {$@focus unedit}}
	if {$@action == "insert"} {return}
	if {![expr $f&255]} {
		$self deselect_all
		#$self click_deselect_io
	}
	switch $@action {
		edit {}
		move {}
		none {}
		insert {}
		chain_obj {}
		imove {}
		mouse_copy {}
		default {$@action unclick $x $y $f $target}
	}
	set @action [SelRect new $self $x $y $f $target]
}

def Canvas right_click {id x y} {
	set c [$self widget]
	set @insert_x $x; set @insert_y $y
	if {$id != $self} {set id [$self gop_target $id]}
	$self popup $id [winfo pointerx $c] [winfo pointery $c]
}

def Canvas unclick {x y f target} {
	set c [$self widget]
	mset {type id detail} $target
	if {$@editmode} {
		switch $@action {
			edit {
				if {[$id canvas] != $self} {
					set obj [$id get_parent_gop $self]
				} else {set obj $id}
				if {[$id class] == "Canvas"} {if {[$id text] == "graph"} {set @action none; return}}
				set focus [$self focus]
				if {$focus != ""} {if {[[$focus class] <= TextBox]} {$focus unedit}}
				$obj edit; set @action none; $obj changed action
			}
			move {$self unclick_move}
			none {}
			insert {}
			chain_obj {}
			mouse_copy {$self mouse_copy}
			imove {$self unclick_move}
			default {$@action unclick $x $y $f $target}
		}
	} else {$self unclick_runmode $target $f $x $y}
	$self adjust_scrollbars
	#$self checkgeometry
}

def Canvas unclick_move {} {
	$self move_selection
	set @action none
}

def Canvas move_selection {} {
	foreach obj [$@objectsel values] {
		if {![[$obj class] <= Box]} {
			puts "Canvas unclick warning: trying to move non-Box explicitly"
			continue
		}
		mset {x1 y1} [$obj xy]
		switch $@action {
			mouse_copy {}
			default {$obj position= [$obj backupxy]}
		}
		$obj moveto $x1 $y1
		if {[$obj edit?]} {focus [[$obj canvas] widget].${obj}text}
	}
	set objs [$@objectsel values]
}

def Canvas unclick_runmode {target f x y} {
	if {[$self focus] != ""} {[$self focus] unclick $x $y $f $target}
	mset {type id detail} $target
	if {$id != ""} {
		if {[$id class] == "Array"} {$id unclick $x $y $f $target; return}
	}
}

def Canvas get_bbox {} {return $@bbox}

if {$have_expand} {
	def Canvas notice {origin args} {$self child_changed $origin {expand}$args}
} else {
	def Canvas notice {origin args} {eval [concat [list $self child_changed $origin] $args]}
}

def Canvas tab_jump {} {
	if {![$@objects size]} {return}
	set @keynav 1
	set olength [$@objectsel size]
	set wlength [  $@wiresel size]
	if {$@keynav_tab_sel == "object"} {
		if {[$@wires size]} {set @keynav_tab_sel "wire"}
	} else {
		set @keynav_tab_sel "object"
	}
	switch $@keynav_tab_sel {
		object {$self tab_jump_object}
		wire {$self tab_jump_wire}
	}
}

def Canvas tab_jump_wire {} {
	# if no selection done by the mouse
	if {![$@wiresel size]} {
		# see if the selected object has wire, if yes, use it
		# if keynav_current is already a wire, do nothing
		if {[$@keynav_current class] != "Wire"} {
			#use the last key selected wire as start if there is one
			if {$@keynav_last_wire != 0} {
				$self deselect_all
				set @keynav_current $@keynav_last_wire
			} else {
				if {[llength [$@keynav_current wires2]]} {
					$self deselect_all
					set @keynav_current [lindex [$@keynav_current wires2] 0]
					set @keynav_last_wire $@keynav_current
				} else {
					# check if the canvas has wires, if yes, use it
					if {[$@wires size]} {
						$self deselect_all
						set @keynav_current [lindex [$@wires values] 0]
						set @keynav_last_wire $@keynav_current
					} else {return}

				}
			}
		}
	}
	$self selection_wire= $@keynav_current
}

def Canvas tab_jump_object {} {
	set olength [$@objectsel size]
	# if there is no selection done by mouse
	if {!$olength} {
		# if keynav_current is 0, aka the start of key navigation
		if {$@keynav_current == 0} {
			if {[$@objects size]} {set @keynav_current [lindex [$@objects values] 0]} else {return}
		} else {
			# if the keynav_current is a wire, set keynav_current to a object
			if {[$@keynav_current class] == "Wire"} {
				set @keynav_last $@keynav_current
				$self deselect_all
				# use the last key selected obj as the start if there is one
				if {$@keynav_last_obj != 0} {
					set @keynav_current $@keynav_last_obj
				} else {
					set @keynav_current [lindex [$@keynav_current report] 0]
					set @keynav_last_obj $@keynav_current
				}
			}
		}
	} elseif {$olength == 1}  {
		set @keynav_current [lindex [$@objectsel values] 0]
		$self deselect_all
	} else {
		$self deselect_all
		set @keynav_current [$@objects get 0]
		set @keynav_last_obj $@keynav_current
	}
	$self selection= $@keynav_current
}

def Canvas key_nav_up          {} {$self key_nav +1 -1 0}
def Canvas key_nav_down        {} {$self key_nav -1 +1 0}
def Canvas key_nav_right       {} {$self key_nav -1 -1 0}
def Canvas key_nav_left        {} {$self key_nav +1 +1 0}
def Canvas key_nav_up_shift    {} {$self key_nav +1 -1 1}
def Canvas key_nav_down_shift  {} {$self key_nav -1 +1 1}
def Canvas key_nav_right_shift {} {$self key_nav -1 -1 1}
def Canvas key_nav_left_shift  {} {$self key_nav +1 +1 1}

def Canvas key_nav {du dv shift} {
	if {$@keynav_shift && !$shift} {}
	set @keynav_shift $shift
	if {[$@objectsel size] > 1} {
		if {[llength $@keynav_iosel_i] > 0 || [llength $@keynav_iosel_o] > 0} {
			#$self deselect_all
		}
	}
	if {!$@keynav} {$self tab_jump}
	switch $@keynav_tab_sel {
	    object {
		set @keynav_next [$self quadrant $du $dv [$@objects values]]
		if {!$shift} {
			$self selection-= $@keynav_current
			$@keynav_current selected?= 0
			set @keynav_last_obj $@keynav_next
		}
		if {[$@objectsel search $@keynav_next] < 0} {$self selection+= $@keynav_next}
	    }
	    wire {
		#$@keynav_current selected?= 0
		set @keynav_next [$self quadrant $du $dv [$@wires values]]
		if {!$shift} {
			$self selection_wire-= $@keynav_current
			$@keynav_current selected?= 0
			set @keynav_last_wire $@keynav_next
		}
		if {[$@wiresel search $@keynav_next] < 0} {$self selection_wire+= $@keynav_next}
	    }
	}
	#$self selection+= $@keynav_next
	set @keynav_current $@keynav_next
}

def Canvas victim {} {
	if {[$@objectsel size]} {return [$@objectsel values]}
	if {[string compare $@keynav_current 0]} {return {}}
	return $@keynav_current
}

def Canvas key_nav_ioselect {} {
	if {![$@objectsel size]} {return}
	set var [lindex [$@objectsel values] end]
	if {$@keynav_iosel != $var} {set @keynav_iocount 0}
	if {$@keynav_port != 0 && $@keynav_iosel == $var} {
		#set hilitebox $@keynav_port
		foreach io $@keynav_port2 {[$self widget] delete ${io}b}
	}
	set obj [lindex [$@objectsel values] 0]
	set ins  [$obj ninlets]
	set outs [$obj noutlets]
	set ports {}; set ports2 {}; set ports3 {}
	for {set i 0} {$i < $ins}  {incr i} {lappend ports ${obj}i${i}; lappend ports2 "i"; lappend ports3 $i}
	for {set i 0} {$i < $outs} {incr i} {lappend ports ${obj}o${i}; lappend ports2 "o"; lappend ports3 $i}
	#incr @keynav_iocount
	if {$@keynav_iocount >= [llength $ports]} {set @keynav_iocount 0}
	set port [lindex $ports3 $@keynav_iocount]
	set type [lindex $ports2 $@keynav_iocount]
	set @keynav_port ${obj}${type}${port}
	set @keynav_port2 {}
	# @keynav_port stores the current hilited in/outlets
	# @keynav_ports stores the current hilited in/outlets if there are multiple objects
	# @keynav_iosel_i stores the selected inlets
	# @keynav_iosel_o stores the selected outlets
	foreach object [$@objectsel values] {
		if {$@keynav_iosel != $var} {set @keynav_iosel $var}
		switch $type {
			i {
				if {[lsearch $@keynav_iosel_i $object] == -1} {
					lappend @keynav_iosel_i $object
					set find [lsearch $@keynav_iosel_o $object]
					if {$find >= 0} {set @keynav_iosel_o [lreplace $@keynav_iosel_o $find $find]}
				}
			}
			o {
				if {[lsearch $@keynav_iosel_o $object] == -1} {
					lappend @keynav_iosel_o $object
					set find [lsearch $@keynav_iosel_i $object]
					if {$find >= 0} {set @keynav_iosel_i [lreplace $@keynav_iosel_i $find $find]}
				}
			}
		}
		mset {x y x1 y1} [[$self widget] bbox ${object}${type}${port}]
		lappend @keynav_port2 ${object}${type}${port}
		set _($object:ioselect) [list [lindex $ports3 $@keynav_iocount] [lindex $ports2 $@keynav_iocount]]
		#set _($object:ioselect) [lindex $ports3 $@keynav_iocount]
		$object hilite_io [lindex $ports2 $@keynav_iocount] [expr $x/$@zoom] [expr $y/$@zoom]
	}
	incr @keynav_iocount
	
}

def Canvas keyboard_mode {} {
	post "loading keyboard-mode...."
	if {[file exists kb-mode.tcl]} {
		package require kb-mode
		$@runcommand defs
		if {![info exists @kbcursor]} {set @kbcursor [kb-mode_init $self]}
		puts "cursor::: $@kbcursor"
	}
}

def Canvas keyboard_mode_exit {} {
	post "exiting keyboard-mode...."
	package forget kb-mode
	remove_callback kb-mode
	$@runcommand defs
	$@kbcursor delete
	read_ddrc
	remove_callback "kb-mode"
	unset @kbcursor
}

def Canvas keyprefix {} {
	if {!$@keyprefix} {set @keyprefix 1} else {set @keyprefix 0}
}

def Canvas click_deselect_io {} {
	if {[llength $@keynav_iosel_i] || [llength $@keynav_iosel_o]} {
		if {!$@iosel_deselect} {set @iosel_deselect 1} else {$self dehilite_io; set @iosel_deselect 0}
	} else {
		$self dehilite_io
	}
}

def Canvas dehilite_io {} {
	foreach io [concat $@keynav_iosel_i $@keynav_iosel_o] {
		puts "$io is a [$io class]"
		set box $_($io:ioselect)
		set type [lindex $_($io:ioselect) 1]
		set port [lindex $_($io:ioselect) 0]
		set tag ${io}${type}${port}
		[$self widget] delete ${tag}b
		set _($io:ioselect) {}
	}
	set @keynav_iosel_i {}
	set @keynav_iosel_o {}
	set @keynav_port 0
	set @keynav_iocount 0
}

def Canvas incr_scale {} {$self scale out}
def Canvas decr_scale {} {$self scale in}

def Canvas scale {mode} {
	set s $::scale_amount
	switch $mode { in { set s [expr 1/$s] }}
	set sel [$@objectsel values]
	if {![llength $sel]} {set sel [$@objects values]}
	foreach child $sel {
		mset {x y} [$child xy]
		set x1 [expr $x*$s]
		set y1 [expr $y*$s]
		$child position= [list $x1 $y1]
		netsend [list .$self object_moveto $child $x1 $y1]
	}
	foreach child $sel {$child changed_wires}
}

def Canvas incr_zoom {} {$self zooming in}
def Canvas decr_zoom {} {$self zooming out}
def Canvas zooming {mode} {
	global zoom bar
	set spinbox .$self.bbar.scale
	set val [string trimright [$spinbox get] %]
	set i [lsearch $zoom(canned) $val]
	set end [expr [llength $zoom(canned)] - 1]
	switch -regexp $mode {
	   in|up   {if {$i<$end} {incr i +1}}
	  out|down {if {$i>0}    {incr i -1}}
	}
	set per [lindex $zoom(canned) $i]
	$spinbox set $per%
	set @zoom [expr $per/100.0] ;# @zoom must be float, not int
	#$self propagate_zoom $@zoom
	$self redraw
	if {[$self look gridstate]} {if {$@editmode} {$@grid erase}}
	foreach child [$@objects values] {if {[$child class] == "Canvas" && [$child gop]} {$child all_changed}}
}

def Canvas propagate_zoom {zoom} {
	foreach child [$@objects values] {
		if {[$child class] == "Canvas"} {
			set @zoom $zoom
			$child propagate_zoom $zoom
		}
	}
}

#-----------------------------------------------------------------------------------#
set lastcanvasconfigured ""
set lastcanvasconfiguration ""

def Canvas checkgeometry {} {
    set topname .$self
    set boo [winfo geometry $topname.c]
    set boo2 [wm geometry $topname]
    global lastcanvasconfigured lastcanvasconfiguration
    if {$topname != $lastcanvasconfigured || $boo != $lastcanvasconfiguration} {
    	    set lastcanvasconfigured $topname
    	    set lastcanvasconfiguration $boo
    }
}
#-----------------------------------------------------------------------------------#
def Canvas selection_move {dx dy} {
	$@history atomically [list move] {
		$self selection_move2 $dx $dy
	}
}
def Canvas selection_move2 {dx dy} {
	if {![$self editmode]} {return}
	foreach o [$@objectsel values] {
		mset {x1 y1} [$o xy]
		if {[[$o class] <= Box]} {
			$o moveto [expr $x1+$dx] [expr $y1+$dy]
		} else {
			puts "selection_move: $o is not a Box, it's a [$o class]"
		}
	}
}

def Canvas key {x y key iso shift} {
	global tooltip; if {$tooltip ne ""} {$tooltip delete; set tooltip ""}
	#if {[modes_callback $self "key" $x $y $key $iso $shift]} {return}
	#if {[$self focus] != ""} {[$self focus] key $key $shift}
	#if {$iso != ""} {scan $iso %c key}
	#set focus [$self focus]
	if {!$@editmode && [llength [$self selection]] == 1} {
		set obj [$self selection]
		if {[[$obj class] <= AtomBox]} {
			if {[regexp {^[a-zA-Z0-9~/\._]{1}$} $key]} {$obj text= $key} {return}
			$obj edit
			$obj clear 0
			return
		}
	}
	if {$shift} {
		if {[$self look snap_grid]} {set motion [expr [$self look grid_size]*2]} {set motion 10}
	} else {
		if {[$self look snap_grid]} {set motion [$self look grid_size]} {set motion 1}
	}
	set n [$@objectsel size]
	switch -regexp -- $key {
		BackSpace|Delete|KP_Delete {$self delete_selection}
		Up    {if {$n} {$self arrow_key 0 -$motion} else {$self scroll y -$motion}}
		Down  {if {$n} {$self arrow_key 0 +$motion} else {$self scroll y +$motion}}
		Left  {if {$n} {$self arrow_key -$motion 0} else {$self scroll x -$motion}}
		Right {if {$n} {$self arrow_key +$motion 0} else {$self scroll x +$motion}}
		Tab   {$self tab_jump}
		Return {$self return_key $x $y $key $iso $shift}
		F1 {$self deselect_all}
		F2 {set @keynav 0; $@active hide}
		default {}
	}
}

def Canvas return_key {x y key iso f} {
	mset {type id detail} [$self identify_target $x $y $f]
	if {![llength $@keynav_iosel_i] && ![llength $@keynav_iosel_o]} {
		if {[$@objectsel size] == 1} {
			mset {bx1 by1 bx2 by2} [[$self selection] bbox]
			set x1 [expr ($bx1+$bx2)/2]; set y1 [expr ($by1+$by2)/2]
			$self   click_wrap $x1 $y1 1 $f
			$self unclick_wrap $x1 $y1 1 $f
		}
	} else {
		foreach out_obj $@keynav_iosel_o {
			set from [$@objects search $out_obj]
			set outlet [lindex $_($out_obj:ioselect) 0]
			foreach in_obj $@keynav_iosel_i {
				set to [$@objects search $in_obj]
				set inlet [lindex $_($in_obj:ioselect) 0]
				$self connect [list $from $outlet $to $inlet]
			}
		}
		$self dehilite_io
	}

}

def Canvas arrow_key {val1 val2} {
	if {![$self editmode]} {
		if {[$@objectsel size] == 1} {
			set o [$@selection values]
			if {[[$o class] <= IEMGUI] || [[$o class] == FloatBox]} {$o key_incr $val1 $val2}
		}
	} else {$self selection_move $val1 $val2}
}

def Canvas clear_selection {} {
	if {[$@objectsel size] > 0 || [$@wiresel size] > 0} {
		$self deselect_all
		$self dehilite_io
	}
	if {$@keynav} {
		set @keynav 0
		#bogus switch so that keynav remains the same next time..
		if {$@keynav_tab_sel == "object"} {set @keynav_tab_sel "wire"} {set @keynav_tab_sel "object"}
	}
}

#-----------------------------------------------------------------------------------#
def Canvas keynav_current {} {return $@keynav_current}
def Canvas keynav_current= {current} {
	set @keynav_current $current
	switch [$current class] {
		Wire {set @keynav_last_wire $current}
		default {set @keynav_last_obj $current}
	}
}
def Canvas keynav {} {return $@keynav}
def Canvas keyup {x y key iso shift} {
	if {$iso != ""} {scan $iso %c key}
	if {$::macro_state} {$::macro state= 1; set ::macro_state 0 }
	$@active draw
}

class_new Active {View}
def Active init {canvas} {
	super
	set @canvas $canvas
	set @length 3
	set @length2 10
}
def Active draw {} {
	set current [$@canvas keynav_current]
	if {$current == ""} {return}
	if {![string compare $current 0]} {return}
	set col [$self look selectframe]
	if {[$@canvas keynav]} {
		if {[$current class] == "Wire"} {
			$self item_delete
			set i 0
			foreach side {1.6 -1.6} {
				mset {ax1 ay1 ax2 ay2} [$current bbox]
				set t [expr atan2($ay2-$ay1, $ax2-$ax1)]
				set cx1 [expr $ax1 + (($ax2-$ax1)* 0.15)]
				set cy1 [expr $ay1 + (($ay2-$ay1)* 0.15)]
				set cx2 [expr ($@length * cos($t+$side)) + $cx1]
				set cy2 [expr ($@length * sin($t+$side)) + $cy1]

				for {set n 0} {$n < 2} {incr n} {
					set angle [lindex {45 90 -45 -90} [expr $n+$i]]
					mset {ax1 ay1 ax2 ay2} [$current bbox]
					set t [expr atan2($ay2-$ay1, $ax2-$ax1)]
					set bx1 [expr $ax1 + (($ax2-$ax1)* 0.15)]
					set by1 [expr $ay1 + (($ay2-$ay1)* 0.15)]
					set bx2 [expr ($@length2 * cos($t+$angle)) + $bx1]
					set by2 [expr ($@length2 * sin($t+$angle)) + $by1]
					set line [list $cx2 $cy2 $bx2 $by2]
					$self item LINE$angle line $line -fill $col -width 2
				}
 				set i [expr $i+2]
			}
		} else {
			$self item_delete
			mset {x y} [lmap - [$current xy] 5]
			set line1 [list $x $y $x [expr $y + $@length2]]
			set line2 [list $x $y [expr $x + $@length2] $y]
			$self item LINE1 line $line1 -fill $col -width 2
			$self item LINE2 line $line2 -fill $col -width 2

		}
	} else {$self hide}
}
def Active hide {} {$self item_delete}
def Active bbox {} {
	set current [$@canvas keynav_current]
	set l [$current xy]
	concat $l $l ;# happy now??
}

def Canvas update_Active {item} {$self keynav_current= $item}

#-----------------------------------------------------------------------------------#
class_new Box {View}

def Box init {{mess {}}} {
	super
	# @wires2 stores the connections to and from a object
	set @wires2 {}
	$self reinit $mess
}

def Box delete {} {if {$@canvas != ""} {[$@canvas objects] unset $@index}; super}

def Box remove_braces {str} {
	# this hack is to remove the "\" in the text
	regsub -all {\\} $str "" text
	return $text
}

def Box reinit {mess} {
    global classinfo fields
	if {[$::macro state] && [llength $mess] > 4} {$::macro append_ref $mess}
    if {![llength $mess]} {return} ;# what?
    if {[lindex $mess 1] == "obj"} {set i 4} else {set i 1}
    set @pdclass [lindex $mess $i]
    if {[info exists fields($@pdclass)]} {
	    set i 0
	    foreach f $fields($@pdclass) {set _($self:$f) [lindex $mess $i]; incr i}
    } else {
	    set @text [$self remove_braces [join [lrange $mess 4 end]]]
    }
    $self outside_of_the_box
}

def Box update_size {} {}

# will be so that @wires are updated correctly without breaking encapsulation
def Box connect_out {} {}
def Box connect_in {} {}

def Box draw {} {
	$self draw_box
	if {[$@canvas show_id]} {$self draw_id} {$self item_delete ID}
	[[$self get_canvas] widget] raise $self
	$self update_hilite_io
#	if {[$self class] == "Canvas"} {$self restack}
	if {[info exists @elapsed]} {
		mset {x1 y1 x2 y2} [$self bbox]
		$self item ELAPSED text [l+ {10 1} [list $x1 $y2]] -anchor nw -fill "#008800" \
			-text $@elapsed -font {{DejaVu Sans Mono} -8}
	}
}

def Box elapsed {f} {
	set @elapsed $f
	$self changed
}

def Canvas get_elapsed {} {netsend [list .$self get_elapsed]}

def Canvas show_id {} {return $@show_id}
def Canvas show_id= {val} {set @show_id $val; $self redraw}
def Canvas id_toggle {} {if {$@show_id} {set @show_id 0} {set @show_id 1}; $self redraw}

def Box draw_id {} {
	set id [$self index]:
	mset {x y} [$self xy]
	set fw [font measure [$self look font] 0]
	if {[$@canvas editmode]} {
		set col [complement [$@canvas look bgedit]]
	} else {set col [complement [$@canvas look bgrun]]}
	$self item ID text [list [expr $x-([string length $id]*$fw)] [expr $y+2]] \
	    -font [$self look  font] -text $id \
	    -fill $col -anchor nw
}

def Box draw_box {} {}
def Box edit {} {}
def Box unedit {} {}

def Box bbox {} {
	mset {x y} [$self xy]
	set xs $@xs
	set ys $@ys
	list $x $y [expr {$x+$xs}] [expr {$y+$ys}]
}

def Box io_bbox {type port} {
	mset {x1 y1 x2 y2} [$self bbox]
	set xs [expr {$x2-$x1}]
	# method calls aren't as fast as we'd want them to be.
	#set iowidth [$self look iowidth]
	#set fy [$self look iopos]
	set iowidth 7
	set fy -1
	switch $type {
		o {set n $@noutlets; set y [expr {$y2+$fy  }]}
		i {set n  $@ninlets; set y [expr {$y1-$fy-1}]}
	}
	set nplus [expr {$n==1 ? 1 : $n-1}]
	set onset [expr {$x1+($xs-$iowidth)*$port/$nplus}]
	set points [list $onset $y [expr {$onset+$iowidth}] $y]
	return $points
}


def Box ioselect= {type port} {set @ioselect [list $type $port]}
def Box ioselect {} {return $@ioselect}

def Box wires2 {} {return $@wires2}
def Box wires2+= {val} {if {[lsearch $@wires2 $val] < 0} {lappend @wires2 $val}}

def Box changed_wires {} {foreach wire $@wires2 {$wire changed}}

def Box delete_wire {wire} {
  set find [lsearch $@wires2 $wire]
  if {$find != -1} {set @wires2 [lreplace $@wires2 $find $find]}
}

def Box move {dx dy} {
	set @x1 [expr {$@x1+$dx}]; set @y1 [expr {$@y1+$dy}]
	set zoom [$@canvas zoom]
	$self changed ;# until we find a way to avoid rounding errors on [$@canvas widget] move.
	$self changed_wires
}

# temporary hack... only used during the moving of objects.
def Box backupxy= {xy} {set @backupxy $xy}
def Box backupxy    {} {return $@backupxy}

# the only one sending to the server.
# View position= is when getting position from server.
# View xy is virtual (for GOP)
def Box moveto {x1 y1} {
	netsend [list .$@canvas object_moveto $self $x1 $y1]
	[$@canvas history] add [list $self moveto $@x1 $@y1]
	if {[$self class] == "Canvas"} {
		if {[$self gop] && ![winfo exists .$self.c]} {foreach x $@visibles {$x changed}}
	}
	set @x1 $x1
	set @y1 $y1
	$self changed
	$self draw_wires
}

def Box draw_io2 {which n color} {
	for {set i 0} {$i<$n} {incr i} {
		set points [$self io_bbox $which $i]
		$self item [list $which$i $which] rectangle $points -outline $color -fill $color -width 1
		[[$self get_canvas] widget] raise $self$which$i
	}
}

def Box draw_io {} {
    $self draw_io2 i $@ninlets  [$self look inletfg]
    $self draw_io2 o $@noutlets [$self look outletfg]
}

def Box tips= {tips} {set @tips $tips}

def Box tip {type port} {
	if {[info exists @tips]} {
		set tips $@tips
	} else {
		netsend [list .$@canvas object_get_tips $self]
		#after 500 $self tip $type $port
		set tips ""
	}
	switch $type {
		i {return "inlet $port: [lindex $tips $port]"}
		o {return "outlet $port"}
	}
}

# type is i or o
def Box hilite_io {type x y} {
	mset {x1 y1 x2 y2} [$self bbox]
	set xs [expr $x2-$x1]
	set c [$@canvas widget]
	switch $type {i {set ports $@ninlets} o {set ports $@noutlets}}
	set port -1
	set iowidth [$self look iowidth]

	for {set n 0} {$n<$ports} {incr n} {
		set tag $self$type$n
		set area [lmap / [$c bbox $self$type$n] [$@canvas zoom]]
		set center [expr ([lindex $area 2] + [lindex $area 0]) / 2 ]
		set dist [expr abs($x - $center)]
		if {$dist < [expr ($iowidth/2)+5] && $dist > 0} {set port $n}
	}

	if {$ports==0 | $port==-1} return
	if {$port >= $ports} {set port [expr $ports-1]}
	$self hilite_io_2 $type $port
	if {[$self look tooltip]} {$@canvas show_tooltip $x $y [$self tip $type $port] $type}
	return $port
}

def Box update_hilite_io {} {
	if {![llength $@ioselect]} {return}
	set type [lindex $@ioselect 1]
	set zoom [$@canvas zoom]
	set port [lindex $@ioselect 0]
	set p $type$port
	$self hilite_io_2 $type $port
}

def Box hilite_io_2 {type port} {
	set outline [switch $type {i {concat [$self look outletfg]} o {concat [$self look inletfg]}}]
	set box [l+ [$self io_bbox $type $port] [list -3 -3 +3 +3]]
	$self item $type${port}b rectangle $box -outline $outline -width 1
}

#def Box popup_help {} {netsend [list pd help $@pdclass]}
def Box popup_help {} {netsend [list .$@canvas object_help $self]}

def Box show_error {text} {
	regsub "\n" $text "" text
	#mset {x1 y1 x2 y2} [$self bbox]
	#[$self get_canvas] show_tooltip [expr $x2+4] [expr ($y1+$y2)/2] $text object 1
	mset {x1 y1 x2 y2} [$self bbox]
	mset {x y} [rect_centre [$self io_bbox i 0]]
	[$self get_canvas] show_tooltip $x $y $text i 1
}

def Canvas macro_event_append {e obj} {
	if {![llength $@macro_q]} {after $@macro_delay [list $self macro_schedule $@macro_delay] $obj}
	lappend @macro_q $e
}

def Canvas get_clipboard {obj} {
	puts "get clipboard"
	set content [clipboard get]
	set l {}; set s ""; set space " ";
	set last_newline [string last ";" $content]
	#foreach char [split $content ""] {lappend l [scan $char %c]}
	set i 0
	foreach char [split $content ""] {
		if {$i == $last_newline} {break}
		#if {$char == ";"} {set s ${s}${space}list} {set s ${s}$char}
		if {$char != ";"} {
			if {$char == "\n"} {
				set s ${s}${space}$char
			} else {
				set s ${s}$char
			}
		}
		incr i
	}
	netsend [concat [list .$obj clipboard_set] $s]
}


def Canvas macro_schedule {delay obj} {
	if {[llength $@macro_q]} {
		set w [focus]
		set m [lindex $@macro_q 0]
		set fudge 0
		mset {event x y mode k} $m
		switch $event {
			key {set name [modekey $k $mode]; set fudge 1}
			click {set name [modeclick $k $mode ButtonPress]; set fudge 1}
			unclick {set name [modeclick $k $mode ButtonRelease]; set fudge 1}
			bang {
				after $delay [list $self macro_schedule $@macro_delay] $obj
				netsend [list .$obj mbang]
				set @macro_q [lreplace $@macro_q 0 0]
				return
			}
			default {puts "Error: this event $event should not have been here.."}
		}
		if {$fudge} {event generate $w <Motion> -x $x -y $y}
		event generate $w <$name> -x $x -y $y
		#puts "event generate $w <$name> -x $x -y $y"
		if {$event=="key"} {event generate $w <KeyRelease-$k> -x $x -y $y}
		set @macro_q [lreplace $@macro_q 0 0]
		after $delay [list $self macro_schedule $@macro_delay] $obj
	}
}

def Canvas foobar {} {$self macro_schedule 1000}

def Canvas macro_q {} {puts "$@macro_q"}
#-----------------------------------------------------------------------------------#
class_new Wire {View}

def Wire canvas= {c} {
	super $c
	mset {from outlet to inlet} $@connects
	set children [$c objects]
	set @from [$children get $from]
	set   @to [$children get $to]
	$@from wires2+= $self; $@to wires2+= $self
}

def  Box index= {i} {super $i; if {$@canvas != ""} {[$@canvas objects] set $i $self}}
def Wire index= {i} {super $i;                      [$@canvas   wires] set $i $self }

def Wire init {mess} {
	super
	$self reinit $mess
}

def Wire reinit {mess} {
	if {[$::macro state]} {$::macro append_ref $mess}
	mset {x msg from outlet to inlet canvas} $mess
	set @connects [list $from $outlet $to $inlet]
	set @outlet $outlet
	set @inlet $inlet
	$self outside_of_the_box
}

def Wire   from {} {return $@from}
def Wire outlet {} {return $@outlet}
def Wire     to {} {return $@to}
def Wire  inlet {} {return $@inlet}
def Wire move {dx dy} {$self changed}

# DON'T do the former, it's so horribly slow
#def View draw_wires {} {foreach wire $_($@canvas:wires) {$wire changed}}
def View draw_wires {} {foreach wire $@wires2 {$wire changed}}

def Wire bbox {} {
	set from $@from; set outlet $@outlet
	set   to $@to;   set  inlet  $@inlet
	set zoom [$@canvas zoom]
	set c [$@canvas widget]
	mset {x1 y1} [lmap / [rect_centre [$c bbox ${from}o${outlet}]] $zoom]
	mset {x2 y2} [lmap / [rect_centre [$c bbox   ${to}i${inlet} ]] $zoom]
	list $x1 $y1 $x2 $y2
}

def Wire xy {} {
	mset {x1 y1 x2 y2} [$self bbox]
	list [expr $x1 + (($x2-$x1)*0.05)] [expr $y1 + (($y2-$y1)*0.05)]
}

def Wire report {} {list $@from $@outlet $@to $@inlet}
def Wire connects {} {return $@connects}
proc xys {x1 y1 x2 y2} {
	return [list $x1 $y1 $x2 $y2] ;# just a straight line, no frills
	set r {}
	lappend r $x1 $y1
	set dx [expr $x2-$x1]
	set dy [expr $y2-$y1]
	set d [expr sqrt($dx*$dx+$dy*$dy)]
	set n [expr 1+$d/10]
	for {set i 1} {$i<$n} {incr i} {
		set w $i*($n-$i)/(0.0+$n*$n)
		lappend r [expr $x1 + $dx*$i/$n + $dy*(rand()-0.5)*$w]
		lappend r [expr $y1 + $dy*$i/$n - $dx*(rand()-0.5)*$w]
	}
	lappend r $x2 $y2
	return $r
}

def Wire draw {} {
	set zoom [$@canvas zoom]
	set c [$@canvas widget]
	set iowidth [$@from look iowidth]
	mset {ox1 oy1 ox2 oy2} [$@from io_bbox o $@outlet]
	mset {ix1 iy1 ix2 iy2} [  $@to io_bbox i $@inlet]
	set x1 [expr ($ox1+$ox2)/2.0]; set y1 $oy2
	set x2 [expr ($ix1+$ix2)/2.0]; set y2 $iy1
	set xys [xys $x1 $y1 $x2 $y2]
	set length [expr sqrt(pow($x2-$x1,2)+pow($y2-$y1,2))]
	# how to customise the arrow size/shape?
	set arrowsize [expr $length<100 ? $length/10 : 10]
	if {$arrowsize < 5} {set arrow none} {set arrow last}
	set arrowshape [list $arrowsize [expr $arrowsize*4/5] [expr $arrowsize/3]]
	set wire_width [$self look thick]
	set wire_color [$self look fg]
	if {[$self selected?]} {
	    set wire_color [$self look fg2] ;# fg2 should be renamed
	} else {
	    if {[info exists _($@from:text)] && [info exists _($@to:text)]} {
		if {[regexp -nocase {~$} [lindex $_($@from:text) 0]] && \
		    [regexp -nocase {~$} [lindex   $_($@to:text) 0]]} {
			set wire_width [expr $wire_width*2]
		}
	    }
	}
	set options {}
	if {[$self look wirearrow]} {lappend options -arrow $arrow -arrowshape $arrowshape}
	eval [concat [list $self item WIRE line $xys -width $wire_width -smooth yes -fill $wire_color] $options]
}

def Wire delete {} {
	$self unsubscribe $@canvas
	$@from delete_wire $self
	$@to   delete_wire $self
	[$@canvas wires] unset $@index
	super
}

def Wire popup_insert {} {
	if {![llength [$@canvas selection_wire]]} {$@canvas selection_wire= $self}
	mset {x y} [$@canvas insertxy]
	$@canvas do_insert_obj $x $y
}

#-----------------------------------------------------------------------------------#
############ colouring

# equivalent to color* #c0c0c0 $c
proc darker {c} {
	scan $c #%02x%02x%02x r g b
	set r [expr $r*3/4]
	set g [expr $g*3/4]
	set b [expr $b*3/4]
	return [format #%02x%02x%02x $r $g $b]
}

proc brighter {c} {
	scan $c #%02x%02x%02x r g b
	set r [min 255 [expr $r*4/3]]
	set g [min 255 [expr $g*4/3]]
	set b [min 255 [expr $b*4/3]]
	return [format #%02x%02x%02x $r $g $b]
}

proc randomise_color {c} {
	scan $c #%02x%02x%02x r g b
	set r [clip [expr $r+int(rand()*65)-32] 0 255]
	set g [clip [expr $g+int(rand()*65)-32] 0 255]
	set b [clip [expr $b+int(rand()*65)-32] 0 255]
	return [format #%02x%02x%02x $r $g $b]
}

proc parse_color {c} {
	regsub {;$} $c {} c
	if {$c<0} {
		set c [expr ~$c]
		set r [expr round((($c>>12)&63)*255/63)]
		set g [expr round((($c>> 6)&63)*255/63)]
		set b [expr round((($c>> 0)&63)*255/63)]
		return [format #%02x%02x%02x $r $g $b]
	} {return #[lindex $::preset_colours2 $c]}
}

proc unparse_color {c} {
	if {[string index $c 0]=="#"} {set c [string replace $c 0 0 0x]}
	set r [expr round((($c>>16)&255)*63/255)]
	set g [expr round((($c>> 8)&255)*63/255)]
	set b [expr round((($c>> 0)&255)*63/255)]
	return [expr ~0[format %02o%02o%02o $r $g $b]]
}

############ data transfer
# note: @pdclass is the server-side class name
#  and   @_class is the client-side class name

# abstract classes
set fields1 {foo bar x1 y1 class}
set fields2 {snd rcv lab ldx ldy fstyle fs bcol fcol lcol}

# real classes
set fields(tgl)    [eval list $fields1    w                  isa   $fields2 on nonzero]
set fields(bng)    [eval list $fields1    w hold break       isa   $fields2]
set fields(nbx)    [eval list $fields1    w h min max is_log isa   $fields2 val log_height]
set fields(hsl)    [eval list $fields1    w h min max is_log isa   $fields2 val steady]
set fields(hradio) [eval list $fields1    w change           isa n $fields2 on]
set fields(vu)     [eval list $fields1    w h        rcv lab ldx ldy fstyle fs bcol lcol scale isa]
set fields(cnv)    [eval list $fields1 hh w h    snd rcv lab ldx ldy fstyle fs bcol lcol       isa]
set fields(vsl)    $fields(hsl)
set fields(vradio) $fields(hradio)
set fields(hdl)    $fields(hradio)
set fields(vdl)    $fields(hradio)
set fields(coords)     {foo bar xfrom yfrom xto yto w h gop x1 y1} ;# goes with #N canvas
set fields(floatatom)  {foo bar x1 y1 w min max pos lab rcv snd}
set fields(symbolatom) {foo bar x1 y1 w min max pos lab rcv snd}
set fields(array)      {name n elemtype flags}

proc classinfo {pdclass _class} {
	global classinfo classinfo2
	set classinfo($pdclass) $_class
	set classinfo2($_class) $pdclass
}

# basic patchables
classinfo obj        ObjectBox
classinfo message    MessageBox
classinfo floatatom  FloatBox
classinfo symbolatom SymbolBox
classinfo text       Comment

# non-patchables (scalars, arrays, ...)
classinfo array    Array

# GUI patchables
classinfo bng      Bang
classinfo tgl      Toggle
classinfo nbx      NumBox
classinfo hsl      Slider
classinfo vsl      Slider
classinfo vu       Vu
classinfo hradio   Radio
classinfo vradio   Radio
classinfo hdl      Radio
classinfo vdl      Radio
classinfo canvas   Canvas
classinfo cnv      Cnv
classinfo display    Display

# remember, _($foo:$bar) notation should die
# because objects ought to be autonomous.

# in array objects, number of inlets is bogus?
#X array array1 1 float 3;
#A 0 0;

def View index= {i} {set @index $i}
def View index {} {return $@index}

proc change {self canvas index e {ninlets 0} {noutlets 0} {valid 1}} {
	foreach mess [pd_mess_split $e] {change_2 $self $mess}
	#the server ought to take care of this:
	#if {[lindex $e 1] == "array"} {set ninlets 0; set noutlets 0}
	if {$canvas != "x0"} {$self   canvas= $canvas}
	$self    index= $index
	$self  ninlets= $ninlets
	$self noutlets= $noutlets
	if {[$self class] == "ObjectBox"} {$self valid= $valid}
	if {[$self class] == "Canvas"} {
		if {[$self subpatch]} {$self valid= $valid}
		if {[$self gop]} {$self valid= $valid}
		if {[$self abs]} {$self valid= $valid}
	}
	$self changed
}

proc change_2 {self mess} {
	set isnew [expr ![info exists ::_($self:_class)]]
	switch -- [lindex $mess 0] {
		"#N" {if {$isnew} {Canvas new_as $self $mess} else {$self reinit $mess}}
		"#X" {
			set i 1
			# would it be possible to merge floatatom,symbolatom as gatom ?
			switch -- [lindex $mess 1] {
				obj        {set class [lindex $mess 4]}
				msg        {set class message}
				default    {set class [lindex $mess 1]}}
			if {[info exists ::classinfo($class)]} {
				set _class [lindex $::classinfo($class) 0]
			} else {
				if {[lindex $mess 1] == "connect"} {
					set _class Wire
				} else {
					set _class ObjectBox
				}
			}
			if {$isnew} {$_class new_as $self $mess} else {$self reinit $mess}
			switch -- $class {
				floatatom  {set class gatom}
				symbolatom {set class gatom}
				array {$self length= [lindex $mess 3]; $self name= [lindex $mess 2]}
				default {$self position= [lrange $mess 2 3]}}
			$self pdclass= $class
		}
		"#A" {
			#post "#A: $mess"
			$self array_set [lrange $mess 2 end]
		}
		"#V" {
			#post "#V: $mess"
		}
		default {if {$mess != ""} {error "what you say? �[lindex $mess 0]� in �$mess�"}}
	}
}

#proc pasting_count {self} {
#	global paste _
#	if {$paste(count2) != $paste(count)} {incr paste(count2)}
#}

# split at message boundaries and atom boundaries, returning a list of lists of atoms.
# spaces    get  temporary value \x01
# A_SEMI    gets temporary value \x02
# backslash gets temporary value \x03
# A_COMMA is not handled yet
proc pd_mess_split {s} {
	set s [regsub -all {\\\\} $s "\x03"]
	set s [regsub -all {(^|[^\\]);}          $s "\\1\x02"]
	set s [regsub -all {(^|[^\\])[\s\n]+}    $s "\\1\x01"]
	set s [regsub -all {^\n}                 $s "\x01"] ;# oops
	set s [regsub -all {\\([\\; \{\}])} $s "\\1\\2"]
	set s [regsub -all \x03 $s \\]
	set r {}
	foreach m [split $s \x02] {
		set m [regsub -all "\x01+" $m "\x01"]
		set m [regsub -all "^\x01" $m ""]
		set t [split $m \x01]
		lappend r $t
	}
	return $r
}

proc canonical_list {list} {
	set r {}
	foreach e $list {lappend r $e}
	return $r
}

proc pd_mess_split_want== {a b} {
	set b [canonical_list $b]
	set c [pd_mess_split $a]
	if {[string compare $c $b]} {
		puts "[VTred]string     �$a�\nparses to  �$c�\ninstead of �$b�[VTgrey]"
	} else {
		puts "[VTgreen]string     �$a� OK[VTgrey]"
	}
}

if 0 {
	pd_mess_split_want== {foo;bar;baz;a\;;b\\;c\\\;;d\\\\;e} {foo bar baz {{a;}} {b\\} {{c\;}} {{d\\}} e}
	pd_mess_split_want== {foo\ bar} {{{foo bar}}}
	pd_mess_split_want== {foo \  bar\ foo\ bar foo} {{foo { } {bar foo bar} foo}}
	pd_mess_split_want== {\\ \\\\ \\\\\\ \ \\\ \\\\\ one} [list [list "\\" "\\\\" "\\\\\\" "\ \\\ \\\\\ one"]]
	pd_mess_split_want== "\n  \n   \n  foo" foo
	pd_mess_split_want== "\\\x7b\\\x7b\\\x7b\\\x7b" [list [list "\x7b\x7b\x7b\x7b"]]
	pd_mess_split_want== "\\\x7d\\\x7d\\\x7d\\\x7d" [list [list "\x7d\x7d\x7d\x7d"]]
	exit
}

############ rendering

class_new MessageBox {TextBox}

def MessageBox init {mess} {
	super $mess
	set @w 15 ;# this is useless?
	set @xs $@w
	set @ys $@w ;# this is a bug
}

def MessageBox draw_box {} {
	mset {x1 y1} [$self xy]
	set x2 [expr $x1+$@xs]
	set y2 [expr $y1+$@ys]
	set points [list $x1 $y1 [expr $x2+4] $y1 $x2 [expr $y1+4] $x2 [expr $y2-4] [expr $x2+4] $y2 $x1 $y2 $x2 $y2 $x1 $y2]
	if {[$self selected?]} {set frcol [$self look selectframe]} {set frcol [$self look frame3]}
	$self item BASE polygon $points -fill [$self look bg] -outline $frcol -width 1
	[$@canvas widget] lower ${self}BASE ${self}TEXT
	[$@canvas widget] raise $self
}

def MessageBox draw {} {
	super
	$self draw_io
}

def MessageBox click {x y f target} {
	$self bang 1
	netsend [list .$self bang]
	after 150 $self bang 0
}

def MessageBox bang {flag} {
	if {$flag} {set color #ffff00} {set color [$self look bg]}
	[$@canvas widget] itemconfigure ${self}BASE -fill $color
}

# it was class_new AtomBox {View Box}, which is wrong because already Box<View
# it shouldn't have mattered, but super doesn't support proper pruning yet
#class_new AtomBox {Box}
class_new AtomBox {Labelled TextBox}
def AtomBox label_xy {} {
	mset {x1 y1} [$self xy]
	switch -- $@pos {
	  0 {list [expr {$x1-3}] [expr {$y1+2}]}
	  1 {list [expr {$x1+2}] [expr {$y1+2}]}
	  2 {list [expr {$x1-1}] [expr {$y1-1}]}
	  3 {list [expr {$x1-1}] [expr {$y1+3}]}
	}
}
def AtomBox label_anchor {} {
	switch -- $@pos {
	  0 {return ne}
	  1 {return nw}
	  2 {return sw}
	  3 {return nw}
	}
}
def AtomBox label_font {} {list Courier 10 bold}
def AtomBox label_color {} {return -1}

def AtomBox draw_box {} {
	$self update_size
	mset {x1 y1 x2 y2} [$self bbox]
	set points [list $x1 $y1 [expr $x2-4] $y1 $x2 [expr $y1+4] $x2 $y2 $x1 $y2]
	if {[$self selected?]} {set frcol [$self look selectframe]} {set frcol [$self look frame3]}
	$self item BASE polygon $points -fill [$self look bg] -outline $frcol
	[[$self get_canvas] widget] lower ${self}BASE ${self}TEXT
	$self draw_io
}

def AtomBox clear {var} {set @clear $var}
def AtomBox clear= {} {return $@clear}

def AtomBox filter_text {{for_edit 0}} {
	if {$for_edit} {return ""}
	if {[string length $@text] <= $@w} {return $@text}
	return [string range $@text 0 [expr $@w-1]]
}

def AtomBox update_size {} {
	set width [font measure [$self look font] 0]
	set ls [font metrics [$self look font] -linespace]
	set @xs [expr ($width*$@w)+3]
	set @ys [expr $ls+3]
}

class_new Comment {TextBox}

def Comment draw_box {} {
	super
        mset {x1 y1} [$self xy]
	set x2 [expr $x1+$@xs]
        set y2 [expr $y1+$@ys]
        set xya [list $x1 $y1 $x2 $y2]
	set xyb [l+ [list $x2 $y1 $x1 $y1 $x1 $y2] [list -1 +1 +1 +1 +1 -1]]
	set xyc [l+ [list $x2 $y1 $x2 $y2 $x1 $y2] [list -1 +1 -1 -1 +1 -1]]
	if {[$@canvas editmode]} {
		if {[$self selected?]} {set frcol [$self look selectframe]} {set frcol [$self look frame3]}
		$self item BASE  rectangle $xya -fill [$self look bg] -outline $frcol
	} else {
		$self item_delete BASE
	}
	#$self item BASE1 line      $xyb -fill [$self look frame1]
	#$self item BASE2 line      $xyc -fill [$self look frame2]
	if {[$@canvas editmode]} {
		[$@canvas widget] lower ${self}BASE ${self}TEXT
		#[$@canvas widget] raise ${self}BASE1 ${self}BASE
		#[$@canvas widget] raise ${self}BASE2 ${self}BASE
	}
}

class_new Display {Box}

def Display height= {val} {set @height $val}

def Display init {{mess {}}} {
	set font [$self look font]
	set fw [font measure $font 0]
	set @max_width 40; #in chars
	set @wrap [expr $fw*$@max_width]; #in pixels
	set @content {display}
	set @height 1
	set @xs [expr [font measure [$self look font] 0]+3]
	set @ys [font metrics [$self look font] -linespace]
	set @textoffset [list 2 2]
	netsend [list .$self height]
	super $mess
}

def Display draw {} {
	super
	set font [$self look font]
	mset {x y} [$self xy]
	mset {xf yf} $@textoffset
	set fh [font metrics [$self look font] -linespace]
	set text [lindex $@content 0];
	for {set i 1} {$i < $@height} {incr i} {set text ${text}\n[lindex $@content $i]}
	set h 0; set w 0
	foreach line $@content {
		set tw [font measure $font $line]
		set h [expr int(ceil($tw/$@wrap.0)+$h)]
		set w [min [max $w $tw] $@wrap]
	}
	set h [max $h 1]
	$self item BASE rect [list $x $y [expr $x+$w+$xf+2] [expr $y+($@ys*$h)+$yf+1]] -fill [$self look bg]
	$self item TEXT text [l+ $@textoffset [$self xy]] -font $font -text $text -fill [$self look fg] -anchor nw -width $@wrap
	$self draw_io
}

def Display dis {text} {
	lappend @content $text
	if {[llength $@content] > $@height} {set @content [lrange $@content 1 end]}
	$self changed
}

class_new IEMGUI {}
def IEMGUI text {} {
	return [$self class]
}
class_new BlueBox {Labelled IEMGUI Box}
#class_new BlueBox {Box Labelled}

def BlueBox draw_box {} {
	super
	set xya [$self bbox]
	mset {x1 y1 x2 y2} $xya
	set color [parse_color $@bcol]
	if {[$self selected?]} {set frcol [$self look selectframe]} {set frcol [$self look frame3]}
	$self item BASE  rectangle $xya -fill $color -outline $frcol
	#below lines draws the 3d box edge
	#incr x1 +1; incr y1 +1; incr x2 -1; incr y2 -1
	#$self item BASE2 line [list $x2 $y1 $x1 $y1 $x1 $y2] -fill #ffffff
	#$self item BASE3 line [list $x2 $y1 $x2 $y2 $x1 $y2] -fill [darker $color]
	$self draw_io
}

def IEMGUI popup_properties {} {IEMPropertiesDialogue new $self}

class_new PropertiesDialogue {Dialogue}

def PropertiesDialogue init {of} {
	super cancel apply ok
	set @of $of
	set f .$self
	checkbutton $f.auto_apply -text [say auto_apply] -anchor w -variable @auto_apply
	frame $f.buttonsep2 -height 2 -borderwidth 1 -relief sunken
	pack $f.auto_apply $f.buttonsep2 -side bottom -fill x
	bind $f <KeyPress-Return> "break";#so that Return don't call do_auto_apply after Dialogue ok
	#bind $f <KeyPress>        [list $self do_auto_apply] ;# too wide: may cause Ok/Cancel to spit errors
	#bind $f <ButtonRelease>   [list $self do_auto_apply] ;# too wide: may cause Ok/Cancel to spit errors
	$self non_resizable
}
class_new IEMPropertiesDialogue {PropertiesDialogue}

def IEMGUI properties_apply {list {orient -1}} {
	set orig [list $self properties_apply]
	foreach var [lrange $::fields($@class) 5 end] {lappend orig $@$var}
	[$@canvas history] add $orig
	foreach v $list {switch -- $v {{} {set v "empty"}}; lappend props $v}
	netsend [concat [list .$self reload] $props]
	if {$orient >= 0} {
		netsend [list .$self orient $orient]
	}
}

def IEMPropertiesDialogue apply {} {
	set class $_($@of:class)
	set props {}
	foreach var [lrange $::fields($class) 5 end] {
		set v $@$var
		if {[regexp -nocase {^[bfl]col$} $var]} {set v [unparse_color $v]}
		lappend props $v
	}
	if {[[$@of class] <= Slider] || [[$@of class] <= Radio]} {
		$@of properties_apply $props $@orient
	} else {
		$@of properties_apply $props
	}
}

def IEMPropertiesDialogue init {of} {
	super $of
	set @class $_($of:class)
	wm title .$self "\[$@class\] [say popup_properties]"
	if {![info exists ::fields($@class)]} {set class obj}
	foreach var $::fields($@class) {
		set val $_($of:$var)
		switch -- $val { empty {set val ""}}
		if {[regexp -nocase {^([a-z])col$} $var]} {set val [parse_color $val]}
		set @$var $val
	}
	if {[[$of class] <= Slider] || [[$of class] <= Radio]} {
		set @orient $_($of:orient)
		$self add .$self [list orient choice -choices {horizontal vertical}]
	}
	foreach prop [lrange $::fields($@class) 5 end] {
		set d [concat [list $prop] [switch $prop {
			w      {list integer -width 7}
			h      {list integer -width 7}
			hold   {list float   -width 9}
			break  {list float   -width 9}
			min    {list float   -width 9}
			max    {list float   -width 9}
			is_log {list choice  -choices {linear logarithmic}}
			isa    {list choice  -choices {no yes}}
			n      {list integer -width 4}
			steady {list choice  -choices {steady_no steady_yes}}
			snd    {list entry   -width 20}
			rcv    {list entry   -width 20}
			lab    {list entry   -width 20}
			ldx    {list integer -width 5}
			ldy    {list integer -width 5}
			fstyle {list choice  -choices {Courier Helvetica Times}}
			fs     {list fontsize -width 5}
			bcol   {list color}
			fcol   {list color}
			lcol   {list color}
			val    {continue}
			on     {continue}
			change {list choice -choices {no yes}}
			nonzero {list float -width 9}
			log_height {list float -width 9}
			hh {continue}
			scale {list toggle}
			default {error "huh? ($prop)"}
		}]]
		$self add .$self $d
	}
}

def IEMPropertiesDialogue dropmenu_open {f name} {super $f}
def IEMPropertiesDialogue dropmenu_set {frame var part val} {
	switch $var {
		orient {if {[$@of class] == "Slider"} {set tmp $@h; set @h $@w; set @w $tmp}}
		default {}
	}
	set tmp ${var}choices
	set textvar ${var}2
	set @$textvar [say [lindex $@$tmp $val]]
	super $frame $var $part $val
	$self do_auto_apply
}

class_new CanvasPropertiesDialogue {PropertiesDialogue}

def CanvasPropertiesDialogue init {of} {
	super $of
	set @canvas $of
	wm title .$self "[say canvas] [say popup_properties]"
	set @gop [$of gop]
	set @properties [list "gop" "xfrom" "xto" "yfrom" "yto" "width" "height" "xmargin" "ymargin"]
	set mess [$of get_mess]
	mset [list @xfrom @yfrom @xto @yto @width @height @xmargin @ymargin] $mess
	if {!$@width} {set @width 85}; if {!$@height} {set @height 60}
	$self add .$self [list gop toggle -command "$self gop_setting"]
	for {set i 1} {$i<[llength $@properties]} {incr i} {
		$self add .$self [list [lindex $@properties $i] integer -width 7]
	}
	$self gop_setting
}

def CanvasPropertiesDialogue gop_setting {} {
	set entries [lrange $@properties 1 end]
	foreach entry $entries {
		if {!$@gop} {
			.$self.$entry.entry configure -state disable
		} else {
			.$self.$entry.entry configure -state normal
		}
	}
}

def CanvasPropertiesDialogue apply {} {
	if {![$@canvas editmode]} {$@canvas editmode= 1}
	netsend [list .$@of coords $@xfrom $@yfrom $@xto $@yto $@width $@height $@gop $@xmargin $@ymargin]
}

proc gatom_escape {sym} {
	if {[string length $sym] == 0} {return "-"}
	if {[string equal -length 1 $sym "-"]} {return [string replace $sym 0 0 "--"]}
	return $sym
}

proc gatom_unescape {sym} {
	if {[string equal -length 1 $sym "-"]} {return [string replace $sym 0 0 ""]}
	return $sym
}

class_new BoxPropertiesDialogue {PropertiesDialogue}
def Box popup_properties {} {BoxPropertiesDialogue new $self}
def Box popup_clear_wires {} {[$self canvas] selection= $self; [$self canvas] clear_wires}
def Box popup_remove_from_path {}   {[$self canvas] selection= $self; [$self canvas] remove_obj_from_path}
def Box popup_delete_from_path {}   {[$self canvas] selection= $self; [$self canvas] delete_obj_from_path}
def BoxPropertiesDialogue init {of} {
	super $of
	wm title .$self "Box Properties"
	pack [label .$self.huh  -text "huh..."]
	pack [label .$self.huh2 -text "this is where some #V properties should go"]
}

class_new WirePropertiesDialogue {PropertiesDialogue}
def Wire popup_properties {} {WirePropertiesDialogue new $self}
def WirePropertiesDialogue init {of} {
	super $of
	wm title .$self "Wire Properties"
	pack [label .$self.huh  -text "huh..."]
	pack [label .$self.huh2 -text "this is where some #V properties should go"]
}

class_new GAtomPropertiesDialogue {PropertiesDialogue}

def AtomBox popup_properties {} {GAtomPropertiesDialogue new $self}

# this is buggy due to miller's escapes vs iem's escapes.
def GAtomPropertiesDialogue apply {} {
	netsend [list .$@of reload $@w $@min $@max $@pos [gatom_escape $@lab] [gatom_escape $@rcv] [gatom_escape $@snd]]
}

def GAtomPropertiesDialogue init {of} {
	super $of
	foreach var {w min max pos} {set @$var $_($of:$var)}
	foreach var {lab rcv snd} {set @$var [gatom_unescape $_($of:$var)]}
	wm title .$self "Atom"
	global properties
	$self add .$self \
	    {w   integer -width  4} \
	    {min float   -width  8} \
	    {max float   -width  8} \
	    {lab symbol  -width 20} \
	    {pos side             } \
	    {snd symbol  -width 20} \
	    {rcv symbol  -width 20}
	#foreach name {w min max} {bind .$self.$name.entry <KeyPress-Return> "$self ok"}
	.$self.w.entry select from 0
	.$self.w.entry select adjust end
	focus .$self.w.entry
}

class_new GraphPropertiesDialogue {Dialogue}

def GraphPropertiesDialogue apply {} {
	netsend [list .$@of dialog $@x1 $@y1 $@x2 $@y2 $@xpix $@ypix]
}

def GraphPropertiesDialogue init {of} {
	super $of
	foreach var {x1 y1 x2 y2 xpix ypix} {set @$var $_($of:$var)}
	wm title .$self "Graph"
	pack [label .$self.label -text "GRAPH BOUNDS"] -side top
	global properties
	$self add .$self {
		{x1   integer -width 7} \
		{x2   integer -width 7} \
		{xpix entry -width 7} \
		{y2   integer -width 7} \
		{y1   integer -width 7} \
		{ypix entry -width 7}
	}
	#.$self.xrangef.x2 select from 0
	#.$self.xrangef.x2 select adjust end
	#focus .$self.xrangef.x2
}

class_new ArrayPropertiesDialogue {Dialogue}

def ArrayPropertiesDialogue apply {} {
	regsub {^\$} $@name "#" name
	netsend [list .$@apply arraydialog $name $@n $@saveit $@otherflag]
}

def ArrayPropertiesDialogue init {of} {
	super $of
	foreach var {name n saveit} {set @$var $_($of:$var)}
	set @otherflag 0
	wm title $id "[say array] [say popup_properties]"
	$self add .$self {name entry} {n entry}
	pack [checkbutton .$self.saveme -text "save contents" -variable @saveit -anchor w] -side top
	if {$newone} {
		pack [frame .$self.radio] -side top
		foreach {i label} {0 "in new graph" 1 "in last graph"} {
			pack [radiobutton .$self.radio.radio$i -value $i -variable @otherflag -text $label] -side top -anchor w
		}
	} else {
		pack [checkbutton .$self.deleteme -text "delete me" -variable @otherflag -anchor w] -side top
	}
	if {$newone} {.$self.buttonframe.apply configure -state disabled}
	bind .$self.name.entry <KeyPress-Return> "$self ok"
	bind .$self.n.entry    <KeyPress-Return> "$self ok"
	.$self.name.entry select from 0
	.$self.name.entry select adjust end
	focus .$self.name.entry
}

class_new FloatBox  {AtomBox}
class_new SymbolBox {AtomBox}

def AtomBox init {mess} {
	super $mess
	set @clickpos {}
	set width [font measure [$self look font] W]
	set height [font metrics [$self look font] -linespace]
	set @xs [expr ($width*$@w)+3]
	set @ys [expr $height+3]
}

def FloatBox init {mess} {super $mess; set @text 0;}

def FloatBox calc {x y x1 y1} {
	#puts "$@min $@max"
	if {!$@min && !$@max} {
		set d [expr $@ovalue+($y1-$y)*$@rate]
	} else {
		set span [expr $@max-$@min]
		set l [expr $@max-$@min]
		set d [clip [expr $@ovalue+(($y1-$y)*$span/($l+0.0))*$@rate] $@min $@max]
	}
	return $d
}

def SymbolBox init {mess} {super $mess; set @text "symbol"}

def AtomBox set {val} {set @text [format "%g" $val]; $self changed}

def AtomBox setto {text} {
	[$@canvas widget] configure -cursor {}
	if { [string is double $text]} {
		set @text $text; #so that the text gets updated immediately
		netsend [list .$self float  $text]
		#[$self get_canvas] selection-= $self
	}
}
def SymbolBox set {text} {set @text $text}
def SymbolBox setto {text} {
	[$@canvas widget] configure -cursor {}
	if {![string is double $text]} {netsend [list .$self symbol $text]}
}

def FloatBox ftoa {} {
	set f $@val
	set is_exp 0
	if {[string length $@buf]>0} {return $@buf}
	set buf [format %g $f]
	set bufsize [string length buf]
	if {$bufsize >= 5} {
		# exponential mode
		set is_exp [regexp -nocase e]
	}
	if {$bufsize > $@w} {
		# must shrink number
		if {$is_exp} {
			#...
		} {
			#...
		}
	}
	return $buf
}

def AtomBox click {x y f target} {
	set @clickx $x; set @clicky $y
	set canvas [$self get_canvas]
	set t [$canvas widget].${self}text
	set @ovalue [clip $@text $@min $@max]
	set @clickpos [list $x $y]
	set @mouse [list $x $y]
	$canvas focus= $self
	set @rate [expr $f&1 ? 0.01 : 1.00]
}

def AtomBox unclick {x y f target} {
	if {$x == $@clickx && $y == $@clicky} {$self edit}
	[$self get_canvas] focus= ""
}

def AtomBox motion {x y f target} {
	if {$@edit} {return}
	mset {clx cly} $@clickpos
	set @text [$self calc $x $y $clx $cly]
	netsend [list .$self float [expr {0+$@text}]]
}

def AtomBox log_ratio {} {
	set diff [expr $@is_log ? log($@max/$@min) : ($@max-$@min)]
	return [expr $diff / $@max]
}

def AtomBox key_incr {val1 val2} {
	set @text [expr $@text - $val2]
	netsend [list .$self float [expr {0+$@text}]]
}

def SymbolBox motion {x y f target} {}
class_new NumBox {Labelled IEMGUI AtomBox}

def NumBox init {mess} {
	super $mess
	set @clicking 0
	set changed 0
	set @buf ""
	set @text [clip 0 $@min $@max]
}
def NumBox calc {x y x1 y1} {
	set span [expr $@max-$@min]
	set l [expr $@is_log ? $@log_height : ($@max-$@min)]
	set d [expr {($y1-$y)*$span/($l+0.0)}]
	set d [expr $@is_log ? $@ovalue*exp($d*$@rate*[$self log_ratio]) : $@ovalue+$d*$@rate]
	set d [clip $d $@min $@max]
	return $d
}
def NumBox reinit {mess} {
	super $mess
	set @text $@val
}
def NumBox draw {} {
	super
	mset {x1 y1} [$self xy]
	set xs [expr 4+10*$@w]
	set ys $@h
	set x2 [expr $x1+$xs]
	set y2 [expr $y1+$ys]
	set c [[$self get_canvas] widget]
	set points [list $x1 $y1 [expr $x2-4] $y1 $x2 [expr $y1+4] $x2 $y2 $x1 $y2]
	set xt [expr $x1+$ys/2+2]
	set yt [expr $y1+$ys/2+1+$xs/34]
	set points2 [list $x1 $y1 [expr $x1+$ys/2] [expr $y1+$ys/2] $x1 $y2]
	set focused [$self == [$@canvas focus]]
	if {$focused} {set color4 #00ff00} {set color4 [$self look bg]}
	$self item BASE4  polygon $points2 -outline [$self look frame3] -fill $color4
	$c raise ${self}BASE4
}
def NumBox ftoa {} {
	set f $@text
	set is_exp 0
	if {[string length $@buf]>0} {return $@buf}
	set buf [format %g $f]
	set bufsize [string length buf]
	if {$bufsize >= 5} {
		# exponential mode
		set is_exp [regexp -nocase e]
	}
	if {$bufsize > $@w} {
		# must shrink number
		if {$is_exp} {
			#...
		} {
			#...
		}
	}
	return $buf
}
def NumBox unfocus {} {set @buf ""; $self changed}

class_new Radio {BlueBox}
def Radio reinit {mess} {
	super $mess
	switch [lindex $mess 4] {
		hradio  {set @orient 0} hdl {set @orient 0}
		vradio  {set @orient 1} vdl {set @orient 1}
		default {set @orient 0}
	}
}
def Radio bbox {} {
	mset {x1 y1} [$self xy]
	set x2 [expr {$x1+$@w*($@orient ?1:$@n)}]
	set y2 [expr {$y1+$@w*($@orient ?$@n:1)}]
	list $x1 $y1 $x2 $y2
}
def Radio draw {} {
	mset {x1 y1 x2 y2} [$self bbox]
	super
	$self item_delete BUT ;# would be cool if this didn't delete stuff that we're about to redraw.
	for {set i 0} {$i<$@n} {incr i} {
		#set coords [list [expr {$x1+3}] [expr {$y1+3}] [expr {$x1+$@w-3}] [expr {$y1+$@w-3}]]
		set coords [list $x1 $y1 [expr {$x1+$@w}] [expr {$y1+$@w}]]
		$self item [list BUT$i BUT] rectangle $coords -fill [parse_color $@bcol] -outline [parse_color $@fcol]
		if {$@orient} {set y1 [expr {$y1+$@w}]} {set x1 [expr {$x1+$@w}]}
	}
	$self set $@on
}
def Radio set {value} {
	set c [$self get_canvas]
	mset {x1 y1} [$self xy]
	set value [expr round($value)]
	if {$@orient} {set y1 [expr $y1+$@w*$value]} {set x1 [expr $x1+$@w*$value]}
	set coords [list [expr {$x1+3}] [expr {$y1+3}] [expr {$x1+$@w-2}] [expr {$y1+$@w-2}]]
	puts "coords=$coords"
	$self item [list CHECK BUT] rectangle $coords -fill [parse_color $@fcol] -width 0
}
def Radio click {x y f target} {
	mset {x1 y1} [$self xy]
	set i [expr {int($@orient ?$y-$y1-2:$x-$x1-2)/$@w}]
	netsend [list .$self fout $i]
}
def Radio key_incr {val1 val2} {netsend [list .$self fout [expr {$@on-$val2}]]}

# in sliders, @value is the kind of value that goes thru inlets and outlets
# whereas @val is always measured in "centipixels" (unzoomed).
class_new Slider {BlueBox}
def Slider reinit {mess} {
	super $mess
	switch [lindex $mess 4] {
		hsl {set @orient 0}
		vsl {set @orient 1}
	}
	$self update_value
}
def Slider update_value {} {
	set span [expr {$@max-$@min}]
	set l [expr $@orient ?$@h:$@w]
	set @value [expr $@val*$span/($l-1)/100]
	#set t [expr $@val * [$self slider_ratio] * 0.01]
	#set @value [expr $@min*exp($t)]
}
def Slider init {mess} {
	super $mess
	set @oposition $@min
	$self update_value
}
def Slider bbox {} {
	mset {x1 y1} [$self xy]
	if {!$@orient} {
		list [expr {$x1-3}] $y1 [expr {$x1+$@w+2}] [expr {$y1+$@h}]
	} else {
		list $x1 [expr {$y1-2}] [expr {$x1+$@w}] [expr {$y1+$@h+3}]
	}
}
#the value/centipixel ratio
def Slider slider_ratio {} {
	set diff [expr $@is_log ? log($@max/$@min) : ($@max-$@min)]
	return [expr $diff / ($@orient ? ($@h-1) : ($@w-1))]
}
def Slider draw_knob {} {
	mset {x1 y1} [$self xy]
	set l [expr $@orient ?$@h:$@w]
	set span [expr {$@max-$@min}]
	set scaled [expr {$@value*($l-1)/$span}]
	if {$@orient} {
		set y1 [expr $y1+2]
		set y [expr $y1+$@h-$scaled-2]
		set coords [list [expr {$x1+2}] [expr {$y-1}] [expr {$x1+$@w-2}] [expr {$y+1}]]
	} else {
		set x1 [expr $x1-1]
		set x [expr $x1+$scaled]
		set coords [list $x [expr {$y1+2}] [expr {$x+2}] [expr {$y1+$@h-2}]]
	}
	$self item KNOB rectangle $coords -outline red -fill [darker [$self look bg]]
}
def Slider draw {} {
	mset {x1 y1 x2 y2} [$self bbox]
	#if {$@orient} {set y1 [expr $y1-2]} {set x1 [expr $x1-2]}
	#if {$@orient} {set ys [expr $@h+5]} {set xs [expr $@w+5]}
	super
	$self draw_knob
	$self update_value
}
# not used
def Slider draw_notches {} {
	if {$@orient} {
		set thick [clip [expr $xs/3] 1 5]
		set x3 [expr $x1+$xs-$thick/2-2]
		set eighth [expr round($ys/8-1)]
		set coords [list $x3 $y1 $x3 [expr $y1+$ys]]
	} else {
		set thick [clip [expr $ys/3] 1 5]
		set y3 [expr $y1+$ys-$thick/2-2]
		set eighth [expr $xs/8]
		set coords [list $x1 $y3 [expr $x1+$xs] $y3]
	}
	# there were supposed to be 7 notches... i don't remember what happened here.
	$@canvas item NOTCH $coords -dash [list 1 $eighth 1 $eighth] -width $thick -fill [darker [$self look bg]]
}
def Slider click {x y f target} {
	set canvas [$self get_canvas]
	mset {type id detail} [$canvas identify_target $x $y $f]
	if {$type == "label"} {return}
	$canvas focus= $self
	set @click_at [list $x $y]
	set @rate [expr $f&1 ? 0.01 : 1.00]
	if {!$@steady} {
		mset {x1 y1 x2 y2} [$self bbox]
		set t [expr [$self calc $x $y $x1 $y2]*$@rate]
		set @value [expr $@is_log ? [expr $@min*exp($t*[$self slider_ratio])] : $t]
		set @oposition $t
		netsend [list .$self float $@value]
	} else {set @oposition $@value}
}
def Slider unclick {x y f target} {
	### keep focus if only clicked. do we want that feature?
	# if {[distance $@click_at [list $x $y]] == 0} {return}
	set canvas [$self get_canvas]
	$canvas focus= ""
}
def Slider motion {x y f target} {
	set canvas [$self get_canvas]
	set focused [$self == [$canvas focus]]
	if {!$focused} {return}
	mset {clx cly} $@click_at
	set d [$self calc $x $y $clx $cly]
	set t ($@oposition+$d*$@rate)
	set value [expr $@is_log ? [expr $@min*exp($t*[$self slider_ratio])] : $t]
	set out [clip $value $@min $@max]
	netsend [list .$self float $out]
}
def Slider key_incr {val1 val2} {
	set @value [expr {$@value-$val2}]
	netsend [list .$self float $@value]
}
def Slider calc {x y x1 y1} {
	set span [expr {$@max-$@min}]
	set l [expr {$@orient ?$@h:$@w}]
	set d [expr {($@orient ?$y1-$y:$x-$x1)*$span/($l+0.0)}]
	return $d
}
def Slider unfocus {} {$self draw}

class_new Labelled {}
def Labelled draw {} {
	super
	mset {lx ly} [$self label_xy]
	set label $@lab; switch -- $label {empty {set label ""} - {set label ""}}
	set lfont [$self label_font]
	set lcolor [parse_color [$self label_color]]
	if {$::leet} {
		    set text [string map -nocase {a 4 e 3 t 7 s 5 i 1 o 0 g 9} $label]
	    } else {
		    set text $label
	    }
	$self item LABEL text [list $lx $ly] -text $text -anchor [$self label_anchor] -font $lfont -fill $lcolor
}
def Labelled label_xy {} {mset {x1 y1} [$self xy]; list [expr {$x1+$@ldx}] [expr {$y1+$@ldy}]}
def Labelled label_anchor {} {return w}
def Labelled label_font {} {list [lindex {courier helvetica times} $@fstyle] $@fs bold}
def Labelled label_color {} {return $@lcol}

#-----------------------------------------------------------------------------------#
class_new Bang {BlueBox}
def Bang init {mess} {
	super $mess
	set @flash 0
	set @count 0
}
def Bang bbox {} {
	mset {x1 y1} [$self xy]
	list $x1 $y1 [expr {$x1+$@w}] [expr {$y1+$@w}]
}
def Bang draw {} {
	super
	mset {x1 y1 x2 y2} [$self bbox]
	set rect [list [expr {$x1+1}] [expr {$y1+1}] [expr {$x2-1}] [expr {$y2-1}]]
	if {$@flash} {
		#$self item BUT oval $rect -fill [parse_color $@fcol]
		set fcol []
		set bcol []
		$self item BUT oval $rect -fill $fcol
		after 100 [list $self item BUT oval $rect -fill $bcol]
		set @flash 0
	} else {
		set colour [parse_color $@bcol]
		$self item BUT oval $rect -fill $colour -outline [$self look frame3]
	}
}
def Bang unclick {x y f target} {}
def Bang click   {x y f target} {netsend [list .$self bang]}
def Bang bang {count} {set @count $count; set @flash 1}
def Bang key_incr {val1 val2} {netsend [list .$self bang]}

class_new Toggle {BlueBox}
def Toggle bbox {} {
	mset {x1 y1} [$self xy]
	list $x1 $y1 [expr $x1+$@w] [expr $y1+$@w]
}
def Toggle draw {} {
	super
	mset {x1 y1 x2 y2} [$self bbox]
	set colour [parse_color $@bcol]
	set t [expr {int(($@w+29)/30)}]
	set fill $colour
	set x3 [expr {$x1+$t+2}]; set y3 [expr {$y1+$t+2}]
	set x4 [expr {$x2-$t-2}]; set y4 [expr {$y2-$t-2}]
	if {$@on} {
		set fill [parse_color $@fcol]
	} {
		set fill [parse_color $@bcol]
	}
	$self item X1 line [list $x3 $y3 [expr {$x4+1}] [expr {$y4+1}]] -width $t -fill $fill
	$self item X2 line [list $x3 $y4 [expr {$x4+1}] [expr {$y3-1}]] -width $t -fill $fill
}
def Toggle unclick {x y f target} {}
def Toggle click {x y f target} {
	if {!$@on} {set @on 1} {set @on 0}
	netsend [list .$self float $@on]
	$self changed
}

class_new Vu {Labelled IEMGUI Box}
set vu_col {
	0 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16
	15 15 15 15 15 15 15 15 15 15 14 14 13 13 13 13 13 13 13 13 13 13 13 19 19 19
}
def Vu init {mess} {
	super $mess
	set @value 0
	set @peak 0
}
def Vu bbox {} {
	mset {x1 y1} [$self xy]
	list $x1 $y1 [expr $x1+$@w] [expr $y1+$@h]
}
def Vu led_size {} {
	set n [expr $@h/40]
	if {$n < 2} {set n 2}
	return [expr $n-1]
}
def Vu draw {} {
	global vu_col
	mset {x1 y1 x2 y2} [$self bbox]
	set colour [parse_color $@bcol]
	super
	$self draw_io
	set led_size [$self led_size]
	set x3 [expr $x1+$@w/4]
	set x4 [expr $x2-$@w/4]
	$self item BASE rectangle [list $x1 $y1 $x2 $y2] -width 0 -fill $colour
	for {set i 1} {$i<=40} {incr i} {
		set y [expr $y1 + ($led_size+1)*(41-$i) - ($led_size+1)/2]
		$self item RMS${i} rectangle [list $x3 $y $x4 [expr $y+$led_size]] \
		    -fill [parse_color [lindex $vu_col $i]] -width 0
	}
	#if {!$@zoom} {return}
	set lfont [list [lindex {courier helvetica times} $@fstyle] $@fs bold]
	set lcolor [parse_color $@lcol]
	set i 0
	foreach level { <-99 -50 -30 -20 -12 -6 -2 -0dB +2 +6 >+12 } {
		set k1 [expr $led_size+1]
		set k2 41
		set k3 [expr $k1/2]
		set k4 [expr $y1-$k3]
		set yyy [expr $k4 + $k1*($k2-4*$i)]
		$self item SCALE{$level} text [list [expr $x2+4] [expr $yyy+$k3-3]] \
		    -text $level -anchor w -font $lfont -fill $lcolor
		incr i
	}
	set y [expr $y1 + ($led_size+1)*(41-$@value) - ($led_size+1)/2]
	$self item MASK rectangle [list $x3 $y1 $x4 $y] -width 0 -fill $colour
	set c [lindex $vu_col [expr int($@peak)]]
	set y [expr $y1 + ($led_size+1)*(41-$@peak) - ($led_size+1)/2]
	$self item PEAK rectangle [list $x1 $y $x2 [expr $y+$led_size]] -fill [parse_color $c] -width 0
}
def Vu rms=  {rms } {set @value $rms; $self changed rms}
def Vu peak= {peak} {set @peak $peak; $self changed peak}
def Vu set {i j} {
	set @value $i
	set @peak  $j
	$self changed
}

if {![catch {
	package require tkdnd
	dnd bindtarget . text/uri-list <Drop> {open_file %D}
}]} {
	puts "warning: tkdnd package not found"
}

class_new Cnv {Labelled IEMGUI Box}
def Cnv draw {} {
	mset {x1 y1} [$self xy]
	$self item BASE rectangle [list $x1 $y1 [expr {$x1+$@w+1}] [expr {$y1+$@h+1}]] -fill [parse_color $@bcol] -width 0
	super
}
def Cnv bbox {} {
	mset {x1 y1} [$self xy]
	return [list $x1 $y1 [expr $x1+$@w] [expr $y1+$@h]]
}

class_new Array {Box}
def Array init {mess} {
	super $mess
	set @name [lindex $mess 2]
	set @length 0
	set @data {}
	set @draw 0
}
def Array bbox {} {
	return {0 0 1 1} ;# huh?
}
def Array draw_name {} {
	mset {x_off y_off} [$@canvas xy]
	$self item TEXT text [lmap + [list $x_off $y_off] 2] \
		    -font [View_look $self font] -text $@name \
		    -fill [View_look $self fg] -anchor nw
}
def Array draw {} {
	$self draw_name
	mset {x_off y_off} [$@canvas xy]
	set m [$@canvas get_mess]
	mset {xfrom yto xto yfrom pixwidth pixheight} $m
	if {[winfo exists [$@canvas widget]]} {
		mset {c_width c_height} [$@canvas get_dimen]
		set width [expr $c_width / $@length]
		set i 0
		foreach val $@data {
			if {!$val} {set val 0.0}
			set y [expr $c_height - (((double($val)+abs($yfrom))/($yto-$yfrom) * $c_height))]
			set x1 [expr $width * $i]
			set x2 [expr $x1 + $width]
			set line [list $x1 $y $x2 $y]
			$self item elem${i} line $line -fill [$@canvas look compfg] -width 2 -tags "$self ${self}elem${i}"
			#.$self.c raise ${self}elem${i}
			incr i
		}
	} else {
		set width [expr $pixwidth / $@length]
		set canvas [$self get_canvas]
		set i 0
		foreach val $@data {
			if {!$val} {set val 0.0}
			#set val2 [lindex $@data [expr $i+1]]
			set y [expr  ($pixheight - ((double($val)+abs($yfrom))/($yto-$yfrom) * $pixheight)) + $y_off]
			#set y2 [expr  ($pixheight - ((double($val2)+abs($yfrom))/($yto-$yfrom) * $pixheight)) + $y_off]
			set x1 [expr ($width * $i) + $x_off]
			set x2 [expr $x1 + $width]
			set line [list $x1 $y $x2 $y]
			$self item ${self}ELEM${i} line $line -fill [$self look fg] -width 2
			incr i
		}
		[$canvas widget] raise $self
		#set width [expr $pixwidth / [expr $@length-1]]
		#set canvas [$self get_canvas]
		#set i 0
		#for {set i 0} {$i < [expr $@length-1]} {incr i} {
		#	#if {!$val} {set val 0.0}
		#	set val [lindex $@data [expr $i]]
		#	set val2 [lindex $@data [expr $i+1]]
		#	set y [expr  ($pixheight - ((double($val)+abs($yfrom))/($yto-$yfrom) * $pixheight)) + $y_off]
		#	set y2 [expr  ($pixheight - ((double($val2)+abs($yfrom))/($yto-$yfrom) * $pixheight)) + $y_off]
		#	set x1 [expr ($width * $i) + $x_off]
		#	set x2 [expr $x1 + $width]
		#	set line [list $x1 $y $x2 $y2]
		#	$self item ${self}ELEM${i} line $line -fill [$self look fg] -width 0
		#}
	}
}
def Array click {x y f target} {
	if {[winfo exists [$@canvas widget]]} {set canvas $@canvas} else {set canvas [$@canvas canvas]}
	$canvas focus= $self
	set @draw 1
}
def Array unclick {x y f target} {
	if {[winfo exists [$@canvas widget]]} {set canvas $@canvas} else {set canvas [$@canvas canvas]}
	$canvas focus= ""
	set @draw 0
}
def Array motion {x y f target} {
	if {!$@draw} return
	if {[winfo exists [$@canvas widget]]} {
		mset {c_width c_height} [$@canvas get_dimen]
		mset {xfrom yto xto yfrom pixwidth pixheight} [$@canvas get_mess]
		set width [expr $c_width / $@length]
		set i [format %d [expr int($x/$width)]]
		set x1 [expr $width * $i]
		set x2 [expr $x1 + $width]
		set line [list $x1 $y $x2 $y]
		set val [expr (($c_height-$y)/$c_height) * ($yto-$yfrom) + $yfrom]
		netsend [list .$self $i $val]
	} else {
		mset {xfrom yto xto yfrom pixwidth pixheight} [$@canvas get_mess]
		mset {x_off y_off} [$@canvas xy]
		set width [expr $pixwidth / $@length]
		set i [format %d [expr int(($x-$x_off)/$width)]]
		set val [expr (($pixheight-$y+$y_off)/$pixheight) * ($yto-$yfrom) + $yfrom]
		netsend [list .$self $i $val]
	}
}
def Array length= {val} {set @length [format %f $val]}
def Array name= {val} {set @name $val}
def Array array_set {data_list} {
	if {[llength $data_list] == $@length} {
		set @data {}
		for {set i 0} {$i < $@length} {incr i} {
			lappend @data [lindex $data_list $i]
		}
	} else {
		puts "error..."
	}
}

############ evaluator

class_new Listener {Thing}
def Listener init {serf name command} {
	set @history [History new 20]
	set @command $command
	set @expanded 0
	set @serf $serf
	set font $::look(View:font)
	frame $serf
	pack [frame  $serf.1]   -side left -fill y
	pack [frame  $serf.1.1] -side bottom
	pack [button $serf.1.1.expander -image icon_plus -command "$self toggle_expand"] -side left
	pack [label  $serf.1.1.label -width 11 -font $font -text "$name: "] -side left
	pack [entry $serf.entry -width 40 -font $font] -side left -fill x -expand yes
	pack $serf -fill x -expand no
	bind $serf.entry <Up>     "$self scroll_history +1"
	bind $serf.entry <Down>   "$self scroll_history -1"
	bind $serf.entry <Return> "$self eval"
}
def Listener toggle_expand {} {
	set @expanded [expr 1-$@expanded]
	if {$@expanded} {$self expand} {$self unexpand}
}
def Listener expand {} {
	set e $@serf.entry
	set text [$e get]
	destroy $e
	pack [text $e -width 40 -height 8] -side left -fill x -expand yes
	$e insert 0.0 $text
	$@serf.1.1.expander configure -image icon_minus
	bind $e <Alt-Return> "$self eval"
}
def Listener unexpand {} {
	set e $@serf.entry
	set text [$e get 0.0 end]
	regsub "\n$" $text "" text
	destroy $e
	pack [entry $e -width 40] -side left -fill x -expand yes
	$e insert 0 $text
	$@serf.1.1.expander configure -image icon_plus
	bind $e <Up>     "$self up"
	bind $e <Down>   "$self down"
	bind $e <Return> "$self eval"
}
def Listener replace {stuff} {
	$@serf.entry delete 0 end
	$@serf.entry insert 0 $stuff
	$@serf.entry icursor end
}
def Listener scroll_history {incr} {
	if {![$@history histi]} {$@history set_hist 0 [$self get_command]}
	$self replace [$@history traverse $incr]
	
}
def Listener append {v}  {
	$@history prepend $v
	lappend @hist $v; set @histi [llength $@hist]
}
def Listener get_command {} {
	set e $@serf.entry
	if {$@expanded} {
		set l [$e get 0.0 end]; return $l
	} else {
		set l [$e get]; return $l
	}

}
def Listener eval {} {
	set e $@serf.entry
	$@history histi= 0
	set l [$self get_command]
	$self append $l
	if {$@expanded} {$e delete 0.0 end} {$e delete 0 end}
	$@command $self $l
}

proc tcl_eval {self l} {post %s "tcl: $l"; post %s "returns: [uplevel [info level] $l]"}
proc  pd_eval {self l} {post %s "pd: $l"; netsend $l}
proc canvas_eval {self l} {post %s "tcl: $l"; post %s "returns: [uplevel [info level] [join [list [$self canvas] $l]]]"}
############ button bar

set butt {
	{ObjectBox  Object  {obj}}
	{MessageBox Message {msg}}
	{FloatBox   Number  {floatatom}}
	{SymbolBox  Symbol  {symbolatom}}
	{CommentBox Comment {text}}
	{bng     bng    {obj bng}}
	{tgl     tgl    {obj tgl}}
	{nbx     nbx    {obj nbx}}
	{vsl     vsl    {obj vsl}}
	{hsl     hsl    {obj hsl}}
	{vradio  vradio {obj vradio}}
	{hradio  hradio {obj hradio}}
	{vu      vu     {obj vu}}
	{cnv     cnv    {obj cnv}}
	{Graph   graph  {graph}}
	{Array   array  {menuarray 0}}
}

proc button_bar_add {x y} {lappend ::butt [list $x $y noload]}
if {$tk} {
	set dir $cmdline(icons)
	foreach icon {mode_edit mode_run pd_32}  {image create photo icon_$icon -file $dir/$icon.gif}
	foreach b $butt {mset {icon name cmd} $b; image create photo icon_$icon -file $dir/$icon.gif}
}
class_new ButtonBar {View}
def ButtonBar init {canvas} {
	set @canvas $canvas
	set bb .$@canvas.bbar
	frame $bb
	pack [button $bb.edit -image icon_mode_edit -border 1 -command [list $@canvas editmodeswitch]] -side left
	foreach e $::butt {
		mset {icon name cmd} $e
		pack [button $bb._$name -image icon_$icon -border 1 -command "$@canvas new_object $cmd"] -side left
		balloon $bb._$name [say $name]
	}
	pack [entry $bb.name -font {helvetica -12} -width 8 -border 0] -side right
	pack [spinbox $bb.scale -width 5 -command "$canvas zooming %d" -state readonly] -side right
	$bb.scale set [format %d%% [expr int(100*[$@canvas zoom])]]
	$bb.name insert 0 $@canvas
}
def ButtonBar widget {} {return .$@canvas.bbar}
proc obj_create {c flag} {}

############ crosshair

class_new Crosshair {View}
def Crosshair classtags {} {return {}}
def Crosshair init {canvas} {
	super
	set @canvas $canvas
	$self data= 0 0 {none}
}
def Crosshair data= {x y target} {
	set @x $x
	set @y $y
	set @target $target
}
def Crosshair draw {} {
	mset {type id detail} $@target
	set x $@x; set y $@y
	if {[$@canvas look hairsnap]} {
		switch -regexp -- $type {^object|outlet|inlet$ {mset {x y x3 y3} [$id bbox]}}
	}
	mset {x1 y1 x2 y2} [$self display_area]
	set h1 [list $x1 $y $x2 $y]
	set v1 [list $x $y1 $x $y2]
	$self item VHAIR1 line $v1 -fill [$@canvas look crosshair] -width 1 -dash {4 4 4 4}
	$self item HHAIR1 line $h1 -fill [$@canvas look crosshair] -width 1 -dash {4 4 4 4}
}

#def Crosshair erase {} {$self item_delete VHAIR1; $self item_delete HHAIR1}
class_new Sense {View}
def Sense init {canvas} {
	super
	set @canvas $canvas
	$self data= 0 0 0 red
}
def Sense data= {x y range col} {
	set @x $x
	set @y $y
	set @range $range
	set @col $col
}
def Sense flash {x y sense col} {
	$self data= $x $y $sense $col
	$self draw
	after 500 $self erase
}
def Sense draw {} {
	set c [$@canvas widget]
	set x1 [expr $@x-$@range]; set y1 [expr $@y-$@range]
	set x2 [expr $@x+$@range]; set y2 [expr $@y+$@range]
	$self item SENSE oval [list $x1 $y1 $x2 $y2] -fill $@col -outline yellow
}
def View display_area {} {
	set c [$@canvas widget]; set z [$@canvas zoom]
	set edge 10
	set x1 [expr {int([$c canvasx $edge]/$z)}]
	set y1 [expr {int([$c canvasy $edge]/$z)}]
	set x2 [expr {int(([$c canvasx [winfo width $c]]-$edge)/$z)}]
	set y2 [expr {int(([$c canvasy [winfo height $c]]-$edge)/$z)}]
	return [list $x1 $y1 $x2 $y2]
}

class_new Grid {View}
def Grid init {canvas} {
	super
	set @canvas $canvas
	set c [$@canvas widget]
	set @width [winfo width $c]
	set @height [winfo height $c]
	set @size [$@canvas look grid_size]
	set @col [$@canvas look grid]
	set @gap 5
}
def Grid classtags {} {return {}}
def Grid update {h w} {set @width $w; set @height $h}
def Grid size= {size} {set @size $size}
def Canvas snap_grid {} {return [$self look snap_grid]}
def Canvas snap_grid= {val} {set ::look(Canvas:snap_grid) $val}
def Canvas snap_objs2grid {} {
	if {![$self editmode]} {return}
	foreach obj [$@objects values] {
		mset {x y} [$obj xy]
		set grid [$self look grid_size]
		set x [expr floor($x/$grid)*$grid]
		set y [expr floor($y/$grid)*$grid]
		$obj moveto $x $y
	}
}
def Grid draw {} {
	mset {x1 y1 x2 y2} [$self display_area]
	set c [$@canvas widget]
	set lowest [$@canvas lowest_item]
	$self draw_lines $x1 $x2 $y1 $y2 VL
	$self draw_lines $y1 $y2 $x1 $x2 HL
	if {$lowest != -1} {$c lower $self $lowest}
}
def Grid draw_lines {v1 v2 v3 v4 tag} {
	set s $@size; set g $@gap
	for {set i $v1} {$i < $v2} {incr i} {
		#if {$l%$g == 0} {set width 1;set dash [list 7 1]} {set width 1;set dash [list 1 4 1 4]}
		if {![expr {$i % int($s*$g)}]} {set w 1;set d [list 7 1]} {set w 1;set d [list 1 4 1 4]}
		if {![expr {$i % int($s)}]} {
			switch $tag {VL {set line [list $i $v3 $i $v4]} HL {set line [list $v3 $i $v4 $i]}}
			$self item ${tag}$i line $line -fill $@col -width $w -dash $d
		}
	}
}

def Canvas lowest_item {} {
	set c [$self widget]
	set all [$c find withtag foo]
	if {![llength $all]} {return -1}
	set lowest [lindex [$c gettags [lindex $all 0]] 0]
	return $lowest
}
#def Canvas grid_size {} {return [$self look grid_size]}
def Canvas grid_size= {size} {
	set ::look(Canvas:grid_size) $size
	if {[$self editmode]} {$@grid size= $size; $@grid erase; after 0 $@grid draw}
}
############ tooltips (only those that are drawn as canvas items)

class_new Tooltip {View}
def Tooltip init {canvas pos curpos text type iserror} {
	set @canvas $canvas
	set @pos $pos
	set @curpos $curpos
	set @text $text
	set @type $type
	set @iserror $iserror
}
def Tooltip iserror {} {return $@iserror}
def Tooltip curpos  {} {return $@curpos}
def Tooltip text    {} {return $@text}
def Tooltip draw {} {
	set c [$@canvas widget]
	if {$@iserror} {
		set fg "#ffffff"; set bg "#dd0000"
	} else {
		set fg "#000000"; set bg "#ffffcc"
	}
	mset {x y} $@pos
	switch -- $@type {
		o {set ny [expr {$y+24}]}
		i {set ny [expr {$y-24}]}
	}
	$self item TEXT text [list [expr $x+12] $ny] -fill $fg -text $@text -anchor sw -width 300
	mset {x1 y1 x2 y2} [l+ [$c bbox ${self}TEXT] [list -4 -4 +4 +4]]
	switch -- $@type {
	  o {set coords [list $x1 $y1 [expr $x1+8] $y1 $x $y [expr $x1+16] $y1 $x2 $y1 $x2 $y2 $x1 $y2 $x1 $y1]}
	  i {set coords [list $x1 $y1 $x2 $y1 $x2 $y2 [expr $x1+16] $y2 $x $y [expr $x1+8] $y2 $x1 $y2 $x1 $y1]}
	}
	$self item RECT polygon $coords -fill $bg -outline $fg
	$c lower ${self}RECT ${self}TEXT
}
# $c delete tooltip_bg tooltip_fg
set tooltip ""
def Canvas show_tooltip {x y text type {iserror 0}} {
	global tooltip
	if {$tooltip ne "" && [$tooltip text] eq $text} {return}
	if {$tooltip ne ""} {$tooltip delete}
	set tooltip [Tooltip new $self [list $x $y] $@curpos $text $type $iserror]
	$tooltip draw
}

############ class browser

# future use, maybe:
#class_new ServerClassDict {Observable Thing}
#def ServerClassDict init {} {}

# Completion/Browser init can be cleaned up a bit more, do it later...
class_new ClassBrowser {Dialogue}
class_new Browser {ClassBrowser}
def Browser init {name x y textbox} {super $name $x $y $textbox}

class_new Completion {ClassBrowser}
def Completion init {name x y textbox} {super $name $x $y $textbox}
def Completion cancel {} {
	bind $@textbox <Key>            "$@textself key_input %W %x %y %K %A 0"
	bind $@textbox <Control-Return> "$@textself key_input %W %x %y 10 %A 0"
	bind $@textbox <Return>         "$@textself unedit"
	bind $@textbox <Tab>            "$@textself key_input %W %x %y %K %A 0"
	focus $@textbox
	$self delete
}
def ClassBrowser delete {} {set @exist 0; super}
def ClassBrowser init {name x y textbox} {
	set @name $name
	set @width 0
	set @height 0
	# so that in completion mode, it know which textbox to switch the focus to
	set @textbox $textbox
	netsend [list pd update-path]
        netsend [list pd update-class-list $self list_callback]
}
def ClassBrowser fill_box {s} {
	$@listbox delete 0 end
	foreach class [lsort $::class_list] {
		if {[string length $s]==0 || [string first $s $class]>=0} {
			set t "\[$class\]"
			if {[can_say $class]} {append t "  [say $class]"}
			$@listbox insert end $t
			#if {[string length $t] > [string length $@width]} {set @width [string length $t]}
			if {[string length $t] > $@width} {set @width [string length $t]}
		}
	}
	set none [say no_matches]
	if {![$@listbox size]} {$@listbox insert 0 $none; set @width [string length $none]}
	$@listbox selection set 0 0
}

def Completion fill_box {s} {
	super $s
	wm maxsize .$self [winfo reqwidth .$self.comp] [winfo reqheight .$self.comp]
}

def Browser fill_box {s} {
	super $s
	.$self.title configure -text [format [say how_many_object_classes] [$@listbox size] [llength $::class_list]]
}

proc search_for_externs {} {
	foreach dir $::pd_path {
		catch {
			#set xs [glob "$dir/*.pd*"]
			set xs [glob "$dir/*.pd_*"]
			foreach x $xs {
				set fn [lindex [file split $x] end]
				set fn [join [lrange [split $fn .] 0 end-1] .]
				lappend ::class_list $fn
			}
		}
	}
}

def ClassBrowser info {listbox} {
	set class [$self current_class]
	if {$class != ""} {netsend [list pd update-class-info $class $self info_callback]}
}

def Browser list_callback {} {
	search_for_externs
	set class_list [luniq [lsort $::class_list]]

	toplevel .$self
	set f .$self.cl
	pack [frame $f] -side top -fill both -expand yes
	pack [label .$self.title -text ""] -side top
	listbox $f.1 -width 50 -height 20 -yscrollcommand "$f.2 set" -activestyle none
	scrollbar $f.2 -command "$f.1 yview"
	text    $f.3 -width 30 -height 20 -yscrollcommand "$f.4 set"
	scrollbar $f.4 -command "$f.3 yview"
	set @listbox $f.1

	frame $f.5
	button $f.5.help -text [say help] -command [list $self help]
	pack $f.5.help -side top
	pack $f.5 -side left -fill    y -expand no
	pack $f.1 -side left -fill both -expand yes
	pack $f.2 -side left -fill    y -expand no
	pack $f.3 -side left -fill both -expand yes
	pack $f.4 -side left -fill    y -expand no

	set b .$self.butt
	frame $b
	pack [label $b.1 -text [say filter]] -side left
	pack [entry $b.2 -width 15] -side left
	pack [button $b.close -text [say close] -command "destroy .$self"] -side right
	pack $b -side bottom -fill x -expand no
	set @textbox $b.2
	$self fill_box ""
	#bind $f.1 <Button-1> "after 1 \"$self info $f.1 \""
	foreach w [list $f.1 $b.2] {
		bind $w       <KeyPress> "after 1 \"$self key %K 0\""
		bind $w <Shift-KeyPress> "after 1 \"$self key %K 1\""
		bind $w <Return> [list $self help]
	}
	focus $@textbox
}

def Browser help {} {
	set f .$self.cl
	netsend [list pd help [$self current_class]]
}

def Completion list_callback {} {
	search_for_externs
	set class_list [luniq [lsort $::class_list]]
	toplevel .$self
	wm protocol .$self WM_DELETE_WINDOW "$self cancel"
	wm overrideredirect .$self 1
	set canvas $@name
	set f .$self.comp
	set @listbox $f
	set @rootx [winfo rootx .$@name.c]
	set @rooty [winfo rooty .$@name.c]
	set @max [wm maxsize .$self]
	if {[regexp {(x[0-9a-z]{6,8})text$} $@textbox dummy textself]} {set @textself $textself}
	if {[$canvas look showcomp] <= 20} {set @height [$canvas look showcomp]} else {set @height 20}
	listbox $f -width $@width -height $@height -relief flat -activestyle dotbox -font $::look(View:font) \
	    -bg [$@textself look bg] -selectbackground [$@textself look fg] \
	    -fg [$@textself look fg] -selectforeground [$@textself look bg]
	$self adjust_box
	bind $f <Button-1> "after 1 \"$self complete\""
	bind $f <Return>   "after 1 \"$self complete\""
	bind $f       <KeyPress> "$self key %K 0"
	bind $f <Shift-KeyPress> "$self key %K 1"
	bind $@textbox <Tab>      "$self key %K; break"
	bind $@textbox <KeyPress> "$self key %K "
	focus .$self.comp
}

def Completion adjust_box {} {
	mset {x1 y1 x2 y2} [lmap * [$@textself bbox] [$@name zoom]]
	set x1 [format %0.f $x1];set y1 [format %0.f $y1]
	set x2 [format %0.f $x2];set y2 [format %0.f $y2]
	$self fill_box [$@textbox get 1.0 1.end]
	set f .$self.comp
	$f configure -width $@width
	set box_width  [winfo reqwidth  $f]
	set box_height [winfo reqheight $f]
	pack $f -side left -expand yes

	.$self configure -width $box_width
	.$self configure -height $box_height

	#test the right edge of the screen, assuming the left edge has enough space
	if {[expr $x1+$@rootx+$box_width] < [lindex $@max 0]} {
		set box_x [expr $x1+$@rootx]
	} else {
		set box_x [expr $x2 - $box_width + $@rootx]
	}
	#test the lower edge of the screen, assuming the upper edge has enough space
	if {[expr $y2+$@rooty+$box_height] < [lindex $@max 1]} {
		set box_y [expr $y2 + 5 + $@rooty]
	} else {
		set box_y [expr $y1 - $box_height - 2 + $@rooty]
	}

	wm geometry .$self [winfo reqwidth .$self]x[winfo reqheight .$self]+$box_x+$box_y

}

def ClassBrowser current_class {} {
	set i [$@listbox curselection]
	if {$i == ""} {return {}}
	return [string range [lindex [$@listbox get $i] 0] 1 end-1]
}

def ClassBrowser complete {} {
	if {[regexp {x([0-9a-z]{6,8})text$} $@textbox obj]} {
		set cut [string first "text" $obj]
		set obj [string range $obj 0 [expr $cut -1]]
	}
	set class [$self current_class]
	$@textbox delete 1.0 1.end
	$@textbox insert 1.0 $class
	$obj unedit
	destroy .$self
}

def ClassBrowser key {key {shift 0}} {
 	switch -regexp -- $key {
		Up|Down {
			if {[focus] != $@listbox} {
				focus $@listbox
				event generate $@listbox <KeyPress> -keysym $key
			} else {
				if {$self == "browser"} {$self info $@listbox}
			}
		}
		Escape {after 1 "$self cancel"} ;# doesn't really work
		Tab {
			focus $@listbox
			set next [$@listbox index active]
			incr next
			if {$next >= [$@listbox size]} {set next 0}
			$@listbox activate $next
			$@listbox selection clear 0 [expr [$@listbox size] - 1]
			$@listbox selection set $next $next
			#if {$next >= [expr $@height - 1]} {$@listbox yview scroll 1 units}
			$@listbox see $next
			if {$self == "browser"} {$self info $@listbox}
		}
		BackSpace {
			if {[focus] == $@listbox} {focus $@textbox}
			#classbrowser uses entry as input widget, where as completion is text widget...
			switch $self {
				browser {$self fill_box [$@textbox get]}
				completion {$self adjust_box; $@textself resize; $@textself changed}
			}
		}
		default {
			$self key_default $key
		}
	}
}

def Browser key_default {key} {
	if {[focus] == $@listbox} {
		if {[regexp {^[a-zA-Z0-9~/\._]{1}$} $key]} {
			.$self.butt.2 insert end $key
			$self fill_box [$@textbox get]
		}
	} else {$self fill_box [$@textbox get]}
}

def Completion key_default {key} {
	if {[focus] == $@listbox} {
		if {[regexp {^[a-zA-Z0-9~/\._]{1}$} $key]} {
			$@textbox insert 1.end $key
			$@textself after_key $@textbox
			$self adjust_box
			focus $@textbox
		}
	}
	if {[focus] == $@textbox & $key != "Tab"} {
		$self adjust_box
		$@textself resize
		#hum, no idea why i need after 1 for it to work...
		after 1 $@textself after_key $@textbox
	}
}

def ClassBrowser info_callback {class} {
	global class_info
	set f .browser.cl
	set class [$self current_class]
	$f.3 delete 0.0 end
	$f.3 insert end "class $class\n"
	foreach {k v} $class_info($class) {$f.3 insert end "$k=\"$v\"\n"}
}

def TextBox propose_completions {} {
	set c [$@canvas widget]
	set widget $c.${self}text
	set propose $c.${self}propose
	#$propose configure -state normal
	if {![info exists ::class_list]} {
		netsend [list pd update-class-list $self propose_completions]
		return
	}
	set r {}
	set c {}
	set n 0
	set prev ""
	foreach class [luniq [lsort $::class_list]] {
		if {[string length $@text]==0 || [string first $@text $class]>=0} {
			if {[string compare [say $class] "{{$class}}"]} {
				lappend r "$class : [say $class]"
			} {
				lappend r $class
			}
			lappend c $class
			incr n
		}
		if {$n > 16} {lappend r ...; break}
	}
	set r [join $r "\n"]
	mset {x1 y1 x2 y2} [$self bbox]
	set @action [Completion new_as completion $@canvas $x1 $y1 $widget]
}

############ properties_dialog #########
proc change_entry {self val} {
	set v [expr [$self get]+$val]
	$self delete 0 end
	$self insert 0 $v
}

class_new Dialogue {View}
def Dialogue add_stuff {f name label} {
	frame $f
#	frame $f.label -width $@label_width -borderwidth 2
#	pack  [button  $f.label.0 -image "icon_empty" -width $@label_width] -side left
#	place [message $f.label.1 -text $label        -width $@label_width] -x 0 -y 0
#	puts [$f.label.1 cget -height]
	pack [label $f.label -text $label -width [expr $@label_width/7] -wraplength $@label_width -anchor e] -side left
	balloon $f.label $name
}
def Dialogue add_side {f name label} {
	$self add_stuff $f $name $label
	frame $f.side -relief ridge -borderwidth 2
	foreach {i side} {0 left 1 right 2 top 3 bottom} {
		radiobutton $f.side.$side -value $i -variable @$name -text $side
	}
	pack $f.side.left   -side   left -fill y
	pack $f.side.right  -side  right -fill y
	pack $f.side.top    -side    top
	pack $f.side.bottom -side bottom
	pack $f.side -side left
}
def Dialogue add_color {f name label} {
	$self add_stuff $f $name $label
	set v $@$name
	set text_color [complement $v]
	button $f.color -text $v -font {Courier 10} -width 10 -pady 2 -fg $text_color \
	    -command [list $self choose_col $f $name $v] -relief sunken -background $v -highlightbackground $v -activebackground $v
	button $f.preset -text [say "preset"] -pady 2 -font {Helvetica 8} \
	    -command [list $self color_popup $f $name 10]
	bind $f.preset <Return> "$self color_popup $f $name 10"
	pack $f.color $f.preset -side left
}
def Dialogue add_choice {f name label choices} {
	$self add_stuff $f $name $label
	menu $f.menu -tearoff 0
	set i 0
	foreach part $choices {
		$f.menu add command -label [say $part] -command [list $self dropmenu_set $f $name $part $i]
		incr i
	}
	set trim_name [string trimleft $name "-"]
	set _($self:${name}choices) $choices
	set choice $@$name
	if {[string is integer $choice]} {set choice [lindex $choices $choice]}
	label $f.butt -text [say $choice] -relief raised -width 20
	balloon $f.butt "click to change setting"
	pack $f.label $f.butt -side left
	bind $f.butt <1> [list $self dropmenu_open $f $name]
}
def Dialogue add_key {f name label} {
	set text ""
	set n 0
	foreach item $name {
		if {$n != 0} {append text " & " [say $item]} else {set text [say $item]}
		incr n
	}
	$self add_stuff $f $name $text
	#balloon $f.label $name
	foreach item $name {
		set v $_($self:$item) ;# bug in objtcl
		set item_lower [string tolower $item]
		entry $f.$item_lower -width 15 -textvariable @$item
		pack $f.$item_lower -side left
	}
}
def Dialogue add_folders {f name label} {
	$self add_stuff $f $name $label
	set v $_($self:$name) ;# bug in poetcl
	frame $f.a
	listbox $f.a.list -width 40 -height 8 -yscrollcommand "$f.a.yscroll set" \
	    -activestyle none -xscrollcommand "$f.a.xscroll set"
	foreach line $v {$f.a.list insert end $line}
	set @$name $f.a.list ;# save the listbox path at @$name instead
	scrollbar $f.a.yscroll -command "$f.a.list yview"
	scrollbar $f.a.xscroll -command "$f.a.list xview" -orient horizontal
	pack $f.a.xscroll -side bottom -fill x
	pack $f.a.list -side left -fill both -expand 1
	pack $f.a.yscroll -side left -fill y
	pack $f.a -side left
	frame $f.b -borderwidth 0
	foreach {cmd lab} {dir_add add listbox_remove remove listbox_up up listbox_down down} {
		pack [button $f.b.$cmd -command "$self $cmd $f.a.list" -text [say $lab] -width 6] -side top
		balloon $f.b.$cmd [say dir_$lab]
	}
	pack $f.b -side top
}
def Dialogue add_libraries {f name label} {
	$self add_stuff $f $name $label
	set v $_($self:$name) ;# bug in poetcl. is it still there?
	frame $f.a
	listbox $f.a.list -width 32 -height 16 -yscrollcommand "$f.a.yscroll set" \
	    -activestyle none -xscrollcommand "$f.a.xscroll set"
	#foreach line $@$name {$f.a.list insert end $line}
	foreach line $v {$f.a.list insert end $line}
	# save the listbox path at @$name instead
	set @$name $f.a.list
	pack [scrollbar $f.a.yscroll -command "$f.a.list yview"] -side right -fill y
	pack $f.a.list -side top -fill both -expand 1
	pack [scrollbar $f.a.xscroll -command "$f.a.list xview" -orient horizontal] -side bottom -fill x
	pack $f.a -side left

	frame $f.b -borderwidth 0
	pack [entry $f.b.entry -width 15 -borderwidth 5 -relief ridge] -side top
	bind $f.b.entry <Return> "$self lib_add $f.a.list"

	foreach {cmd lab} {lib_add add listbox_remove remove listbox_up up listbox_down down} {
		pack [button $f.b.$cmd -command "$self $cmd $f.a.list" -text [say $lab] -width 6] -side top
		#balloon $f.b.$cmd [say dir_$lab]
	}
	pack $f.b -side top
}
def Dialogue dir_add {listbox} {
	set dir [tk_chooseDirectory -initialdir ~ -title "Choose a folder" -parent .$self]
	if {$dir == ""} {return}
	$listbox insert end $dir
	$listbox yview end
	focus .$self
}
# doesn't work with toplevel widget
proc upwidget {levels name} {
	set l [split $name .]
	return [join [lrange $l 0 end-$levels] .]
}
def Dialogue lib_add {f} {
	set f [upwidget 2 $f]
	set listbox $f.a.list
	set entry $f.b.entry
	set var [$entry get]
	if {$var != ""} {$listbox insert end $var}
	$listbox yview end
	$entry delete 0 end
	focus $entry
}
def Dialogue listbox_remove {listbox} {
	set sel [$listbox curselection]
	if {$sel == ""} {return}
	$listbox delete $sel
	$listbox selection set $sel
}
def Dialogue listbox_swap {listbox dir} {
	set sel [$listbox curselection]
	if {$sel == ""} {return}
	if {![inside [expr $sel+$dir] 0 [$listbox size]]} {return}
	set line [$listbox get $sel]
	$listbox delete $sel
	incr sel $dir
	$listbox insert $sel $line
	$listbox selection set $sel
	$listbox see $sel
}
def Dialogue add_devlist {f name label} {
	$self add_stuff $f $name $label
	menu $f.menu -tearoff 0
	set i 0
	set trim_name [string trimleft $name "-"]
	set part none ;# in case there are none
	foreach part $@$trim_name {
		$f.menu add command -label $part -command [list $self dropmenu_set $f $name $part $i]
		incr i
	}
	#label $f.butt -text [lindex $@$trim_name 0] -relief raised -width 20
	label $f.butt -textvariable _($self:${trim_name}0) -relief raised -width 20
	balloon $f.butt "click to change setting"
	pack $f.label $f.butt -side left
	bind $f.butt <1> [list $self dropmenu_open $f $name]
}
def Dialogue add_spins {f name label option} {
	$self add_stuff $f $name $label
	set i 0
	set trim_name [string trimleft $name "-"]
	set n [llength $@$option]
	foreach part $@$trim_name {
		if {$i < $n} {set s "readonly"} else {set s "disabled"}
		set v "::_($self:$trim_name${i})"
		spinbox $f.$i -width 2 -command "$self spinning %d $v" -state $s -textvariable $v
		pack $f.$i -side left
		balloon $f.$i "Device [expr $i+1]"
		incr i
	}
}
def Dialogue spinning {mode v} {
	switch $mode {
		up {incr $v; puts " incr $v"}
		down {incr $v -1}
	}
}
def Dialogue listbox_up   {listbox} {$self listbox_swap $listbox -1}
def Dialogue listbox_down {listbox} {$self listbox_swap $listbox +1}
def Dialogue add {w args} {
	foreach row $args {
		set name [lindex $row 0]
		set type [lindex $row 1]
		set options [lrange $row 2 end]
		set f $w.$name
		set label "[say $name]: "
		set k [lsearch $options -choices]
		if {$k>=0} {
			set choices [lindex   $options    [expr $k+1]]
			set options [lreplace $options $k [expr $k+1]]
		}
		#set v $@$name
		#set v $_($self:$name) ;# bug in poetcl
		switch -- $type {
			side      {$self add_side      $f $name $label}
			color     {$self add_color     $f $name $label}
			font      {$self add_font      $f $name $label $options}
			choice    {$self add_choice    $f $name $label $choices}
			key       {set f $w.[string tolower [lindex $name 0]]
				   $self add_key       $f $name $label}
			folders   {$self add_folders   $f $name $label}
			libraries {$self add_libraries $f $name $label}
			devlist   {$self add_devlist $f $name $label}
			spins     {$self add_spins $f $name $label $options}
			section    {label $f -text $label -bg "#0000aa" -fg "#ffff55" -font {helvetica -10 bold}}
			subsection {label $f -text $label -bg "#0000aa" -fg "#ffff55" -font {helvetica -10 bold}}
			toggle {
				$self add_stuff $f $name $label
				eval [concat [list checkbutton $f.toggle -variable @$name] $options]
				pack $f.toggle -side left
			}
			default {
				$self add_stuff $f $name $label
				set trim_name [string trimleft $name "-"]
				eval [concat [list entry $f.entry -textvariable _($self:$trim_name)] $options]
				pack $f.entry -side left
				bind $f.entry <Return> "$self ok"
				switch -regexp -- $type {
					integer|float|fontsize {
						frame $f.b -borderwidth 0
						button $f.b.1 -image icon_wedge_up   -command "change_entry $f.entry +1"
						button $f.b.2 -image icon_wedge_down -command "change_entry $f.entry -1"
						pack $f.b.1 $f.b.2 -side top
						pack $f.b -side left
						bind $f.entry <Button-4> "change_entry $f.entry +1"
						bind $f.entry <Button-5> "change_entry $f.entry -1"
					}
					entry {}
					default {
						label $f.type -text "($type)" -fg "#808080"
						pack $f.type -side right -anchor e
					}
				}
			}
		}
		pack $f -side top -fill x
	}
}

proc logvar {args} {
	set r {}
	foreach var $args {
		regsub {^_\(.*:(.*)\)$} $var {@\1} var2
		lappend r "$var2=[uplevel 1 list \$$var]"
	}
	puts [join $r "; "]
}

def Dialogue spinbox_update {mode} {puts " $mode"}
def Dialogue add_font {f name label class} {
	$self add_stuff $f $name $label
	set v $@$name
	label $f.font -text $v -font [lreplace $v 1 1 -10] -width [string length $v] -height 1 -pady 3 -fg black \
	    -relief sunken -bg white
	button $f.preset -text [say "edit"] -pady 2 -font {Helvetica 8} \
	    -command "FontDialogue new_as $name $class $f.font"
	pack $f.font $f.preset -side left
}

def Canvas fd {} {FontDialogue new_as view_font [$self look font] "View"}

class_new FontDialogue {Dialogue}
def FontDialogue init {class orig} {
	if {[winfo exists .$self]} {return}
	super cancel ok
	set f .$self
	set @class $class
	set @orig $orig
	bind all <KeyPress-F1> help
	set font $::look($@class:font)
	set @family [lindex $font 0]
	set @size   [expr -[lindex $font 1]]
	set @bold   [expr [lsearch $font bold  ]>=0]
	set @italic [expr [lsearch $font italic]>=0]
	set @str $font
	pack [label $f.label -text [say font_family] -anchor w] -side top -fill x

	frame $f.list -bd 2
	pack [listbox $f.list.box -relief sunken -yscrollcommand "$f.list.scroll set" -width 30] -side left -expand yes -fill both
	pack [scrollbar $f.list.scroll -relief sunken -command "$f.list.box yview" -takefocus 0] -side right -fill y
	bind $f.list.box <<ListboxSelect>> "$self font_update $f"
	foreach name [lsort [font families]] {$f.list.box insert end $name}
	set fontlist [$f.list.box get 0 end]
	set find [max 0 [lsearch $fontlist $@family]]
	$f.list.box selection set $find $find
	$f.list.box activate $find
	$f.list.box see $find
	bind $f.list.box <ButtonRelease-1> "$self font_update $f"
	bind $f.list.box <ButtonPress-1> "focus $f.list.box"
	pack $f.list -side left -expand yes -fill both

	#pack [ttk::separator $f.sep -orient vertical] -side left -fill y
	#pack [ttk::sizegrip $f.sep] -side left -fill y
	#ttk::panedwindow

	frame $f.var
	frame $f.var.size
	pack [label $f.var.size.label -text [say font_size]] -side left
	pack [spinbox $f.var.size.entry -relief sunken -textvariable fontsize -width 4 \
		      -command "$self font_changesize $f %d"] -side left
	bind $f.var.size.entry <KeyPress-Return> "$self font_update_size $f"
	$f.var.size.entry delete 0 end
	$f.var.size.entry insert 0 $@size

	frame $f.var.style
	pack [label $f.var.style.label -text [say font_style]] -side left
	set cmd [list $self font_update $f]
	pack [checkbutton $f.var.style.bold   -text [say font_bold]   -variable @bold   -command $cmd] -side top
	pack [checkbutton $f.var.style.italic -text [say font_italic] -variable @italic -command $cmd] -side top

	pack $f.var.size -side left 
	pack $f.var.style -side left -padx 20

	frame $f.preview
	pack [label  $f.preview.label -text [say font_preview]] -side left
	pack [canvas $f.preview.canvas -width 250 -height 50 -relief sunken -borderwidth 1] -side left -fill x
	$f.preview.canvas create text 4 4 -tags ${self}TEXT -anchor nw -text [say font_preview_2] -font $font

	pack $f.var -side top -fill x
	pack $f.preview -side top -pady 10
	focus $f.list.box
	#$self non_resizable
}
def FontDialogue font_update {f} {
	global font
	set lb $f.list.box
	set @family [$lb get [$lb curselection]]
	set @str [list $@family [expr -$@size]]
	if {$@bold  } {lappend @str bold  }
	if {$@italic} {lappend @str italic}
	# logvar @str
	$f.preview.canvas itemconfigure ${self}TEXT -font $@str
}
def FontDialogue font_changesize {f mode} {
	switch $mode {
		up   {set @size [expr $@size+1]}
		down {set @size [expr $@size-1]}
	}
	$f.var.size.entry delete 0 end
	$f.var.size.entry insert 0 $@size
	$self font_update $f
}
def FontDialogue font_style {f bold} {
	set @bold $bold
	$self font_update $f
}
def FontDialogue font_update_size {f} {
	set size [$f.var.size.entry get]
	if [regexp {^[0-9]+$} $size] {set @size $size}
	$self font_update $f
}
def FontDialogue apply {} {
	set ::look($@class:font) $@str
	$@orig configure -font [lreplace $@str 1 1 -10] -text $@str -width [string length $@str]
}

############ .pdrc editor
#Turns #rgb into 3 elem list of decimal vals.
proc rgb2dec {c} {
	set c [string tolower $c]
	if {[regexp -nocase {^#([0-9a-f])([0-9a-f])([0-9a-f])$} $c x r g b]} {
		# double'ing the value make #9fc == #99ffcc
		scan "$r$r $g$g $b$b" "%x %x %x" r g b
	} else {
		if {![regexp {^#([0-9a-f]+)$} $c junk hex] || \
			[set len [string length $hex]]>12 || $len%3 != 0} {
			if {[catch {winfo rgb . $c} rgb]} {
				return -code error "bad color value \"$c\""
			} else {
				return $rgb
			}
		}
		set len [expr {$len/3}]
		scan $hex "%${len}x%${len}x%${len}x" r g b
	}
	return [list $r $g $b]
}
#Returns a complementary color
proc complement {orig {grays 1}} {
	foreach {r g b} [rgb2dec $orig] {break}
	set R [expr {(~$r)%256}]
	set G [expr {(~$g)%256}]
	set B [expr {(~$b)%256}]
	if {$grays && abs($R-$r) < 32 && abs($G-$g) < 32 && abs($B-$b) < 32} {
		set R [expr {($r+128)%256}]
		set G [expr {($g+128)%256}]
		set B [expr {($b+128)%256}]
	}
	return [format "\#%02x%02x%02x" $R $G $B]
}

# this makes the tooltip
proc balloon {w help} {
	bind $w <Any-Enter> "after 500 [list balloon:show %W [list $help]]"
	#bind $w <Any-Leave> "destroy %W.balloon; puts \"destroy balloon\" "
	bind $w <Any-Leave> "destroy %W.balloon"
}

proc balloon:show {w arg} {
	if {[eval winfo containing  [winfo pointerxy .]]!=$w} {return}
	set top $w.balloon
	catch {destroy $top}
	toplevel $top -bd 1 -bg black
	wm overrideredirect $top 1
	if {$::tcl_platform(platform) == "macintosh"} {unsupported1 style $top floating sideTitlebar}
	pack [message $top.txt -aspect 10000 -bg lightyellow -font fixed -text $arg]
	set wmx [expr [winfo rootx $w]+[winfo width $w]]
	set wmy       [winfo rooty $w]
	wm geometry $top [winfo reqwidth $top.txt]x[winfo reqheight $top.txt]+$wmx+$wmy
	raise $top
}

def Dialogue ok     {} {$self apply; $self cancel}
def Dialogue cancel {} {$self delete}
def Dialogue close  {} {$self delete}
def Dialogue apply  {} {}
def Dialogue delete {} {destroy .$self; super}
def Dialogue erase {} {}; # so that it doesn't call View erase
def Dialogue init {args} {
	super
	set f .$self
	set @label_width 160 ;# 20
	toplevel $f
	frame $f.buttonsep -height 2 -borderwidth 1 -relief sunken
	frame $f.buttonframe
	set i 0
	foreach a $args {
		if {[llength $args]<=1 || $i>0} {
			pack [label $f.buttonframe.$i -width 1] -side left -fill x -expand 1
		}
		pack [button $f.buttonframe.$a -text [say $a] -command "$self $a"] -side left
		bind $f.buttonframe.$a <Return> "$self $a"
		incr i
	}
	pack $f.buttonframe -side bottom -fill x -pady 2m
	pack $f.buttonsep   -side bottom -fill x
	wm protocol $f WM_DELETE_WINDOW "$self cancel"
	bind .$self         <Tab> "$self traversal %K %W forward"
	bind .$self <Control-Tab> "$self traversal %K %W back"
	set @auto_apply 0
}
def Dialogue do_auto_apply {} {if {$@auto_apply} {$self apply}}
def Dialogue non_resizable {} {wm resizable .$self 0 0}
def Dialogue traversal {k w direction} {
	switch $direction {
		forward {focus [tk_focusNext $w]}
		back    {focus [tk_focusPrev $w]}
	}
}
def Dialogue dropmenu_open {frame} {
	set x [winfo rootx $frame.butt]
	set y [expr [winfo height $frame.butt] + [winfo rooty $frame.butt]]
	tk_popup $frame.menu $x $y
}
def Dialogue dropmenu_set {frame var part val} {
	#if {$say} {set text [say $part]} else {set text $part}
	set @$var $val
	$frame.butt configure -text [say $part]
}
def Dialogue color_popup_select {frame var color} {
	set @$var $color
	set col [format #%6.6x $color]
	if {$self == "ddrc"} {set @$var $col} ;#!@#$ fishy
	$frame.color configure -background $col -activebackground $col -highlightbackground $col \
	  -foreground [complement $col] -text $col
	destroy $frame.color.popup
	$self do_auto_apply
}
def Dialogue color_popup {frame var i} {
	set w $frame.color.popup
	if [winfo exists $w] {destroy $w}
	#menu $w -tearoff false
	toplevel $w -bg "#000000"
	wm overrideredirect $w 1
	wm geometry $w +[expr [winfo rootx $frame.preset]]+[expr {[winfo rooty $frame.preset]+[winfo height $frame.preset]}]
	for {set i 0} {$i<3} {incr i} {
	    pack [frame $w.$i -bd 0 -bg "#000000"]
	    for {set j 0} {$j<10} {incr j} {
		set c [lindex $::preset_colors [expr {$i*10+$j}]]
		pack [frame $w.$i.$j -width 24 -height 24 -background "#$c" -relief raised] -side left
		bind $w.$i.$j <ButtonRelease> [list $self color_popup_select $frame $var [expr 0x$c]]
	    }
	}
}
def Dialogue choose_col {frame var val} {
	set c 0xFFFFFF
	set color [tk_chooseColor -title $val -initialcolor $val]
	if {$color != ""} {
		$frame.color configure -text $color
		$self color_popup_select $frame $var [expr [string replace $color 0 0 "0x"]&0xFFFFFF]
	}
}

class_new PagedDialogue {Dialogue}
def PagedDialogue init {args} {
	eval [concat [list super] $args]
	set @nb [ttk::notebook .$self.1]
	set @nbs $@nb
	pack $@nb -expand 1 -fill both
	$self non_resizable
}

# numbers in section are for the % of space taken by labels
set pdrc_options {
	section {section_audio 50}
	integer -r
	devlist -audioindev|-soundindev
	devlist -audiooutdev|-soundoutdev
	spins {-inchannels audioindev}
	spins {-outchannels audiooutdev}
	integer -audiobuf|-soundbuf
	integer -blocksize
	integer -sleepgrain
	void -nodac
	void -noadc
	choice {audio_api_choice}
	void -32bit

	section {section_midi 50}
	void -nomidiin
	void -nomidiout
	devlist -midiindev
	devlist -midioutdev

	section {section_externals 20}
	libraries -lib

	section {section_paths 20}
	folders -path
	folders -helppath

	section {section_other 50}
	files -open
	void -verbose
	integer -d
	void -noloadbang
	string -send
	void -listdev
	void -realtime|-rt
}

proc pdtk_audio_dialog {indevlist indevs inchans outdevlist outdevs outchans sr dspblock advance multi longform} {
	pdrc audio_properties $indevlist $indevs $inchans $outdevlist $outdevs $outchans $sr $dspblock $advance $multi
}

class_new ServerPrefsDialogue {PagedDialogue}
def ServerPrefsDialogue apply {} {
	set audio_props [$self audio_properties=?]
	#pd pd audio-dialog $audio_props
	netsend [list "pd" "audio-dialog" $audio_props]
	$self write
}
def ServerPrefsDialogue init_reverse_hash {} {
	foreach {type names} $::pdrc_options {
		set name [lindex $names 0]
		if {[info exists @$name]} {
			if {$name != "audio_api_choice"} {set @$name ""}
		} else {set @$name ""}
		foreach alias $names {set ::pdrc_options_h($alias) [list $type $name]}
		if {$name == "audio_api_choice"} {
			foreach alias [lrange $::pd_apilist2 1 end] {set ::pdrc_options_h($alias) [list $type $name]}
		}
	}
}
def ServerPrefsDialogue audio_properties {indevlist indevs inchans outdevlist outdevs outchans sr dspblock advance multi} {
	set @audioindev $indevlist
	set @audiooutdev $outdevlist
	# the following @audioindev* is used as -textvariable for devlist
	set @audioindev0  [lindex $@audioindev 0]
	set @audiooutdev0 [lindex $@audiooutdev 0]
	set @inchannels $inchans
	set @outchannels $outchans
	set @audiobuf $advance
	set @blocksize $dspblock
	set @usemulti $multi
	set @r $sr
	set @midiindev "midione"
	set @midioutdev "miditwo"
	# below are also used as -textvariable
	mset [list @inchannels0  @inchannels1  @inchannels2  @inchannels3]  $@inchannels
	mset [list @outchannels0 @outchannels1 @outchannels2 @outchannels3] $@outchannels
	set @audio_api_choice2 [say [lindex $::pd_apilist2 $@audio_api_choice]]
	if {![winfo exists .$self.1.1]} {$self   init_content} else {$self update_content}
}
def ServerPrefsDialogue audio_properties=? {} {
	set indev0 [lsearch $@audioindev $@audioindev0]
	set outdev0 [lsearch $@audiooutdev $@audiooutdev0]
	return [list $indev0  0 0 0  $@inchannels0  $@inchannels1  $@inchannels2  $@inchannels3 \
		     $outdev0 0 0 0 $@outchannels0 $@outchannels1 $@outchannels2 $@outchannels3 \
	             $@r $@blocksize $@audiobuf]
}
def ServerPrefsDialogue read_one {type name contents i} {
	switch -- $type {
		folders   {incr i; lappend @$name [lindex $contents $i]}
		libraries {incr i; lappend @$name [lindex $contents $i]}
		files     {incr i; lappend @$name [lindex $contents $i]}
		choice    {
			if {$name == "audio_api_choice"} {
				if {$@$name == ""} {set @$name [lsearch $::pd_apilist2 [lindex $contents $i]]}
			} else {set @$name [lindex $contents $i]}
		}
		void      {        set @$name 1}
		default   {incr i; set @$name [lindex $contents $i]}
	}
	incr i
	return $i
}
def ServerPrefsDialogue read {} {
	global pdrc_options pdrc_options_h cmdline
	set fd [open $cmdline(rcfilename) "RDONLY CREAT"]
	set contents {}
	foreach line [split [read $fd] "\n"] {
		if {[string index $line 0] != "#"} {lappend contents $line}
	}
	close $fd
	set contents [concat [join $contents " "]] ;# concat casts to list type (faster) (?)
	set i 0

	# prevent Tk8.5 from showing grey checkmarks
	foreach name {-nodac -noadc -32bit -nomidiin -nomidiout -verbose -noloadbang -listdev -realtime} {set _($self:$name) 0}

	while {$i < [llength $contents]} {
		set op [lindex $contents $i]
		if {[string length $op]==0} {break}
		if {![info exists pdrc_options_h($op)]} {post "unknown option: %s" $op; incr i; continue}
		mset {type name} $pdrc_options_h($op)
		set name [lindex [split $name "|"] 0]
		set i [$self read_one $type $name $contents $i]
	}
}
def ServerPrefsDialogue write {} {
	set fd [open $::cmdline(rcfilename) w]
	#set fd stdout; puts "WOULD SAVE:"
	foreach {type names} $::pdrc_options {
		set name [lindex [split [lindex $names 0] "|"] 0]
		if {[info exists _($self:$name)]} {set v $_($self:$name)} ;# bug in objective.tcl ?
		switch $type {
			folders   {foreach item [$v get 0 end] {puts $fd "$name $item"}}
			libraries {foreach item [$v get 0 end] {puts $fd "$name $item"}}
			#files    {foreach item $v {puts $fd "$name $item"}}
			void      {if {$v != ""} {if {$v} {puts $fd $name}}}
			choice    {
				if {$name != "audio_api_choice"} {set vv [lindex $names [expr 1+$v]]} \
				else                             {set vv [lindex $::pd_apilist2 $v] }
				if {$vv != "default"} {puts $fd $vv}
			}
			devlist {}
			default   {if {[string length $v]} {puts $fd "$name $v"}}
		}
	}
	close $fd
	#puts "THE END"
}
#def ServerPrefsDialogue reset {} {}
def ServerPrefsDialogue init_content {} {
	set f .$self.1
	set section 0
	set @label_width 200 ;# 24
	foreach {type names} $::pdrc_options {
		set name [lindex [split [lindex $names 0] "|"] 0]
		switch $type { void { set type toggle }}
		switch $type {
			section {
				set @label_width [expr 6*[lindex $names 1]] ;# % of 600 px
				frame .$self.1.[incr section]
				$@nb add .$self.1.$section -text [say $name]
			}
			choice  {
				if {$name == "audio_api_choice"} {
					set ops $::pd_apilist2
				} else {
					set ops [lrange $names 1 end]
				}
				$self add $f.$section [list $name choice -choices $ops]
			}
			devlist {$self add $f.$section [list $name devlist] }
			spins   {$self add $f.$section [list $name spins [lindex $names 1]] }
			default {$self add $f.$section [list $name $type]}
		}
	}
}
def ServerPrefsDialogue update_content {} {$self update_channels}
def ServerPrefsDialogue update_channels {} {
	set  indev_len [llength $@audioindev]
	set outdev_len [llength $@audiooutdev]
	set i 0
	foreach chan  $@inchannels {
		if {$i< $indev_len} {set s "readonly"} else {set s "disabled"}
		.$self.1.1.-inchannels.$i configure -state $s
		incr i
	}
	set i 0
	foreach chan $@outchannels {
		if {$i<$outdev_len} {set s "readonly"} else {set s "disabled"}
		.$self.1.1.-outchannels.$i configure -state $s
		incr i
	}
}
def ServerPrefsDialogue init {} {
	netsend [list pd audio-properties]
	$self init_reverse_hash
	$self read
	super reset cancel apply ok
	# pd pd  midi-properties
}
def ServerPrefsDialogue dropmenu_set {frame var part val} {
	set trim_part [string trimleft $part "-"]
	set trim_var [string trimleft $var "-"]
	if {$var == "audio_api_choice"} {
		foreach api $::pd_apilist {
			if {$trim_part == [string tolower [lindex $api 0]]} {
				netsend [list pd audio-setapi [lindex $api 1]]
				after 1 [netsend [list pd audio-properties]]
			}
		}
	} else {
		set ::_($self:${trim_var}0) $part
	}
	super $frame $var $part $val
}
#used by choice and devlist
def ServerPrefsDialogue dropmenu_open {f name} {
	set trim_name [string trimleft $name "-"]
	if {$trim_name != "audio_api_choice"} {
		set i 0
		set m $f.menu
		$m delete 0 end
		foreach part $@$trim_name {
			$m add command -label $part -command [list $self dropmenu_set $f $name $part $i]
			incr i
		}
	}
	super $f
}

#################### ClientPrefsDialogue
set ddrc_options {
section Client section_color
  subsection Client canvas_color
    color Canvas bgedit
    color Canvas bgrun
    color Canvas grid
  subsection Client object_color
    color View bg
    color View fg
    color View frame1
    color View frame2
    color View frame3
    color Comment bg
    color Comment fg
    color Comment frame1
    color Comment frame2
    color Comment frame3
    color View selectframe
    font View font
  subsection Client wire_color
    color Wire fg
    color Wire dspfg
    color Wire fg2
    color FutureWire dash
  subsection Client others_color
    color Box inletfg
    color Box outletfg
    color SelRect rect
    font KeyboardDialogue font
    font Console font
section Client keys
  subsection Client put
    key Canvas Object
    key Canvas Message
    key Canvas {Number nbx}
    key Canvas Symbol
    key Canvas Comment
    key Canvas bng
    key Canvas tgl
    key Canvas {vsl hsl}
    key Canvas {vradio hradio}
    key Canvas vu
    key Canvas cnv
    key Canvas Graph
    key Canvas Array
  subsection Client edit
    key Canvas {cut copy}
    key Canvas {undo redo}
    key Canvas {paste duplicate}
    key Canvas select_all
    key Canvas clear_selection
    key Canvas {reload redraw}
    key Canvas editmodeswitch
    key Canvas {insert_object chain_object}
    key Canvas {clear_wires auto_wire}
    key Canvas subpatcherize
  subsection Client general
    key Canvas Pdwindow
    key Canvas {new_file open_file}
    key Canvas {save save_as}
    key Client {server_prefs client_prefs}
    key Canvas {close quit}
    key Canvas {find find_again}
    key Canvas {audio_on audio_off}
    key Client {audio_settings midi_settings}
    key Client test_audio_and_midi
    key Canvas {load_meter latency_meter}
    key Canvas about
  subsection Canvas keynav
    key Canvas {key_nav_up key_nav_up_shift}
    key Canvas {key_nav_down key_nav_down_shift}
    key Canvas {key_nav_right key_nav_right_shift}
    key Canvas {key_nav_left key_nav_left_shift}
    key Canvas key_nav_ioselect
section Client others
    toggle Canvas hairstate
    toggle Canvas hairsnap
    toggle Canvas gridstate
    integer Canvas grid_size
    toggle Canvas snap_grid
    toggle Canvas buttonbar
    toggle Canvas statusbar
    toggle Canvas menubar
    toggle Canvas scrollbar
    toggle View tooltip
    toggle Wire wirearrow
    integer Client console
    language View language
    language View language2
    integer Canvas pointer_sense
}

class_new ClientPrefsDialogue {PagedDialogue}
def ClientPrefsDialogue apply {} {$self write; $self do_apply}
def ClientPrefsDialogue read {} {read_ddrc}
def ClientPrefsDialogue do_apply {} {
	foreach canvas $::window_list {
		if {[$canvas class] == "Canvas"} {
			$canvas activate_menubar= [$canvas look menubar]
			$canvas activate_buttonbar= [$canvas look buttonbar]
			$canvas activate_statusbar= [$canvas look statusbar]
			$canvas activate_scrollbars= [$canvas look scrollbar]
			$canvas activate_grid= [$canvas look gridstate]
			$canvas redraw
		}
	}
}
def ClientPrefsDialogue write {} {
	global look key
	$self get_val
	set fd [open $::cmdline(ddrcfilename) w]
	foreach category {look key} {
		set class_list {}
		puts $fd "$category \{"
		foreach name [array names $category] {
			mset {class var} [split $name ":"]
			lappend class_list $class
		}
		set class_list [luniq [lsort $class_list]]
		foreach class $class_list {
			puts $fd "  $class \{"
			foreach name [lsort [array names $category -glob $class:*]] {
				mset {class var} [split $name ":"]
				# the eval here annoys me a bit because of possible special chars -- matju
				puts $fd "    $var [eval list \$${category}($class:$var)]"
				#puts " $var $category $class $var"
			}
			puts $fd "  \}"
		}
		puts $fd "\}"
	}
	close $fd
}
#this retrieves the values set in the editor
def ClientPrefsDialogue get_val {} {
	global ddrc_options look key accels
	set check_key {}
	foreach {type class name} $ddrc_options {
		switch -regexp -- $type {
			color {
				set str [string tolower $class$name]
				set look($class:$name) $@$str
			}
			key {
				foreach item $name {
					set new_key $@$item
					set old_key $key($class:$item)
					if {$key($class:$item) != $new_key} {
						if {[dict exists $accels $old_key]} {
							set cmd    [dict get    $accels $old_key]
							set accels [dict remove $accels $old_key]
							dict set accels $new_key $cmd
						}
					}
					if {[dict exists $check_key $new_key] && $new_key != ""} {
						error "$new_key already assigned"
					} else {dict set check_key $new_key key($item)}
					set key($class:$item) $new_key
				}
			}
			toggle  {set look($class:$name) $@$name}
			integer {set look($class:$name) $@$name}
			choice|language {
			  if {$@$name == "autolanguage"} {set l auto} {set l $@$name}
			  set look($class:$name) $l
			}
			#font {set look(View:font) $@str}
		}
	}
}
def ClientPrefsDialogue reset {} {
	# this should reload defaults.ddrc ?
}
def ClientPrefsDialogue revert {} {
	# this should reload currently used settings ?
}
def ClientPrefsDialogue init {} {
	global look key
	super cancel apply ok
	#super cancel reset revert apply ok
	set f .$self.1
	set section 0
	set subsection 0
	set @label_width 200 ;# 24
	foreach {type class names} $::ddrc_options {
		set name [lindex [split $names |] 0]
		switch $type { void { set type toggle }}
		switch $type {
			section {
				incr section
				$@nb add [ttk::notebook $@nb.$section] -text [say $name]
				set which_section $f.$section
				set which_self $self
				set subsection 0
			}
			subsection {
				set subself $self.1.$section
				.$subself add [frame .$subself.[incr subsection]] -text [say $name]
				set which_section .$subself.$subsection
				set which_self $subself
			}
			language {
				set @$name $look(View:$name)
				say autolanguage "[say auto] ([say [lindex [figure_out_language [guess_lang]] 1]])"
				$self add $which_section [list $name choice -choices [concat autolanguage $::langoptions]]
			}
			color {
				set str [string tolower $class$name]
				set @$str $look($class:$name)
				$self add $which_section [list $str $type]
			}
			key {
				foreach item $name {set @$item $key($class:$item)}
				$self add $which_section [list $name $type]
			}
			toggle {
				set @$name $look($class:$name)
				$self add $which_section [list $name $type]
			}
			font {
				set str [string tolower $class$name]
				set @$str $look($class:$name)
				$self add $which_section [list $str $type $class]
			}
			default {
				switch $name {
					console {set @$name $look(Client:console)}
					pointer_sense {set @$name $look(Canvas:pointer_sense)}
					grid_size {set @$name $look(Canvas:grid_size)}
					default {}
				}
				$self add $which_section [list $name $type]
			}
		}
	}
}
def ClientPrefsDialogue dropmenu_set {frame var part val} {set @$var $part; $frame.butt configure -text [say $part]}
def ClientPrefsDialogue dropmenu_open {f name} {super $f}

############ find dialogue ###########

class_new FindDialogue {Dialogue}
def FindDialogue init {canvas} {
	super cancel find
	set @canvas $canvas
	set @break 0
	set f .$self
	$self add $f [list "string" "entry"]
	focus .find.string.entry
}
def FindDialogue find {} {$self ok}
def FindDialogue ok {} {
	$@canvas find_string= $@string
	$@canvas search
	super
}
############ other stuff #########

def Client client_class_tree {} {ClientClassTreeDialogue new}
class_new ClientClassTreeDialogue {Dialogue}

proc* place_stuff {args} {}

def ClientClassTreeDialogue make_row {w tree} {
	pack [frame $w] -fill x
	pack [frame $w.row] -side top -fill x
	pack [button $w.row.butt -image icon_minus] -side left
	pack [label $w.row.label -text [lindex $tree 0]] -side left -fill x
	pack [frame $w.dent -width 32] -side left
	set i 1
	foreach node [lrange $tree 1 end] {
		$self make_row $w.$i $node
		incr i
	}
}
def ClientClassTreeDialogue init {} {
	super close
	pack [frame .$self.1   -width 600 -height 400] -fill y -expand y
	pack [frame .$self.1.1 -width 600 -height 400 -bg "#6688aa"] -side left -fill y -expand y
	# "$w.1.scroll set"
	# i'd like a scrollable frame
	pack [scrollbar .$self.1.scroll -command "ClientClassTreeDialogue_scroll $self"] -side left -fill y -expand y
	place [frame .$self.1.1.tree] -x 0 -y 0
	set w .$self.1.1.tree.1
	$self make_row $w [Thing get_hierarchy]
	after 100 "$self update_scrollbar"
}
def ClientClassTreeDialogue update_scrollbar {} {
	set w .$self.1.1
	set zy [winfo height $w]
	set sy [winfo height $w.tree]
	set y1 [expr   0.0-[winfo y $w.tree]]
	set y2 [expr   0.0+$y1+$zy]
	.$self.1.scroll set [expr $y1/$sy] [expr $y2/$sy]
}
def ClientClassTreeDialogue scroll {args} {
	set w .$self.1.1
	set zy [winfo height $w]
	set sy [winfo height $w.tree]
	switch [lindex $args 0] {
		moveto {
			set y [clip [expr (0.0-[lindex $args 1])*$sy] [expr $zy-$sy] 0]
			place .$self.1.1.tree -x 0 -y $y
			puts "args=[list $args] zy=$zy sy=$sy y=$y"
		}
		scroll {
		}
	}
	after 100 "$self update_scrollbar"
}

class_new AboutDialogue {Dialogue}
def AboutDialogue init {} {
	super close
        wm title .$self "About DesireData"
        pack [label .$self.title -text $::pd_version -font {helvetica 18}] -side top
	pack [text .$self.text -yscrollcommand ".$self.scroll set" -width 72 -height 36] -side left -fill both -expand yes
	pack [scrollbar .$self.scroll -command ".$self.text yview"] -side right -fill y -expand yes
#12345678901234567890123456789012345678901234567890123456789012345678901 <- 72 chars
	.$self.text insert 0.0 \
"DesireData is a free (libre) real-time computer programming language
interpreter focused on realtime audio and video.
You can get DesireData from http://desiredata.goto10.org/

DesireData and PureData work on Linux, MacOSX, Windows, and others.

PureData is copyrighted, but is free for you to use for any reasonable
purpose, according to the SIBSD license. DesireData's client section
also is free, according to the GPL license. DesireData's server section
is an adaptation of the PureData code and is also using the SIBSD
license.

(insert here: links to both licenses)

Credits:
  DesireData server: Mathieu Bouchard
  DesireData client: Mathieu Bouchard & Chun Lee
  PureData: Miller Puckette feat. Thomas Musil,
  G�nther Geiger, Krzysztof Czaja, Iohannes Zm�lnig & others.

  Translations:
    Fran�ais (French): Patrice Colet
    Catal� (Catalan): N�ria Verges
    Espa�ol (Spanish): Mario Mora, Ramiro Cosentino
    Deutsch (German): Max Neupert, Georg Holzmann, Thomas Grill
    Norsk Bokm�l (Norwegian): Gisle Fr�ysland
    Portugu�s (Portuguese): Nuno Godinho
    Portugu�s do Brasil (Brazilian Portuguese): Carlos Paulino
    Italiano (Italian): Davide Morelli, Federico Ferri
    Euskara (Basque): Ibon Rodriguez Garcia
    Nihongo (Japanese): Kentaro Fukuchi
    Polski (Polish): Michal Seta
    Dansk (Danish): Steffen Leve Poulsen
    Zong wen (Chinese): Chun Lee
    Nederlands (Dutch): Tim Vets
    T�rk�e (Turkish): Koray Tahiroglu
    Russkij (Russian): Ilya Dmitrichenko

Much more documentation and other resources at http://puredata.info/
The Pd mailing list archive at http://lists.puredata.info/pipermail/pd-list/

(insert here: link to \"reference documentation for Pd\", that is, chapter 1)"

	# this looks bad on OSX, iirc
	.$self.text configure -state disabled
}

set manager [Manager new]

def Class post_hierarchy {{i 0}} {
	post %*s%s [expr $i*2] "" $self
	foreach sub $@subclasses {$sub post_hierarchy [expr $i+1]}
}
def Class get_hierarchy {} {
	set l [list $self]
	foreach sub $@subclasses {lappend l [$sub get_hierarchy]}
	return $l
}

# Thing post_hierarchy

#----------------------------------------------------------------
class_new ClipboardDialogue {Dialogue}
def ClipboardDialogue init {clipboard} {
	super close
	set @clipboard $clipboard
        wm title .$self "Clipboard"
	pack [text .$self.text -yscrollcommand ".$self.scroll set" -width 72] -side left -fill both -expand yes
	pack [scrollbar .$self.scroll -command ".$self.text yview"] -side right -fill y
	$@clipboard subscribe $self
	$self notice
}
def ClipboardDialogue notice {args} {
	.$self.text delete 0.0 end
	.$self.text insert 0.0 [$@clipboard value]
}
def ClipboardDialogue delete {} {
	$@clipboard unsubscribe $self
	super
}

#----------------------------------------------------------------
class_new ListDialogue {Dialogue}
def ListDialogue init {history title} {
	super close
	set @history $history
	wm title .$self $title
	frame .$self.1
	pack [listbox .$self.1.list -yscrollcommand ".$self.1.scroll set" -width 72 -height 20] -side left -fill both -expand yes
	pack [scrollbar .$self.1.scroll -command ".$self.1.list yview"] -side right -fill y
	pack .$self.1
	$@history subscribe $self
	$self notice
}
def ListDialogue listbox {} {return .$self.1.list}
def ListDialogue notice {args} {
	set w [$self listbox]
	$w delete 0 end
	foreach e [$@history list] {$w insert end $e}
	$w see end
}
def ListDialogue delete {} {
	$@history unsubscribe $self
	super
}

class_new EventHistoryDialogue {ListDialogue}
def EventHistoryDialogue init {history} {
	super $history [say event_history_view]
	pack [checkbutton .$self.hide -text [say hide_key_release]] -fill x
	[$self listbox] configure -font {Mono -10}
}

#----------------------------------------------------------------
#class_new Splash {Thing}
#def Splash init {} {
#	toplevel .$self
#	frame .$self.f
#	canvas .$self.f.canvas
#	image create photo .dd -format GIF -file desiredata.gif
#	.$self.f.canvas create image 0 0 -image .dd
#	pack .$self.f.canvas
#	pack .$self.f
#}

class_new KeyboardDialogue {Dialogue}

set keyboard_layouts {
    {
	{9 " " 67 68 69 70 " " 71 72 73 74 " " 75 76 95 96}
	{49 10 11 12 13 14 15 16 17 18 19 20 21 22}
	{23 24 25 26 27 28 29 30 31 32 33 34 35 51}
	{66 38 39 40 41 42 43 44 45 46 47 48 36}
	{50 52 53 54 55 56 57 58 59 60 61 62}
	{37 115 64 65 113 116 117 109}
    } {
	{" " " " 98 " "}
	{100 " " " " 102}
	{" " " " 104 " "}
	{" "}
        {" " " " 4 " "}
	{1 2 3}
        {" " " " 5 " "}
    }
}

foreach {k v} {
	9 5
	22 5
	23 4 51 4
	66 5 36 7
	50 8 62 8
	37 4 115 4 64 4 65 24 113 4 116 4 117 4 109 4
        98 2 100 2 102 2 104 2
	1 2 2 2 3 2 4 2 5 2
} {set keyboard_width_of($k) $v}

proc namekey {i args} {foreach name $args {set ::keyboard_text_of($i) $name; incr i}}
namekey 9 Esc
namekey 67 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10
namekey 95 F11 F12
namekey 49 `
namekey 10 "1 !" "2 @" "3 #" "4 \$" "5 %" "6 ^" "7 &" "8 *" "9 (" "0 )" "- _" "= +" BkSp
namekey 23 Tab Q W E R T Y U I O P "\{ \[" "\} \]"
namekey 51 "\\ |"
namekey 66 Caps
namekey 38 A S D F G H J K L "\; :" "' \""
namekey 36 Return
namekey 50 Shift
namekey 52 Z X C V B N M ", <" ". >" "/ ?" Shift
namekey 37 Ctrl
namekey 115 Sup
namekey 64 Alt Space
namekey 113 AltGr
namekey 116 Sup Menu
namekey 109 Ctrl
namekey 98 (up)
namekey 100 (left)
namekey 102 (right)
namekey 104 (down)
namekey 1 1 2 3 4 5
#mouse clicks

def KeyboardDialogue init {history} {
	super close
	set @history $history
	set @fade [dict create]
	wm title .$self "Keyboard View" ;# say
	set i 0; set j 0
	set @names {key misc}
	frame .$self.board
	set layouts {::keyboard_layout ::keyboard_layout2}
	set @keycount 0
	foreach layout $::keyboard_layouts {
		$self pack_keys $layout [lindex $@names $i] [llength [lindex $::keyboard_layouts [expr $i-1]]]
		incr i
	}
	pack .$self.board
	$@history subscribe $self
	$self fade
}

def KeyboardDialogue pack_keys {keys name off} {
	set i $off
	frame .$self.board.$name
	foreach row $keys {
		frame .$self.board.$name.$i
		foreach key $row {
			if {$key==" "} {
				pack [label .$self.board.$name.$i.shim$@keycount -image icon_empty] -side left
				incr @keycount
				continue
			}
			set ::keyboard_row_of($key) $i
			if {[info exists ::keyboard_width_of($key)]} {set width $::keyboard_width_of($key)} else {set width 3}
			if {[info exists ::keyboard_text_of($key)]}  {set text  $::keyboard_text_of($key) } else {set text $key}
			if {[regexp {\((\w+)\)} $text foo bar]} {
				set font [$self look font]
				pack [label .$self.board.$name.$i.key$key -image icon_$bar -relief raise -border 4 \
					-side left -bg "#dddddd" -width [expr $width*[font measure $font 0]] \
					-height [font metrics $font -linespace]]
			} else {
				pack [label .$self.board.$name.$i.key$key -text " $text "  -relief raise -border 4 \
					-side left -bg "#dddddd" -width $width -font [$self look font]]
			}
		}
		pack .$self.board.$name.$i -fill x
		if {$i==0} {pack [label .$self.board.$name.shim -image icon_empty] -fill x -expand yes}
		incr i
	}
	switch $name {
		key {pack .$self.board.key -side left}
		misc {pack .$self.board.misc -side right}
	}
}

def KeyboardDialogue notice {origin add event} {
	mset {type W x y mod K k} $event
	if {![info exists ::keyboard_row_of($k)]} {puts "unknown key $k"; return}
	set i $::keyboard_row_of($k)
	if {$i<[llength [lindex $::keyboard_layouts 0]]} {set section "key"} else {set section "misc"}
	switch -regexp -- $type {
		^KeyPress|ButtonPress$   {
			if { [dict exists $@fade $k]} {dict unset @fade $k}
			.$self.board.$section.$i.key$k configure -bg "#ff0000"
		}
		^KeyRelease|ButtonRelease$ {if {![dict exists $@fade $k]} {dict set @fade $k 255}}
	}
}

def KeyboardDialogue fade {} {
	foreach {k v} $@fade {
		incr v -85
		if {$v<0} {set v 0}
		set r [expr 221+$v*2/15]
		set g [expr 221-$v*13/15]
		set i $::keyboard_row_of($k)
		if {$i<[llength [lindex $::keyboard_layouts 0]]} {set section "key"} else {set section "misc"}
		.$self.board.$section.$i.key$k configure -bg [format #%02x%02x%02x $r $g $g]
		if {$v} {dict set @fade $k $v} {dict unset @fade $k}
	}
	set @after [after 100 "$self fade"]
}

def KeyboardDialogue delete {} {
  $@history unsubscribe $self
  after cancel $@after
  super
}

#----------------------------------------------------------------
# Deconstructors

def Wire deconstruct {{selcanvas ""}} {
	# the selcanvas system could be abstracted out using an ObjectList such that
	#   Canvas<ObjectList and $selection class == ObjectList
	if {$selcanvas == ""} {
		list #X connect \
		    [[$@canvas objects] search $@from] $@outlet \
		    [[$@canvas objects] search   $@to]  $@inlet
	} {
		list #X connect \
		    $::obj_index_sel($@canvas:$@from) $@outlet \
		    $::obj_index_sel($@canvas:$@to)   $@inlet
	}
}

def MessageBox deconstruct {} {concat [list #X msg        $@x1 $@y1] $@text}
def FloatBox   deconstruct {} {concat [list #X floatatom  $@x1 $@y1] $@w $@min $@max $@pos $@lab $@snd $@rcv}
def SymbolBox  deconstruct {} {concat [list #X symbolatom $@x1 $@y1] $@w $@min $@max $@pos $@lab $@snd $@rcv}
def Comment    deconstruct {} {concat [list #X text       $@x1 $@y1] $@text}

def Box deconstruct {} {
	if {[array names ::fields -exact $@pdclass] == ""} {
		return [concat [list #X obj  $@x1 $@y1] $@text]
	} {
		set r {}
		foreach field $::fields($@pdclass) {lappend r $_($self:$field)}
		return $r
	}
}

def View deconstruct_to {stream args} {
	$stream << [philtre [eval [concat [list $self deconstruct] $args]]]
	$stream << ";\n"
}

def Canvas deconstruct {} {
	return [concat [list #X restore $@x1 $@y1] $@text]
}
def Canvas deconstruct_to {stream args} {
	set r [concat [list #N canvas] $@canvas_pos $@canvas_size]
	if {$@subpatch || $@abs} {lappend r $@name $@mapped} else {lappend r $@fontsize}
	$stream << "[philtre $r];\n"
	foreach i [lsort -integer [$@objects keys]] {eval [concat [list [$@objects get $i] deconstruct_to $stream]]}
	foreach i [lsort -integer [  $@wires keys]] {eval [concat [list [  $@wires get $i] deconstruct_to $stream]]}
	$stream << [philtre [eval [concat [list $self deconstruct] $args]]]
	$stream << ";\n"
}

#----------------------------------------------------------------
# openpanel & savepanel

proc pdtk_openpanel {target localdir} {
    if {$localdir == ""} {set localdir $::pd_opendir}
    set filename [tk_getOpenFile -parent $::current_window -initialdir $localdir]
    if {$filename != ""} {
        set directory [string range $filename 0 [expr [string last / $filename]-1]]
        set pd_opendir $directory
        netsend [list $target callback [enquote $filename]]
    }
}

proc pdtk_savepanel {target localdir} {
    if {$localdir == ""} {
    	set filename [tk_getSaveFile -parent $::current_window]
    } else {
    	set filename [tk_getSaveFile -parent $::current_window -initialdir $localdir]
    }
    if {$filename != ""} {
        netsend [list $target callback [enquote $filename]]
    }
}

#----------------------------------------------------------------
# To err is human.

#proc bgerror {err} {
#	set info [error_dump]
#	tk_messageBox2 -message "$err: $info" -type ok
#}

class_new OopsDialogue {Dialogue}

def OopsDialogue init {sig1 sig2 where} {
	super damn
	wm title .$self [say oops]
	pack [label .$self.head -text $sig2 -font {Helvetica -14 bold}] -side top
	pack [label .$self.note -text [say oops_text]] -side top
	pack [text .$self.text -yscrollcommand ".$self.scroll set" -width 108 -height 15] -side left -fill both -expand 1
	pack [scrollbar .$self.scroll -command ".$self.text yview"] -side right -fill y
	.$self.text insert 0.0 $where
}

# Nous sommes donc en pr�sence d'un incendie. C'est normal...
def OopsDialogue damn {} {$self ok}

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

set main [Client new]
set window_list [list $main]

def Canvas auto_test {} {
	$self editmode= 1
	$self select_all
	$self selection_move +10 0
	$self selection_move +10 0
	$self selection_move +10 0
}

#-----------------------------------------------------------------
def Canvas visual_diff {} {
	if {$@blinky != ""} {
		.$self.c delete BLINKY
		after cancel $@blinky
		set @blinky ""
		return
	}
	set filename [$self name]
	set initialfile ""
        foreach suffix {gif jpg jpeg png} {
	    set t [$self folder]/$filename.$suffix
	    post %s $t
	    if {[file exist $t]} {set initialfile $filename.$suffix; break}
        }
	set filename [tk_getOpenFile -parent $::current_window -defaultextension .pd \
		-filetypes $::image_filetypes -initialdir [$self folder] -initialfile $initialfile]
	if {$filename == ""} {return}
	image create photo image_$self -file $filename
	$self blink_image
}

def Canvas blink_image {} {
	set @blinky [after 300 [list $self blink_image]]
	if {[llength [.$self.c gettags BLINKY]]} {
		.$self.c delete BLINKY
	} else {
		# there's something that causes the pd screenshot to be off by 1 pixel. i don't know what.
		.$self.c create image 0 -1 -image image_$self -tag BLINKY -anchor nw
	}
}

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

proc widget_tree {w {indent 0}} {
  foreach element [winfo children $w] {
    puts [format "%*s%s" [expr 2*$indent] "" $element]
    widget_tree $element [expr 2+$indent]
  }
}

if 0 {
def Canvas :-) {} {
  if {![info exists ::pd_path]} {netsend [list pd update-path]}
  if {![info exists ::class_list]} {return [netsend [list pd update-class-list $self :-)]]}
  search_for_externs
  set class_list [luniq [lsort $::class_list]]
  set n 0
  post "there are [llength $::class_list] classes"
  foreach c $::class_list {
    netsend [list .$self obj [expr int(1050*rand())] [expr int(650*rand())] $c]
    incr n
    if {$n>1500} {return "stopped at $n"}
  }
  return "finished"
}
}

def Canvas :-) {} {
  if {![info exists ::pd_path]} {netsend [list pd update-path]}
  if {![info exists ::class_list]} {return [netsend [list pd update-class-list $self :-)]]}
  search_for_externs
  set ::class_list [luniq [lsort $::class_list]]
  post "there are [llength $::class_list] classes"
  $self :-)2 1000 ""
}

def Canvas :-)2 {n meuh} {
  if {$n >= [llength $::class_list]} {return}
  set c [lindex $::class_list $n]
  set x [expr int(1050*rand())]; set y [expr int(650*rand())]
  set x [expr int($n/36)*16]; set y [expr ($n%36)*18]
  puts ":-) $c"
  netsend [list .$self obj $x $y $c] [list $self :-)2 [expr {$n+1}]]
  #if {$n>1500} {post "stopped at %d" $n}
}

proc pd {args} {post %s "what is 'proc pd' ?"}