#-------------------------------------------------------
# Useful tools for the Tcl-based version of magic
#-------------------------------------------------------
# This file is included by wrapper.tcl if it is found
# in the magic install directory.
#-------------------------------------------------------

# Suspend and resume drawing in windows
# Modified 8/17/04 so that calls to suspendall and resumeall
# may nest.
# Modified 11/23/16
# Modified 12/30/16 to add automatic button accelerator text

proc magic::suspendall {} {
   global Winopts
   if {[info commands winfo] != ""} {
      foreach window [magic::windownames layout] {
	 if {$window == 0} {continue}
         set framename [winfo toplevel $window]
         if {$framename == "."} {
	    set framename $window
         }
         if {[incr Winopts(${framename},suspend)] == 1} {
	    $window update suspend
         }
      }
   }
}

proc magic::resumeall {} {
   global Winopts
   if {[info commands winfo] != ""} {
      foreach window [magic::windownames layout] {
	 if {$window == 0} {continue}
         set framename [winfo toplevel $window]
         if {$framename == "."} {
	    set framename $window
         }
         if {$Winopts($framename,suspend) <= 0} {
      	    error "resume called without suspend"
         } else {
	    incr Winopts($framename,suspend) -1
	    if { $Winopts(${framename},suspend) <= 0 } {
	       unset Winopts(${framename},suspend)
	       $window update resume
	    }
	 }
      }
   }
}

#--------------------------------------------------------------------------
# Crash backups.  Create a new crash recovery backup every 10 minutes, or
# at the interval specified by Opts(backupinterval)
#--------------------------------------------------------------------------

proc magic::makecrashbackup {} {
   global Opts

   *bypass crash save
   if {![catch set Opts(backupinterval)]} {
      if {$Opts(backupinterval) > 0} {
         after $Opts(backupinterval) magic::makecrashbackup
      }
   }
}

#----------------------------------------------------------------
# magic::crashbackups ---
#
# Create periodic backups.  Options are:
#
#   start:	Begin periodic backups.  If interval is not
#		specified, then set interval to 10 minutes.
#
#   resume:	Resume periodic backups if started and stopped,
#		but not if disabled or never started.
#
#   stop:	Stop periodic backups.
#
#   disable:	Disable periodic backups;  set to state of
#		never having been started.
#
#----------------------------------------------------------------

proc magic::crashbackups {{option start}} {
   global Opts

   switch -exact $option {
      start {
         if {[catch set Opts(backupinterval)]} {
            set Opts(backupinterval) 600000
         }
	 if {$Opts(backupinterval) > 0} {
	    after $Opts(backupinterval) magic::makecrashbackup
	 }
      }
      resume {
         if {![catch set Opts(backupinterval)]} {
	    if {$Opts(backupinterval) > 0} {
	       after $Opts(backupinterval) magic::makecrashbackup
	    }
	 }
      }
      stop -
      cancel {
         after cancel magic::makecrashbackup
      }
      disable {
         after cancel magic::makecrashbackup
	 unset Opts(backupinterval)
      }
   }
}

#--------------------------------------------------------------------------
# Push and Pop---Treat the edit hierarchy like a stack.
#--------------------------------------------------------------------------

proc magic::pushstack {{name ""}} {
   global editstack
   if {$name == ""} {
      # no cell selected, so see if we can select one
      set selected [what -list]
      if {[llength [lindex $selected 2]] == 0} {
	 pushbox
	 select cell
	 popbox
      }
      set name [cellname list self]
   }

   if {$name == ""} {
       error "No cell to push!"
   } elseif {[llength $name] > 1} {
       error "Too many cells selected!"
   }
   if {[catch {lindex $editstack end}]} {
      set editstack {}
   }
   lappend editstack [view get]
   lappend editstack [cellname list window]
   set ltag [tag load]
   tag load {}
   load $name
   catch {magic::cellmanager}
   catch {magic::captions}
   tag load $ltag
   return
}

proc magic::popstack {} {
   global editstack
   if {[llength $editstack] == 0} {
      error "No subcell stack!"
   } else {
      set ltag [tag load]
      tag load {}
      suspendall
      load [lindex $editstack end]
      set snaptype [snap]
      snap internal
      view [lindex $editstack end-1]
      snap $snaptype
      catch {magic::cellmanager}
      catch {magic::captions}
      resumeall
      tag load $ltag
      set editstack [lrange $editstack 0 end-2]
   }
   return
}

