#
# $Source: /home/nlfm/Working/Zircon/Released/lib/RCS/Util.tcl,v $
# $Date: 2001/06/13 07:20:27 $
# $Revision: 1.18.1.87 $
#
package provide zircon 1.18
#
proc makeArray {args} {
    foreach x $args {
        uplevel #0 "set ${x}(1) 1; unset ${x}(1)"
    }
}
#
proc window {chan} {return [[find $chan] window]}
#
proc normal {w} { switch normal [$w cget -state] {return 1} ; return 0}
#
proc capitalise {str} {return [string toupper [string index $str 0]][string range $str 1 end]}
#
proc getOption {vn dflt} {
    upvar #0 $vn var
    switch {} [set var [option get . $vn [capitalise $vn]]] {set var $dflt}
}
#
# List utilities
#
proc listmember {list val} {return [expr {[lsearch $list $val] >= 0}]}
#
proc listkill {list val} {
    upvar $list lst
    if {[set x [lsearch $lst $val]] >= 0} { set lst [lreplace $lst $x $x]}
    return $lst
}
#
proc listremove {lst val} {
    if {[set x [lsearch $lst $val]] >= 0} { set lst [lreplace $lst $x $x]}
    return $lst
}
#
proc listincl {list val} {
    upvar $list lst
    if {![listmember $lst $val]} { lappend lst $val }
    return $lst
}
#
proc listmatch {list val} {
    set i 0
    foreach item $list {
	switch -- [lindex $item 0] $val {return $i}
	incr i
    }
    return -1
}
#
proc listdel {v item} {
    upvar $v lst
    catch {set lst [lreplace $lst $item $item]}
    return $lst
}
#
proc listput {list posn val} {
    upvar $list lst
    return [set lst [linsert $lst $posn $val]]
}
#
proc listupdate {list item val} {
    upvar $list lst
    while {[llength $lst] <= $item} {lappend lst {} }
    return [set lst [lreplace $lst $item $item $val]]
}
#
proc listmove {list from to val} {
    upvar $list lst
    return [set lst [linsert [lreplace $lst $from $from] $to $val]]
}
#
# Procedure used to shorten menu labels to 10 characters. Used
# when adding user provided items to menus
#
proc prune {name lng} {
    regsub -all "\[\002\017\026\037\]" $name {} name
    return [expr {[string length $name] > $lng ? \
      "[string range $name 0 [expr {$lng - 3}]]..." : $name}]
}
#
proc killWindow {win} {
    safeUnset Icon($win) IconBM($win)
    catch {destroy $win}
}
#
#	proc me : Returns true if nk is this user
#		  Assumes that nk is in lower case!!!
#
proc me {nk net} {
    switch -- [string tolower $nk] [[$net myid] lname] {return 1}
    return 0
}
#
proc addCTCPMenu {net name usr} {
    global ctcpCmds DEBUG specialCmds
    $name add cascade -label [trans ctcp] -menu $name.ctcp
    menu $name.ctcp 
    set cmdlst $ctcpCmds
    switch {} [$net players] {} default { lappend cmdlst Sound }
    if {$DEBUG} { lappend cmdlst Zircon }
    foreach cmd $cmdlst {
	switch {{}} $usr {
	    set prc "usersCTCP $net [string toupper $cmd]"
	    $name.ctcp configure -tearoff 0
	} default {
	    set prc "doCtcp $net [string toupper $cmd] \[$usr name\]"
	    catch {$name.ctcp configure -tearoffcommand "retitle \"CTCP menu for \[$usr name\]\""}
	}
	$name.ctcp add command -label [trans $cmd] -command $prc
    }
}
#
proc addChanCTCPMenu {name ctl} {
    global ctcpCmds DEBUG specialCmds
    $name add cascade -label [trans ctcp] -menu $name.ctcp
    menu $name.ctcp -tearoff 0
    foreach cmd [expr {$DEBUG ? [concat $ctcpCmds $specialCmds] : $ctcpCmds}] {
	$name.ctcp add command -label [trans $cmd] \
	  -command "chanCTCP [string toupper $cmd] $ctl"
    }
}
#
proc addDCCMenu {name usr} {
    $name add cascade -label [trans dcc] -menu $name.dcc
    menu $name.dcc -tearoff 0
    foreach cmd {Send Chat} { $name.dcc add command -label [trans $cmd] \
      -command "doDCC [$usr net] [string toupper $cmd] \[$usr name\]" }
}
#
proc newNickAction {net win usr chid} {
    mkEntryBox .@${usr}action [trans action] "Enter your action for [$usr name]:" \
      [list [list action {} {} palette]] \
      [list send "NickAction 1 0 $net $win $usr $chid"] [list {send & keep} "NickAction 1 1 $net $win $usr $chid"] \
      [list keep "NickAction 0 1 $net $win $usr $chid"] [list cancel {}]
}
#
proc NickAction {send keep net win usr chid action} {
    if {$send} { $chid doNickAction $usr $action }
    if {$keep} {
        $chid configure +nickactions [list [$usr name] $action]
	$win.action add command -label [prune $action 15] \
	  -command "$chid doNickAction $usr [list $action]"
    }
}
#
proc addActionMenu {net win usr chid} {
    if {![$chid isa Channel]} return
    $win add cascade -label [trans action] -menu $win.action
    menu $win.action -tearoff 0
    $win.action add command -label [trans New] \
      -command "newNickAction $net $win $usr $chid"
    $win.action add separator
    foreach x [$net nickactions] {
        foreach {chan nick label action} $x break
	switch {} $action {
	    set action $label
	    set label [prune $action 12]
	}
	if {[regexp -- $chan [$chid name]] && [regexp -- $nick [$usr name]]} {
	    $win.action add command -label $label \
	      -command "$chid doNickAction $usr [list $action]"
	}
    }
    foreach x [$chid nickactions] {
        foreach {nick label action} $x break
	switch {} $action {
	    set action $label
	    set label [prune $action 12]
	}
	if {[regexp -- $nick [$usr name]]} {
	    $win.action add command -label $label \
	      -command "$chid doNickAction $usr [list $action]"
	}
    }
    switch nil [set frd [$usr fobj]] {} default {
        foreach x [$frd actions] {
            foreach {chan label action} $x break
	    switch {} $action {
	        set action $label
	        set label [prune $action 12]
	    }
	    if {[regexp -- $chan [$chid name]]} {
	        $win.action add command -label $label \
	          -command "$chid doNickAction $usr [list $action]"
	    }
	}
    }
}
#
proc makeUserMenu {chid win usr} {
    if {[winfo exists $win]} { return $win }
    return [menu $win -postcommand "postUM $chid $win $usr"]
}
#
#
proc postUM {chid win usr} {
    global ucmds Ops
    set w [winfo parent $win]
    set net [$usr net]
    set nrm [string compare nil $chid]
    array set ucmds "
	Whois	{$net WHOIS \[$usr lname\]}
	Message	{Message :: make $net \[$usr name\]}
	Notice	{channelNotice $net \[$usr lname\]}
	Action	{}
	Time	{$net TIME \[$usr name\]}
	CTCP	{}
	DCC	{}
	Notify	{}
	Ignore	{}
	Finger	{}
	Speak	{}
	ChanOp	{}
	Kick	{$chid kick $usr}
	Ban	{doBan $net + $chid \[$usr name\]!*@*}
	BanKick {$chid banKick $usr}
	Kill	{}
    "
    foreach cmd $Ops(userMenu) {
	switch $cmd {
	Action { addActionMenu $net $win $usr $chid}
	CTCP { addCTCPMenu $net $win $usr }
	DCC { addDCCMenu $win $usr }
	Notify {
		$win add checkbutton -label [trans notify] \
		  -variable ${usr}(notify) -command "$usr doNotify"
	    }
	Whois -
	Message -
	Notice -
	Time { $win add command -label [trans $cmd] -command $ucmds($cmd) }
	Ignore { if {$nrm} {addIgnoreMenu $win $usr} }
	Finger {
		if {$nrm} {
		    $win add command -label [trans finger] -command "$usr finger"
		}
	   }
	Silence {
		if {[$net undernet]} {
		    $win add cascade -label [trans silence] \
		      -menu $win.silence
		    menu $win.silence -tearoff 0
		    $win.silence add command -label [trans (silence] \
		      -command "underUSilence + $net $usr"
		    $win.silence add command -label [trans delete] \
		      -command "underUSilence - $net $usr"
		}
	    }
	}
    }
    addPluginMenu $win $net $chid $usr
    if {$nrm} {
	set st [expr {(![$net restricted] && [$chid operator]) ? "normal" : "disabled"}]
	foreach cmd $Ops(chanop) {
	    switch $cmd {
	    Speak {
		$win add checkbutton -label [trans speak] \
		  -variable ${chid}(lclSpk,$usr) \
		  -command "$chid userMode $usr v" -state $st
	      }
	    ChanOp {
		$win add checkbutton -label [trans chanop] \
		  -variable ${chid}(lclOp,$usr) \
		  -command "$chid userMode $usr o" -state $st
	      }
	    Kick -
	    Ban -
	    BanKick {
		$win add command -label [trans $cmd] \
		  -command $ucmds($cmd) -state $st
	      }
	    }
        }
	foreach cmd $Ops(ircop) {
	    switch $cmd {
	    Kill {
		$win add command -label [trans kill] -command "$usr kill" \
		  -state [expr {[$net ircop] ? {normal} : {disabled}}] \
		  -foreground red
	      }
	    }
	}
    }
    $win configure -postcommand {}
}
#
proc invert {b} {
    $b conf -fg [$b cget -bg] -bg [$b cget -fg] -activef [$b cget -activeb] \
      -activeb [$b cget -activef]
}
#
proc makeLB {win args} {
    frame $win -borderwidth 0 -relief flat
    grid columnconfigure $win 0 -weight 1
    grid rowconfigure $win 0 -weight 1
    scrollbar $win.vs -command "$win.l yview" -relief sunken -highlightthickness 0
    scrollbar $win.hs -command "$win.l xview" -orient horizontal \
      -relief sunken -highlightthickness 0
    eval listbox $win.l -xscrollcommand "{ghsSet $win.hs}" \
      -yscrollcommand "{gvsSet $win.vs}" -selectmode single -setgrid 1 $args
    grid $win.l -row 0 -column 0 -sticky nsew
    bind $win <Enter> {focus %W.l}
    return $win
}
#
proc ghsSet {sb f l} {
    switch 0 $f {switch 1 $l {catch {grid forget $sb ; return}}}
    catch {grid $sb -row 1 -column 0 -sticky ew}
    $sb set $f $l
}
#
proc gvsSet {sb f l} {
    switch 0 $f {switch 1 $l {catch {grid forget $sb ; return}}}
    catch {grid $sb -row 0 -column 1 -sticky ns}
    $sb set $f $l
}
#
proc utest {net win x y cmd} {
    set name [lindex [$win tag names @$x,$y] 0]
    switch -glob -- $name {
    user* { eval $cmd $net \{[$name lname]\}}
    @?@user* { eval $cmd $net \{[[string range $name 3 end] lname]\}}
    }
    notIdle $win $net
}
#
proc who2 {net name} {$net WHOIS $name $name}
#
proc rebind {txt net} {
    $txt configure -state disabled -takefocus 0
    bind $txt <Any-KeyPress> "notIdle %W $net; break"
    bind $txt <Double-Button-2> "utest $net %W %x %y {Message :: make}"
    bind $txt <Shift-Double-Button-2> "utest $net %W %x %y who2"
    bind $txt <Control-Double-Button-2> "utest $net %W %x %y finger"
    bind $txt <Double-3> "findURL %W %x %y $net"
}
#
# retitle is used by tearoff code...
#
proc retitle {t w1 w2} { wm title $w2 $t }
#
proc makeMB {win text} {
    menubutton $win -text [trans $text] -menu $win.menu -padx 4 -pady 5
    return [menu $win.menu]
}
#
proc addSeparator {w args} {
    global zircon
    eval grid [frame $w.[newName s] -background $zircon(sepColor) \
      -borderwidth 4]  -sticky ew -pady 4 $args
}
#
proc getDate {} { return [clock format [clock seconds]] }
#
proc ipPack {ip} {
    set val 0
    foreach x [split $ip .] { set val [expr {($val << 8) | ($x & 0xff)}] }
    return [format %u $val]
}
#
proc IPaccept {p1 p2 args} {
    global hostIPaddress
    set hostIPaddress $p2
    catch {close $p1}
}
#
proc ipAddress {} {
    global hostIPaddress
    if {![info exists hostIPaddress]} {
	set hostIPaddress {}
	set sock [socket -server IPaccept 0]
	set sock2 [socket [info hostname] \
	  [lindex 1 [fconfigure $sock -sockname]]]
	vwait hostIPaddress
	catch {close $sock}
	catch {close $sock2}
	switch -- $hostIPaddress {0.0.0.0} - {127.0.0.1} {
	    tkwait [mkDialog {} .@ip {IP Number} \
	      {Please enter the IP number for your host} \
	      [list [list {IP Number} {}]] [list ok "set hostIPaddress"]]
	}
    }
    return $hostIPaddress
}
#
proc makeServer {cmd} {
    if {[catch {socket -server $cmd 0} sock]} { error $sock }
    if {[catch {fconfigure $sock -sockname} xx]} {
	error "Cannot get port for server - $xx"
    }
    return [list $sock $xx]
}
#
proc zping {res} {
    if {![regexp {^[0-9]+$} [string trim $res]]} { return $res }
    if {[catch  {set v [expr {[clock seconds] - $res}]}]} {
	return $res
    }
    return $v
}
#
proc safeClean {txt} {return [split [string trim $txt]]}
#
proc notIdle {win args} {
    switch {} $args {
	global currentNet
	catch {$currentNet configure -idle 0}
    } default {
	catch {$args configure -idle 0}
    }
    catch {[Window :: id $win] extendTime}
}
#
proc popup {win} {
    wm deiconify [set win [winfo toplevel $win]]
    raise $win
    catch {[Window :: id $win] extendTime}
}
#
proc checkIndicator {net win flag} {
    if {[$net ircIIops]} {
        set txt [$win cget -text]
	regexp {^(@?)(\+?)(.*)} $txt m op spk txt
        switch $flag operator { set txt @$txt } speaker { set txt +$txt }
	$win configure -text $txt
    }
}
#
proc markButton {net name which} {
    if {![winfo exists $name]} return
    foreach {opt uopt lopt} {font Font font \
	foreground Foreground foreground \
	background Background background \
	activeForeground ActiveForeground activeforeground \
	activeBackground ActiveBackground activebackground} {
	set fopt $which[switch {} $which {set opt} default {set uopt}]
	switch {} [set cl [option get $name $fopt $uopt]] {
	    switch {} $which {
		switch {} [set cl [lindex [$name conf -$lopt] 3]] {} default {
		    if {[catch {$name conf -$lopt $cl} msg]} {
			tellError {} {Option Error} "$opt configuration error - \"$msg\""
		    }
		}
	    }
	} default {
	    if {[catch {$name conf -$lopt $cl} msg]} {
		tellError {} {Option Error} "$opt configuration error - \"$msg\""
	    }
	} \
    }
    checkColour $name
    checkIndicator $net $name $which
}
#
proc lchange {x} {
    set inc 20
    if {(255 - $x) < 20} { set inc -20 }
    return [expr {(($x + $inc) & 255)}]
}
#
proc lighten {w cl} {
    set res #
    foreach x [winfo rgb $w $cl] { append res [format %04x [lchange $x]] }
    return $res
}
#
proc markEntry {name index which} {
    if {![winfo exists $name] || $index == -1} return
    foreach {opt uopt lopt} {font Font font \
      background Background background \
      activeBackground ActiveBackground activebackground} {
	set fopt $which[switch {} $which {set opt} default {set uopt}]
	switch {} [set cl [option get $name $fopt $uopt]] {
	    switch {} $which {
		switch {} [set cl [lindex [$name conf -$lopt] 3]] {} default {
		    $name entryconfigure $index -$lopt $cl
		}
	    }
	} default {
	    $name entryconfigure $index -$lopt $cl
	} \
    }
    checkColour $name
}
#
proc checkColour {name} {
    switch -- [$name cget -activef] [$name cget -fg] {
	switch -- [$name cget -activeba] [set bg [$name cget -bg]] {
	    $name configure -activeba [lighten $name $bg]
	}
    }
}
proc bgIns {col chan prt} {
    switch $prt {
    Topic {
	    set w [$chan window].topic.entry
	    set txt [$w get 1.0 end]
	    set indx [string range [$w index insert] 2 end]
	}
    Entry {
	    set w [$chan window].cmd.entry
	    set txt [$w get]
	    set indx [$w index insert]
	}
    default {
	    set w $prt
	    set txt [$w get]
	    set indx [$w index insert]
	}
    }
    if {[regexp "\003\[0-9\]\[0-9\]?$" [string range $txt 0 [incr indx -1]]]} {
	set str ,$col
    } {
	set str "\0031,$col"
    }
    $w insert insert $str
}
#
proc palIns {char chan prt} {
    switch $prt Topic { set w [$chan window].topic.entry } \
      Entry { set w [$chan window].cmd.entry } default {set w $prt}
    $w insert insert $char
}
#
proc popPalette {m chn prt col x y} {
    global mIRCCol vt100Colour
    if {![winfo exists $m]} {
	menu $m -tearoff 0
	$m add command -label [trans bold] -command "palIns \002 {$chn} {$prt}"
	$m add command -label [trans inverse] -command "palIns \026 {$chn} {$prt}"
	$m add command -label [trans underline] -command "palIns \037 {$chn} {$prt}"
	$m add command -label [trans normal] -command "palIns \017 {$chn} {$prt}"
        foreach ct $col {
	    switch $ct mirc {
	        $m add separator
		$m add command -label {mIRC Colour}
	        $m add cascade -label [trans foreground] -menu $m.fg
	        $m add cascade -label [trans background] -menu $m.bg
	        menu $m.fg -tearoff 0
		set cls [lsort [array names mIRCCol]]
	        foreach cnt $cls {
		    $m.fg add command -background $mIRCCol($cnt) \
		       -command "palIns [list \003$cnt] $chn [list $prt]"
	        }
	        menu $m.bg -tearoff 0
	        foreach z $cls {
		    $m.bg add command -background $mIRCCol($z) \
		       -command "bgIns $z $chn [list $prt]"
	        }
	    } ansi {
	        $m add separator
		$m add command -label {ANSI Colour}
	        $m add cascade -label [trans foreground] -menu $m.vfg
	        $m add cascade -label [trans background] -menu $m.vbg
	        menu $m.vfg -tearoff 0
	        foreach cnt {0 1 2 3 4 5 6 7} {
		    $m.vfg add command -background $vt100Colour($cnt) \
		       -command "palIns [list \033\[3${cnt}m] $chn [list $prt]"
	        }
	        menu $m.vbg -tearoff 0
	        foreach cnt {0 1 2 3 4 5 6 7} {
		    $m.vbg add command -background $vt100Colour($cnt) \
		       -command "palIns [list \033\[4${cnt}m] $chn [list $prt]"
	        }
	    } ctcp2 {
	        $m add separator
		$m add command -label {CTCP2 Colour}
	    }
	}
    }
    tk_popup $m $x $y
}
#
proc makePalette {w chn prt name ft col} {
    if {[winfo exists $w]} {popup $w ; return} 
    global Ft
    killWindow $w
    makeToplevel $w "$name Palette" {} {}
    wm resizable $w 0 0
    set row 0
    switch {} $ft {set ft [$prt cget -font]}
    foreach i {8 9 10 11 12 13 14 15} {
	foreach j {0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15} {
	    set qc [set c [format "%c" [expr {$i * 16 + $j}]]]
	    set qc [list $c]
	    grid [button $w.${i}l$j -text $c -relief raised -width 1 \
	      -command "palIns $qc {$chn} {$prt}" -font $ft] -row $row \
	      -column $j
	}
	incr row
    }
    grid [frame $w.s0] -columnspan 16 -pady 4
    incr row
    foreach {x y} {Bold \002 Inverse \026 Underline \037 Normal \017} {
	button $w.f$x -text $x -width 10 -command "palIns $y {$chn} {$prt}"
    }
    grid $w.fBold $w.fInverse $w.fUnderline $w.fNormal -columnspan 4
    foreach c $col {
        switch $c mirc {
	    set row [colourBtns $w $row $chn $prt mIRCCol mIRC \
	      palIns \003 {} bgIns {} {}]
	} ansi {
	    set row [colourBtns $w $row $chn $prt vt100Colour ANSI \
	      palIns "\033\[3" m palIns "\033\[4" m]
	    foreach {x y} {Bold 1 Inverse 7 Underline 4 Normal 0} {
		button $w.v$x -text "ANSI\n$x" -width 10 \
		-command "palIns [list \033\[${y}m] $chn {$prt}"
	    }
	    grid $w.vBold $w.vInverse $w.vUnderline $w.vNormal -columnspan 4
	} ctcp2 {
	}
    }
    grid [frame $w.s3] -columnspan 16 -pady 4
    grid [button $w.ok -text [trans dismiss] -command "destroy $w" -width 6] -columnspan 16
}
#
proc colourBtns {w row chn prt var txt fgc fg1 fg2 bgc bg1 bg2} {
    upvar #0 $var colrs
    grid [frame $w.${var}s1] -columnspan 16 -pady 4
    grid [label $w.${var}fl -text "$txt [trans {foreground colour}]"] -columnspan 16
    incr row 4
    set cls [lsort [array names colrs]]
    set cl 0
    set cinc [expr {16 / [llength $cls]}] 
    foreach x $cls {
        grid [button $w.$var$x -bg $colrs($x) \
          -command "$fgc [list $fg1$x$fg2] $chn {$prt}"] \
	  -row $row -column $cl -columnspan $cinc
	incr cl $cinc
    }
    grid [frame $w.${var}s2 ] -columnspan 16 -pady 4
    grid [label $w.${var}bl -text "$txt [trans {background colour}]"] -columnspan 16
    incr row 3
    set cl 0
    foreach x $cls {
        grid [button $w.${var}b$x -bg $colrs($x) \
          -command "$bgc [list $bg1$x$bg2] $chn {$prt}"] \
	  -row $row -column $cl -columnspan $cinc
	incr cl $cinc
    }
    return $row
}
#
proc fixList {lst} {
    set nl {}
    regsub -all {\\} $lst {\\\\} l1
    foreach x $l1 {lappend nl $x}
    regsub -all {\\\\} $nl {\\} nl
    return $nl
}
#
proc uExpand {w net chid} {
    global pick zircon
    set txt [$w get]
    set end [$w index insert]
    set start [string wordstart $txt $end]
    switch {} [set hd [string tolower [string range $txt $start $end]]] {
        return {}
    }
    set hits {}
    foreach u [$chid users] {
	if {[string match $hd* [$u lname]]} {lappend hits $u}
    }
    switch {} $hits { return {} }
    if {[llength $hits] > 1} {
	set m [menu .@[newName Exp] -tearoff 0]
	set f 0
	foreach u $hits {
	    set cmd "set pick($w) $u ; destroy $m"
	    incr f
	    if {[catch {bind $m <F$f> $cmd}]} {
	        $m add command -label [$u name] -command $cmd
	    } {
	      $m add command -label [$u name] -accelerator F$f \
	      -command $cmd
	    }
	}
	set xy [$w bbox insert]
	set x [expr {[lindex $xy 0] + [winfo rootx $w]}]
	set y [expr {[lindex $xy 1] + [winfo rooty $w]}]
	tk_popup $m $x $y [$m index end]
	focus $m
	tkwait window $m
	focus $w
	set res $pick($w)
	unset pick($w)
    } {
	set res $hits
    }
    return [string range [$res name] [string length $hd] end]
}
#
proc evenGrid {w what start end} {
    for {set i $start} {$i <= $end} {incr i} {
	grid ${what}configure $w $i -weight 1
    }
}
#
proc fullName {file} {
    switch [file pathtype $file] {
    relative { return [file join [pwd] $file] }
    }
    return $file
}
#
proc safeDestroy {args} { foreach x $args { catch {destroy $x} } }
#
proc makeToplevel {w title del save} {
    toplevel $w -class Zircon
    wm title $w $title
    switch {} $del {} default {
        wm protocol $w WM_DELETE_WINDOW $del
    }
    switch {} $save {set save "wm command $w { }"}
    wm protocol $w WM_SAVE_YOURSELF $save
    wm resizable $w 1 1
    return $w
}
#
proc colonLast {arg} {
    switch {} [set rest [string trim $arg]] { return {} }
    if {[set ix [string last { } $rest]] < 0} {
	return :$rest
    }
    return [string range $rest 0 $ix]:[string range $rest [expr {$ix+1}] end]
}