proc magic::clearstack {} {
   global editstack
   set editstack {}
}

# More stacking stuff---stacked box values

#---------------------------------------------------------------------
# pushbox --
#       Remember the current box values
#
#---------------------------------------------------------------------

proc magic::pushbox {{values {}}} {
   global boxstack
   set snaptype [snap list]
   snap internal
   if {[catch {set boxstack}]} {
      set boxstack {}
   }
   if {$values == {}} {
      lappend boxstack [box values]
   } else {
      lappend boxstack $values
   }
   snap $snaptype
   return
}

#---------------------------------------------------------------------
# popbox --
#       Recall the last pushed box position
#
# Option "type" may be empty, or "size" or "position" to pop a specific
# box size or position without affecting the other box parameters.
#---------------------------------------------------------------------

proc magic::popbox {{type values}} {
   global boxstack
   set snaptype [snap list]
   snap internal
   if {[catch {set boxstack}]} {
      error "No stack"
   } elseif {$boxstack == {}} {
      error "Empty stack"
   }
   set b [lindex $boxstack end]
   switch -exact $type {
      values {
        box values [lindex $b 0] [lindex $b 1] [lindex $b 2] [lindex $b 3]
      }
      size {
        box size [expr {[lindex $b 2] - [lindex $b 0]}] \
                  [expr {[lindex $b 3] - [lindex $b 1]}]
      }
      position {
        box position [lindex $b 0] [lindex $b 1]
      }
   }
   set boxstack [lrange $boxstack 0 end-1]
   snap $snaptype
   return $b
}

#---------------------------------------------------------------------
# peekbox --
#       Shell procedure that calls popbox but follows by pushing the
#       popped value back onto the stack, resulting in a "peek" mode.
#
# Options are the same as for "popbox" (see above).
#---------------------------------------------------------------------

proc magic::peekbox {{type values}} {
   global bidx
   if {![catch {set b [magic::popbox $type]}]} {
      magic::pushbox $b
   } else {
      error "No stack"
   }
   return $b
}

#---------------------------------------------------------------------
# Automatic handling of menu button accelerator text
#---------------------------------------------------------------------

proc magic::button_auto_bind_text {framename} {
    set macrolist [string trimleft [string trimright \
		[string map {magic:: {}} [macro list -reverse]]]]
    set macrodict [dict create {*}${macrolist}]
    set menutop [winfo children ${framename}.titlebar.mbuttons]
    foreach menub $menutop {
	set menuw [lindex [winfo children $menub] 0]
	set items [$menuw index end]
        for {set i 0} {$i <= $items} {incr i} {
	    set itype [$menuw type $i]
	    if {$itype == "command"} {
		set icmd [string trimleft [string trimright \
			[string map {magic:: {}} [$menuw entrycget $i -command]]]]
		if {![catch {set keyname [dict get $macrodict $icmd]}]} {
 		    set canonname [string map \
				{Control_ ^ XK_ {} less < more > comma , question ?}\
				$keyname]
		    $menuw entryconfigure $i -accelerator "(${canonname})"
		} else {
		    $menuw entryconfigure $i -accelerator ""
		}
	    }
	}
    }
}

#---------------------------------------------------------------------
# Text auto-increment and auto-decrement
#---------------------------------------------------------------------

proc magic::autoincr {{amount 1}} {
   set mtext [macro list .]
   set num [regexp -inline {[+-]*[[:digit:]]+} $mtext]
   if {$num != ""} {
      incr num $amount
      regsub {[+-]*[[:digit:]]+} $mtext $num mtext
      eval $mtext
      macro . "$mtext"
   }
}

magic::macro XK_plus {magic::autoincr 1}
magic::macro XK_minus {magic::autoincr -1}

#---------------------------------------------------------------------
# The following several routines are designed to aid in generating
# documentation for technology files, or to generate design rule
# documents using magic layout windows in a Tk tabbed-window
# framework.
#---------------------------------------------------------------------

#---------------------------------------------------------------------
# Ruler generation using the "element" command
# A line with arrows is drawn showing the dimension of the cursor box.
# The text of "text", if non-NULL, is placed in the middle of the
# ruler area.  The orientation of "orient" describes whether the
# ruler is a vertical or horizontal measurement.  By default, the
# longest dimension of the box is the orientation.
#---------------------------------------------------------------------

proc magic::ruler {{text {}} {orient auto}} {
   global Opts

   if {[catch {set Opts(rulers)}]} {
      set Opts(rulers) 0
   } else {
      incr Opts(rulers)
   }

   set bv [box values]
   set llx [lindex $bv 0]
   set lly [lindex $bv 1]
   set urx [lindex $bv 2]
   set ury [lindex $bv 3]

   set width [expr {[lindex $bv 2] - [lindex $bv 0]}]
   set height [expr {[lindex $bv 3] - [lindex $bv 1]}]
   if {$orient == "auto"} {
      if {$width > $height} {
	 set orient "horizontal"
      } else {
	 set orient "vertical"
      }
   }

   if {[llength $text] > 0} {
      if {$orient == "horizontal"} {
         set tclr 4
      } else {
         set tclr 2
      }
   } else {
      set tclr 0
   }

   set mmx [expr {($llx + $urx) / 2}]
   set mmy [expr {($lly + $ury) / 2}]

   set snapsave [snap]
   snap internal

   if {$orient == "horizontal"} {
      element add line l1_$Opts(rulers) black $llx $lly $llx $ury
      element add line l4_$Opts(rulers) black $urx $lly $urx $ury

      set mmx1 [expr {$mmx - $tclr}]
      set mmx2 [expr {$mmx + $tclr}]
      if {$mmx1 == $llx} {set mmx1 [expr {$llx - 2}]}
      if {$mmx2 == $urx} {set mmx2 [expr {$urx + 2}]}

      element add line l2_$Opts(rulers) black $llx $mmy $mmx1 $mmy
      element add line l3_$Opts(rulers) black $mmx2 $mmy $urx $mmy

      if {$tclr > 0} {
         element add text t_$Opts(rulers) black $mmx $mmy $text
      }
      if {$llx < $mmx1} {
	  element configure l2_$Opts(rulers) flags arrowleft
      } else {
	  element configure l2_$Opts(rulers) flags arrowright
      }
      if {$urx > $mmx2} {
	  element configure l3_$Opts(rulers) flags arrowright
      } else {
	  element configure l3_$Opts(rulers) flags arrowleft
      }

   } else {
      element add line l1_$Opts(rulers) black $llx $lly $urx $lly
      element add line l4_$Opts(rulers) black $llx $ury $urx $ury

      set mmy1 [expr {$mmy - $tclr}]
      set mmy2 [expr {$mmy + $tclr}]
      if {$mmy1 == $lly} {set mmy1 [expr {$lly - 2}]}
      if {$mmy2 == $ury} {set mmy2 [expr {$ury + 2}]}

      element add line l2_$Opts(rulers) black $mmx $lly $mmx $mmy1
      element add line l3_$Opts(rulers) black $mmx $mmy2 $mmx $ury

      if {$tclr > 0} {
         element add text t_$Opts(rulers) black $mmx $mmy $text
      }
      if {$lly < $mmy1} {
	  element configure l2_$Opts(rulers) flags arrowbottom
      } else {
	  element configure l2_$Opts(rulers) flags arrowtop
      }
      if {$ury > $mmy2} {
	  element configure l3_$Opts(rulers) flags arrowtop
      } else {
	  element configure l3_$Opts(rulers) flags arrowbottom
      }
   }
   snap $snapsave
}

#---------------------------------------------------------------------
# Automatic measurement ruler
#---------------------------------------------------------------------

proc magic::measure {{orient auto}} {

   set scale [cif scale out]

   set bv [box values]
   set llx [lindex $bv 0]
   set lly [lindex $bv 1]
   set urx [lindex $bv 2]
   set ury [lindex $bv 3]

   set width [expr {[lindex $bv 2] - [lindex $bv 0]}]
   set height [expr {[lindex $bv 3] - [lindex $bv 1]}]
   if {$orient == "auto"} {
      if {$width > $height} {
	 set orient "horizontal"
      } else {
	 set orient "vertical"
      }
   }

   if {$orient == "horizontal"} {
      set tval [expr {$scale * $width}]
   } else {
      set tval [expr {$scale * $height}]
   }
   set text [format "%g um" $tval]
   ruler $text $orient
}

#---------------------------------------------------------------------
# Remove all rulers (this should probably be refined to remove
# just the rulers under the box).
#---------------------------------------------------------------------

proc magic::unmeasure {} {
   set blist [element inbox]
   set mlist {}
   foreach m $blist {
      switch -regexp $m {
	 l[1-4]_[0-9] {
	    lappend mlist [string range $m 3 end]
	 }
	 t_[0-9] {
	    lappend mlist [string range $m 2 end]
         }
      }
   }
   set blist [lsort -unique $mlist]
   foreach m $blist {
      element delete t_$m
      element delete l1_$m
      element delete l2_$m
      element delete l3_$m
      element delete l4_$m
   }
}

#---------------------------------------------------------------------
# Key generation for annotating layouts.
#---------------------------------------------------------------------

proc magic::genkey {layer {keysize 4}} {
   global Opts

   box size $keysize $keysize
   paint $layer
   if {[catch {set Opts(keys)}]} {
      set Opts(keys) 0
   } else {
      incr Opts(keys)
   }
   # eval "element add rectangle keyrect$Opts(keys) subcircuit [box values]"

   box move e $keysize
   set bv [box values]
   set cx [expr {([lindex $bv 2] + [lindex $bv 0]) / 2}]
   set cy [expr {([lindex $bv 3] + [lindex $bv 1]) / 2}]
   element add text key$Opts(keys) white $cx $cy $layer
   element configure key$Opts(keys) flags east
}

#---------------------------------------------------------------------
# Because this file is read prior to setting the magic command
# names in Tcl, we cannot run the magic commands here.  Create
# a procedure to enable the commands, then run that procedure
# from the system .magic script.
#---------------------------------------------------------------------

proc magic::enable_tools {} {
   global Opts

   # Set keystrokes for push and pop
   magic::macro XK_greater {magic::pushstack [cellname list self]}
   magic::macro XK_less {magic::popstack}

   # Set keystrokes for the "tool" command.
   magic::macro space		{magic::tool}
   magic::macro Shift_space	{magic::tool box}

   set Opts(tool) box
   set Opts(motion) {}
   set Opts(origin) {0 0}
   set Opts(backupinterval) 60000
   magic::crashbackups start
}

#---------------------------------------------------------------------
# routine which tracks wire generation
#---------------------------------------------------------------------

proc magic::trackwire {window {option {}}} {
   global Opts

   if {$Opts(motion) == {}} {
      if {$option == "done"} {
	 wire switch
      } elseif {$option == "pick"} {
	 puts stdout $window
	 wire type
	 set Opts(motion) [bind ${window} <Motion>]
	 bind ${window} <Motion> [subst {$Opts(motion); *bypass wire show}]
	 if {$Opts(motion) == {}} {set Opts(motion) "null"}
	 cursor 21
      }
   } else {
      if {$option != "cancel"} {
         wire leg
      }
      if {$option == "done" || $option == "cancel"} {
	 select clear
	 if {$Opts(motion) == "null"} {
            bind ${window} <Motion> {}
	 } else {
            bind ${window} <Motion> "$Opts(motion)"
	 }
         set Opts(motion) {}
         cursor 19
      }
   }
}

#---------------------------------------------------------------------
# routine which tracks a selection pick
#---------------------------------------------------------------------

proc magic::keepselect {window} {
   global Opts
   if {$Opts(motion) == {}} {
      box move bl cursor
   } else {
      select keep
   }
}

proc magic::startselect {window {option {}}} {
   global Opts
   if {$Opts(motion) == {}} {
      if {$option == "pick"} {
         select pick
      } else {
	 set slist [what -list]
	 if {$slist == {{} {} {}}} {
	    select nocycle
	 }
      }
      set Opts(origin) [cursor]
      set Opts(motion) [bind ${window} <Motion>]
      bind ${window} <Motion> [subst {$Opts(motion); set p \[cursor\]; \
	set x \[expr {\[lindex \$p 0\] - [lindex $Opts(origin) 0]}\]i; \
	set y \[expr {\[lindex \$p 1\] - [lindex $Opts(origin) 1]}\]i; \
	*bypass select move \${x} \${y}}]
      if {$Opts(motion) == {}} {set Opts(motion) "null"}
      cursor 21
   } else {
      if {$Opts(motion) == "null"} {
         bind ${window} <Motion> {}
      } else {
         bind ${window} <Motion> "$Opts(motion)"
      }
      copy center 0
      set Opts(motion) {}
      cursor 22
   }
}

proc magic::cancelselect {window} {
   global Opts
   if {$Opts(motion) == {}} {
      box corner ur cursor
   } else {
      if {$Opts(motion) == "null"} {
         bind ${window} <Motion> {}
      } else {
         bind ${window} <Motion> "$Opts(motion)"
      }
      select clear
      set Opts(motion) {}
      cursor 22
   }
}

#---------------------------------------------------------------------
# tool --- A scripted replacement for the "tool"
# command, as handling of button events has been modified
# to act like the handling of key events, so the "tool"
# command just swaps macros for the buttons.
#
# Added By NP 10/27/2004
#---------------------------------------------------------------------

proc magic::tool {{type next}} {
   global Opts

   # Don't attempt to switch tools while a selection drag is active
   if {$Opts(motion) != {}} {
      return
   }

   if {$type == "next"} {
      switch $Opts(tool) {
	 box { set type wiring }
	 wiring { set type netlist }
	 netlist { set type pick }
	 pick { set type box }
      }
   }
   switch $type {
      info {
	 # print information about the current tool.
	 puts stdout "Current tool is $Opts(tool)."
	 puts stdout "Button command bindings:"
	 if {[llength [macro Button1]] > 0} {
	    macro Button1
	 }
	 if {[llength [macro Button2]] > 0} {
	    macro Button2
	 }
	 if {[llength [macro Button3]] > 0} {
	    macro Button3
	 }
	 if {[llength [macro Shift_Button1]] > 0} {
	    macro Shift_Button1
	 }
	 if {[llength [macro Shift_Button2]] > 0} {
	    macro Shift_Button2
	 }
	 if {[llength [macro Shift_Button3]] > 0} {
	    macro Shift_Button3
	 }
	 if {[llength [macro Control_Button1]] > 0} {
	    macro Control_Button1
	 }
	 if {[llength [macro Control_Button2]] > 0} {
	    macro Control_Button2
	 }
	 if {[llength [macro Control_Button3]] > 0} {
	    macro Control_Button3
	 }
      }
      box {
	 puts stdout {Switching to BOX tool.}
	 set Opts(tool) box
	 cursor 0	;# sets the cursor
	 macro  Button1          "box move bl cursor; magic::boxview %W %1"
	 macro  Shift_Button1    "box corner bl cursor; magic::boxview %W %1"
	 macro  Button2          "paint cursor"
	 macro  Shift_Button2    "erase cursor"
	 macro  Button3          "box corner ur cursor"
	 macro  Shift_Button3    "box move ur cursor; magic::boxview %W %1"
	 macro  Button4 "scroll u .05 w; magic::boxview %W %1"
	 macro  Button5 "scroll d .05 w; magic::boxview %W %1"
	 macro  Shift_XK_Pointer_Button4 "scroll r .05 w; magic::boxview %W %1"
	 macro  Shift_XK_Pointer_Button5 "scroll l .05 w; magic::boxview %W %1"

      }
      wiring {
	 puts stdout {Switching to WIRING tool.}
	 set Opts(tool) wiring
	 cursor 19 	;# sets the cursor
	 macro  Button1          "magic::trackwire %W pick"
	 macro  Button2          "magic::trackwire %W done"
	 macro  Button3          "magic::trackwire %W cancel"
         macro  Shift_Button1    "wire incr type ; wire show"
	 macro  Shift_Button2    "wire switch"
	 macro  Shift_Button3    "wire decr type ; wire show"
	 macro  Button4 "wire incr width ; wire show"
	 macro  Button5 "wire decr width ; wire show"

      }
      netlist {
	 puts stdout {Switching to NETLIST tool.}
	 set Opts(tool) netlist
	 cursor 18	;# sets the cursor
         macro  Button1          "netlist select"
	 macro  Button2          "netlist join"
	 macro  Button3          "netlist terminal"
         # Remove shift-button bindings
         macro  Shift_Button1    ""
	 macro  Shift_Button2    ""
	 macro  Shift_Button3    ""
	 macro  Button4 "scroll u .05 w"
	 macro  Button5 "scroll d .05 w"
      }
      pick {
	 puts stdout {Switching to PICK tool.}
	 set Opts(tool) pick
	 cursor 22	;# set the cursor
         macro  Button1          "magic::keepselect %W"
	 macro  Shift_Button2    "magic::startselect %W copy"
	 macro  Button2          "magic::startselect %W pick"
	 macro  Button3          "magic::cancelselect %W"
	 macro  Shift_Button1    "box corner bl cursor"
	 macro  Shift_Button3    "box move ur cursor"
	 macro  Button4 "scroll u .05 w"
	 macro  Button5 "scroll d .05 w"
      }
   }

   # Update window captions with the new tool info
   catch {magic::captions}
   return
}
