#
# Copyright 2005-2008 University of Zagreb, Croatia.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# This work was supported in part by Croatian Ministry of Science
# and Technology through the research contract #IP-2003-143.
#

# default built-in model to use and its default parameters
set DEFAULT_WLAN_MODEL basic_range
set DEFAULT_SCRIPT_MODEL ns2script
set DEFAULT_WLAN_MODEL_TYPES "3 3 9 9 9"
set DEFAULT_RANGE 275
set DEFAULT_WLAN_BW 54000000
set DEFAULT_WLAN_DELAY 20000
set DEFAULT_WLAN_MODEL_VALS [list "range=$DEFAULT_RANGE" \
	"bandwidth=$DEFAULT_WLAN_BW" "jitter=0" "delay=$DEFAULT_WLAN_DELAY" \
	"error=0" ]
# default canvas reference point: X Y lat long alt
set DEFAULT_REFPT "0 0 47.5791667 -122.132322 2.0"

#
# look for all wlan nodes connected to peer if specified,
# otherwise in the global list of nodes
proc findWlanNodes { peer } {
    global node_list
    set wlans { }

    # search the global node list for the first wlan node
    if { $peer == "" } {
	foreach node $node_list {
	    if { [nodeType $node] == "wlan" } {
		lappend wlans $node
	    }
	}
    # search peer for wlan node
    } else {
	foreach ifc [ifcList $peer] {
	    set node [peerByIfc $peer $ifc]
	    if { [nodeType $node] == "wlan" } {
		lappend wlans $node
	    }
	}
    }
    return $wlans
}

#
# Returns 1 if the given interface is wireless
proc isIfcWireless { node ifc } {
    if { $ifc == "wireless" } {
	# wireless peudo-interface
	return false
    }
    set peer [logicalPeerByIfc $node $ifc]
    if { $peer != "" && [nodeType $peer] == "wlan" } {
	return true
    }
    return false
}

#
# remove the (green) WLAN GUI links
#
proc clearWlanLinks { wlan } {
	global .c
	set search "wlanlink"
	if { $wlan != "" } { set search "wlanlink && $wlan" }
	foreach wlanlink [.c find withtag $search] {
		set tags [.c gettags $wlanlink]
		set lnode1 [lindex $tags 1]
		set lnode2 [lindex $tags 2]
		.c delete $wlanlink
		.c delete -withtags "linklabel && $lnode1 && $lnode2"
		# we could also remove wlan node hash table entry for
		# each wlanlink, but we are assuming wlan node will be
		# destroyed anyway
	}
}

# draws circles in GUI representing wlan range
proc updateRangeCircles { wlan range } {
    global .c zoom g_selected_model
    set c .c

    set radius [expr {$zoom * $range/2}]
    $c delete -withtag rangecircles
    if { $radius == 0 } {
    	return
    }
    if { $g_selected_model != "none" } {
    	return
    }
    foreach ifc [ifcList $wlan] {
	set node [peerByIfc $wlan $ifc]
	set coords [getNodeCoords $node]
	set x [expr {[lindex $coords 0] * $zoom}]
	set y [expr {[lindex $coords 1] * $zoom}]
	set x1 [expr $x - $radius]
	set y1 [expr $y - $radius]
	set x2 [expr $x + $radius]
	set y2 [expr $y + $radius]

	set newcircle [$c create oval $x1 $y1 $x2 $y2 \
		-width 2 -outline #00A000 -tags "circle rangecircles"]
    }
}

proc linkSelectedNodes { wlan nodes } {
    set canvas [getNodeCanvas $wlan]

    foreach node $nodes {
	if { $wlan == $node } { continue } ;# don't link to self
	if { [ifcByPeer $wlan $node] != "" } { continue } ;# already linked
	if { [getNodeCanvas $node] != $canvas } { continue }; # on a different canvas
        newGUILink $wlan $node
    }
}

proc linkAllNodes { wlan } {
    global node_list

    set canvas [getNodeCanvas $wlan]

    # vars related to the status bar graph
    set num 0
    set num_nodes [llength $node_list]
    statgraph on $num_nodes
    set update_interval [expr {$num_nodes / 15}]
    .c config -cursor watch; update

    foreach node $node_list {
        statgraph inc 1
	incr num
	# GUI update slows this down considerably, so update every so often
	if { $update_interval > 0 && \
	     [expr { ($num % $update_interval) }] == 0 } { update }
        if { [nodeType $node] != "router" } { continue }
	if { [ifcByPeer $wlan $node] != "" } { continue } ;# already linked
	if { [getNodeCanvas $node] != $canvas } { continue }; # on a different canvas
        newGUILink $wlan $node
    }
    .c config -cursor left_ptr; update
    statgraph off 0
}

proc getWlanColor { wlan } {
	global node_list wlanLinkColors
	set colornum 0
	foreach node $node_list {
		if {[nodeType $node] != "wlan"} {
			continue
		}
		if {$node == $wlan} {
			return [lindex $wlanLinkColors $colornum]
		}
		incr colornum
		if { $colornum >= [llength $wlanLinkColors] } { set colornum 0 }
	}
	# default color
	return [lindex $wlanLinkColors 0]
}

# move a node given incremental coordinates
# dx dy should be adjusted for zoom
proc moveNodeIncr { c node dx dy } {
    global node_list
    #puts "moveNodeIncr $node $dx $dy"
    # check that node exists
    if {[lsearch $node_list $node] == -1 } {
	return
    }
    # move the node and its links
    set img [$c find withtag "node && $node"]
    set coords [$c coords $img]
    set x [lindex $coords 0]
    set y [lindex $coords 1]
    # move doesn't take incremental coordinates
    set xpos [expr ($x + $dx)]
    set ypos [expr ($y + $dy)]
    moveNode $c $node $img $xpos $ypos $dx $dy
}

# move a node given absolute coordinates
# xpos,ypos should be adjusted for zoom
proc moveNodeAbs { c node xpos ypos } {
    global node_list
    # check that node exists
    if {[lsearch $node_list $node] == -1 } {
	return
    }
    # move the node and its links
    if {$xpos != 0 && $ypos != 0} {
        set img [$c find withtag "node && $node"]
        set coords [$c coords $img]
        set x [lindex $coords 0]
        set y [lindex $coords 1]
	# move doesn't take absolute coordinates
        set dx [expr ($xpos - $x)]
        set dy [expr ($ypos - $y)]
	moveNode $c $node $img $xpos $ypos $dx $dy
    }
}

# move a node on the canvas along with its labels and links
# helper function used by moveNodeIncr and moveNodeAbs
proc moveNode { c node img xpos ypos dx dy } {
    global zoom
    $c move $img $dx $dy
    set xposz [expr {$xpos / $zoom}]; set yposz [expr {$ypos / $zoom}]
    setNodeCoords $node "$xposz $yposz"
    $c move "nodelabel && $node" $dx $dy
    $c move "highlight && $node" $dx $dy
    $c move "rangecircles && $node" $dx $dy
    lassign [getDefaultLabelOffsets [nodeType $node]] ldx ldy
    setNodeLabelCoords $node "[expr {$xposz + $ldx}] [expr {$yposz + $ldy}]"
    $c addtag need_redraw withtag "link && $node"
    $c addtag need_redraw withtag "wlanlink && $node"
    foreach link [$c find withtag "link && need_redraw"] {
        redrawLink [lindex [$c gettags $link] 1]
    }
    foreach wlanlink [$c find withtag \
      "wlanlink && need_redraw"] {
        redrawWlanLink $wlanlink
    }
    $c delete -withtags selectmark
    $c dtag link need_redraw
    $c dtag wlanlink need_redraw

    # callback for updating any widgets
    widgets_move_node $c $node 1
}

# called from cfgparse when loading imn file
proc upgradeWlanConfigs {} {
    global node_list
    set model_list [getPluginsCapList]
    foreach node $node_list {
	if { [nodeType $node] != "wlan" } { continue }
	set modcfg [netconfFetchSection $node "mobmodel"]
	if { [lindex $modcfg 0] == "range" } {
	    upgradeWlanRangeConfig $node
	    set modcfg [netconfFetchSection $node "mobmodel"]
	}
	foreach model [lrange $modcfg 1 end] {
	    if { [lsearch $model_list "*=$model"] == -1 } {
		puts "***Warning: missing model '$model'!"
	    }
	}
    }
}

# backwards compatibility with old config files
# convert from "range" model to "basic_range" coreapi model
proc upgradeWlanRangeConfig { wlan } {
    global DEFAULT_RANGE DEFAULT_WLAN_MODEL
    global DEFAULT_WLAN_MODEL_TYPES DEFAULT_WLAN_BW DEFAULT_WLAN_DELAY

    netconfInsertSection $wlan [list mobmodel coreapi $DEFAULT_WLAN_MODEL]
    set range [getNodeRange $wlan]
    if { $range == "" } { set range $DEFAULT_RANGE }
    set bw [getLinkBandwidth $wlan]
    if { $bw == "" } { set bw $DEFAULT_WLAN_BW }
    set jitter 0
    set delay [getLinkDelay $wlan]
    if { $delay == "" } { set delay $DEFAULT_WLAN_DELAY }
    set per [getLinkBER $wlan]
    if { $per == "" } { set per 0 }
    set types $DEFAULT_WLAN_MODEL_TYPES
    set vals [list "range=$range" "bandwidth=$bw" "jitter=$jitter" \
		"delay=$delay" "error=$per"]
    setCustomConfig $wlan $DEFAULT_WLAN_MODEL $types $vals 0
    setNodeRange $wlan ""
    setLinkBandwidth $wlan ""
    setLinkDelay $wlan ""
    setLinkBER $wlan ""
}

# helper to populate popup config for wlan nodes
proc wlanConfigDialogHelper { wi target apply } {
    global range DEFAULT_RANGE DEFAULT_WLAN_MODEL changed
    global DEFAULT_WLAN_MODEL_VALS DEFAULT_WLAN_MODEL_TYPES
    global DEFAULT_SCRIPT_MODEL
    global systype
    global plugin_img_edit
    global g_selected_model
    global oper_mode

    set wlan $target
    set emulation_type [lindex [getEmulPlugin $target] 1]

    set modcfg [netconfFetchSection $target "mobmodel"]
    set mobmodel [lindex [split $modcfg] 1]

    # apply values from the config dialog
    if { $apply } {

	# basic range selected
	if { $g_selected_model == "none" } {
	    set mobmodel $DEFAULT_WLAN_MODEL
	    # bw/delay/ber
	    set rb $wi.wl.note.basic.rb
	    set de $wi.wl.note.basic.de
	    set jt $wi.wl.note.basic.jt
	    set bw [$rb.value get]
	    set jitter [$jt.value1 get]
	    set delay [$de.value2 get]
	    set per [$de.value3 get]
	    set types $DEFAULT_WLAN_MODEL_TYPES
	    set vals [list "range=$range" "bandwidth=$bw" "jitter=$jitter" \
			"delay=$delay" "error=$per"]
	    setCustomConfig $wlan $DEFAULT_WLAN_MODEL $types $vals 0
	# EMANE model selected
	} else {
	    set mobmodel $g_selected_model
	}

	# ns-2 mobility script file
	set scriptcfg [getCustomConfigByID $wlan $DEFAULT_SCRIPT_MODEL]
	if { $scriptcfg != "" } {
	    netconfInsertSection $target \
		[list mobmodel coreapi $mobmodel $DEFAULT_SCRIPT_MODEL]
	} else {
	    netconfInsertSection $target [list mobmodel coreapi $mobmodel]
	}

	# ipv4/ipv6 address
	set ipv4changed 0
	set ipv6changed 0
	set ipaddr [$wi.bottom.ipv4.addrv get]
	set oldipaddr [getIfcIPv4addr $target wireless]
	if { $ipaddr != $oldipaddr } {
	    setIfcIPv4addr $target wireless $ipaddr
	    set changed 1
	    set ipv4changed 1
	}
	set ipaddr [$wi.bottom.ipv6.addrv get]
	set oldipaddr [getIfcIPv6addr $target wireless]
	if { $ipaddr != $oldipaddr } {
	    setIfcIPv6addr $target wireless $ipaddr
	    set changed 1
	    set ipv6changed 1
	}
	foreach ifc [ifcList $target] {
	    set lnode [lindex [linkByIfc $target $ifc] 0]
	    # erase IPv4/IPv6 addresses as needed
	    set peer [peerByIfc $target $ifc]
	    set peerifc [ifcByPeer $peer $target]
	    if { $ipv4changed } { setIfcIPv4addr $peer $peerifc "" }
	    if { $ipv6changed } { setIfcIPv6addr $peer $peerifc "" }
	}
	# addresses have been zeroed above to force using the WLAN subnet
	foreach ifc [ifcList $target] {
	    set peer [peerByIfc $target $ifc]
	    set peerifc [ifcByPeer $peer $target]
	    if { $ipv4changed } { autoIPv4addr $peer $peerifc }
	    if { $ipv6changed } { autoIPv6addr $peer $peerifc }
	}
	# remove any range circles
	updateRangeCircles $target 0

	if { $oper_mode == "exec" } {
	    # this generates Config Messages for updating the model parameters
	    pluginCapsInitialize $target "mobmodel"
	}
	return
    }

    # use default model/values when none configured for this node
    if { $mobmodel == "" } {
	set mobmodel $DEFAULT_WLAN_MODEL
	set vals $DEFAULT_WLAN_MODEL_VALS
    # look for customized range/bw/jitter/delay/per
    } else {
	set vals [getCustomConfigByID $target $DEFAULT_WLAN_MODEL]
	if { $vals == "" } { set vals $DEFAULT_WLAN_MODEL_VALS }
    }

    # set radio button variable
    if { $mobmodel == $DEFAULT_WLAN_MODEL } {
	set g_selected_model "none"
    } else {
	set g_selected_model $mobmodel
    }

    set range  [getServiceValuesItem $vals "range" 0]
    set bw     [getServiceValuesItem $vals "bandwidth" 1]
    set jitter [getServiceValuesItem $vals "jitter" 2]
    set delay  [getServiceValuesItem $vals "delay" 3]
    set per    [getServiceValuesItem $vals "error" 4]

    ttk::labelframe $wi.wl -text "Wireless"
    pack $wi.wl -fill both -expand true -padx 4 -pady 4

    ttk::notebook $wi.wl.note
    pack $wi.wl.note -fill both -expand true -padx 4 -pady 4
    ttk::notebook::enableTraversal $wi.wl.note

    ##
    ## basic range
    ##
    ttk::frame $wi.wl.note.basic
    $wi.wl.note add $wi.wl.note.basic -text "Basic" -underline 0
    set txt "The basic range model calculates on/off connectivity based on"
    set txt "$txt pixel\n distance between nodes."
    ttk::label $wi.wl.note.basic.tlab -text $txt
    pack $wi.wl.note.basic.tlab -side top -anchor w -padx 4 -pady 4

    # range and bandwidth (rb) frame
    set rb $wi.wl.note.basic.rb
    ttk::frame $rb
    ttk::label $rb.rlab -text "Range:"
    ttk::scale $rb.rscale -command "updateRangeCircles $target" \
			-to 1500 -orient horizontal -variable range
    ttk::entry $rb.range -width 5 -textvariable range
    pack $rb.rlab $rb.rscale $rb.range -side left -padx 4 -pady 4

    # bandwidth
    set spinbox [getspinbox]
    ttk::label $rb.label -anchor w -text "Bandwidth (bps):"
    $spinbox $rb.value -justify right -width 10 -validate focus
    $rb.value configure -validatecommand {checkIntRange %P 0 1000000000} \
			-from 0 -to 1000000000 -increment 1000000
    $rb.value insert 0 $bw
    pack $rb.label $rb.value \
	-side left -padx 4 -pady 4
    pack $rb -side top -anchor w -padx 4 -pady 4

    # delay and error (de) frame
    set de $wi.wl.note.basic.de
    ttk::frame $de

    ttk::label $de.label2 -anchor w -text "Delay (us):"
    $spinbox $de.value2 -justify right -width 10 -validate focus
    $de.value2 configure -validatecommand {checkIntRange %P 0 10000000} \
			-from 0 -to 10000000 -increment 5000
    $de.value2 insert 0 $delay
    pack $de.label2 $de.value2 -side left -padx 4 -pady 4

    $spinbox $de.value3 -justify right -width 5 -validate focus
    if { [lindex $systype 0] == "Linux" } {
	ttk::label $de.label3 -anchor w -text "Loss (%):"
	$de.value3 configure -from 0 -to 100.0 -increment 0.1
    } else { ;# netgraph
	ttk::label $de.label3 -anchor w -text "Bit Error (1/N):"
	$de.value3 configure -width 10 -validatecommand \
			{checkIntRange %P 0 10000000000000} \
			-from 0 -to 10000000000000 -increment 1000
    }
    $de.value3 insert 0 $per
    pack $de.label3 $de.value3 \
	-side left -padx 4 -pady 4
    pack $de -side top -anchor w -padx 4 -pady 4

    # jitter frame
    set jt $wi.wl.note.basic.jt
    ttk::frame $jt
    ttk::label $jt.label1 -anchor w -text "Jitter (us):"
    $spinbox $jt.value1 -justify right -width 10 -validate focus
    $jt.value1 configure -validatecommand {checkIntRange %P 0 10000000} \
			-from 0 -to 10000000 -increment 5000
    $jt.value1 insert 0 $jitter
    pack $jt.label1 $jt.value1 -side left -padx 4 -pady 4
    pack $jt -side top -anchor w -padx 4 -pady 4

    ###
    ### EMANE
    ###
    ttk::frame $wi.wl.note.emane
    $wi.wl.note add $wi.wl.note.emane -text "EMANE" -underline 0
    set txt "The EMANE emulation system provides more complex wireless radio"
    set txt "$txt emulation\n using pluggable MAC and PHY modules."
    set txt "$txt Refer to the wiki for configuration option details"
    ttk::label $wi.wl.note.emane.tlab -text $txt
    ttk::button $wi.wl.note.emane.wiki -text "EMANE Wiki" \
	-image $plugin_img_edit -compound right \
	-command \
	"_launchBrowser https://github.com/adjacentlink/emane/wiki"
    pack $wi.wl.note.emane.wiki -side top -anchor w -padx 4 -pady 4
    pack $wi.wl.note.emane.tlab -side top -anchor w -padx 4 -pady 4

    # models
    set mod $wi.wl.note.emane.models
    ttk::labelframe $mod -text "EMANE Models"
    pack $mod -side top -fill both -expand true -padx 4 -pady 4

    set side "nw"
    ttk::radiobutton $mod.none -text "none" -command "updateOptBtn $wi none" \
	-value "none" -variable g_selected_model -width 12
    pack $mod.none -side top -anchor w -padx 4 -pady 0

    set caplist [getPluginsCapList]
    set emane_models {}
    set have_emane_models false
    # TODO: a refresh button here would be nice
    foreach cap $caplist {
	set captype [lindex [split $cap =] 0]
	set capname [lindex [split $cap =] 1]
	if { [string range $capname 0 5] != "emane_" } { continue }
	set emane_model [capTitle $capname]

	ttk::radiobutton $mod.$capname -text $emane_model -value $capname \
		-variable g_selected_model -width 12 \
		-command "updateOptBtn $wi $emane_model"
	pack $mod.$capname -side top -anchor w -padx 4 -pady 0
	set have_emane_models true
    }
    if { ! $have_emane_models } {
	# show connection dialog box to indicate why there are no EMANE models
	$mod.none configure -text "Please install EMANE" \
		-width "45"
	after 500 {
	    update ;# allow dialog layout, otherwise strange results
	    pluginConnect "" connect true
	}
    }

    # options buttons
    set opts $wi.wl.note.emane.opts
    ttk::frame $opts
    ttk::button $opts.model -text "model options" \
	-image $plugin_img_edit -compound right -command "" -state disabled \
	-command "configCap $target \[set g_selected_model\]"
    # global EMANE model uses no node in config request message, although any
    # config will be stored with the EMANE node having the lowest ID
    ttk::button $opts.gen -text "EMANE options" \
	-image $plugin_img_edit -compound right \
	-command "configCap -1 emane"
	#-command "popupPluginsCapConfigHelper $wi config $target"
    pack $opts.model $opts.gen -side left -padx 4 -pady 4
    pack $opts -side top -anchor c -padx 4 -pady 4

    # show correct tab basic/emane based on selection
    if { $g_selected_model == "none" } {
	$wi.wl.note select $wi.wl.note.basic
    } else {
	$wi.wl.note select $wi.wl.note.emane
    }
    updateOptBtn $wi [capTitle $g_selected_model]


    # WLAN has not been linked yet, generate addresses here.
    if { [getIfcIPv4addr $target wireless] == "" } {
        setIfcIPv4addr $target wireless "[findFreeIPv4Net 24].0/32"
    }
    if { [getIfcIPv6addr $target wireless] == "" } {
        setIfcIPv6addr $target wireless "[findFreeIPv6Net 64]::0/128"
    }

    frame $wi.bottom -padx 4 -pady 4

    # 4. IPv4/IPv6 addresses
    #
    # IPv4 address
    #
    frame $wi.bottom.ipv4
    label $wi.bottom.ipv4.addrl -text "IPv4 subnet" \
    	-anchor w
    entry $wi.bottom.ipv4.addrv -bg white -width 30 \
        -validate focus -invcmd "focusAndFlash %W"
    $wi.bottom.ipv4.addrv insert 0 \
        [getIfcIPv4addr $target wireless]
    $wi.bottom.ipv4.addrv configure \
        -vcmd {checkIPv4Net %P}

    #
    # IPv6 address
    #
    frame $wi.bottom.ipv6
    label $wi.bottom.ipv6.addrl -text "IPv6 subnet" \
    	-anchor w
    entry $wi.bottom.ipv6.addrv -bg white -width 30 \
        -validate focus -invcmd "focusAndFlash %W"
    $wi.bottom.ipv6.addrv insert 0 \
        [getIfcIPv6addr $target wireless]
    $wi.bottom.ipv6.addrv configure \
        -vcmd {checkIPv6Net %P}

    #
    # Link all nodes button
    #
    button $wi.bottom.script -text "ns-2 mobility script..." \
    	-command "sendConfRequestMessage -1 $target ns2script 0x1 -1 {}"
    button $wi.bottom.linkall -text "Link to all routers" \
        -command "linkAllNodes $target"
    set msg "Select new WLAN $target members:"
    set cmd "linkSelectedNodes $target"
    button $wi.bottom.memb -text "Choose WLAN members" \
	-command "popupSelectNodes \"$msg\" \"\" {$cmd}"

    # layout items

    pack $wi.bottom.ipv4.addrl $wi.bottom.ipv4.addrv -side left
    pack $wi.bottom.ipv4 -side top -anchor w
    pack $wi.bottom.ipv6.addrl $wi.bottom.ipv6.addrv -side left
    pack $wi.bottom.ipv6 -side top -anchor w
    pack $wi.bottom.script $wi.bottom.linkall $wi.bottom.memb \
	-side left -anchor center

    pack $wi.bottom -side top -anchor w
}

# toggle the enabling/disabling of Basic/EMANE controls
proc updateOptBtn { wi txt } {
    set s normal
    set bs disabled
    if { $txt == "none" } { set s disabled; set bs !disabled; set txt "model" }
    $wi.wl.note.emane.opts.model configure -text "$txt options" -state $s

    $wi.wl.note.basic.rb.range configure -state $bs
    $wi.wl.note.basic.rb.rscale state $bs
    if { $bs == "disabled" } { .c delete -withtag rangecircles }

    # spinbox state: disabled/!disabled (Tk 8.5.8) or disabled/normal (ttk)
    set spinboxstate $bs
    if { [info command ttk::spinbox] == "" && $spinboxstate == "!disabled" } {
	set spinboxstate normal
    }
    $wi.wl.note.basic.rb.value configure -state $spinboxstate
    $wi.wl.note.basic.de.value2 configure -state $spinboxstate
    $wi.wl.note.basic.de.value3 configure -state $spinboxstate
    $wi.wl.note.basic.jt.value1 configure -state $spinboxstate
}

proc wlanDoubleClick { node button } {
    set modeldata [netconfFetchSection $node "mobmodel"]
    # modeldata e.g. = "coreapi emane_rfpipe"
    set modeltype [lindex $modeldata 1]
    if { [string range $modeltype 0 4] == "emane" } {
	if { [string range $modeltype 6 end] == "commeffect" } {
	    set cmd "emanecommeffectcontroller"
	    if { [catch {exec $cmd & } e] } {
		tk_messageBox -icon error -message "Error launching $cmd: $e"
	    }
	}
    } else {
	# TODO: non-EMANE WLAN dialog, e.g. mobility
    }
}

# helper returns true for WLANs configured with EMANE models
proc isEmane { node } {
    if { [nodeType $node] != "wlan" } {
	return false
    }
    set modeldata [netconfFetchSection $node "mobmodel"]
    set modeltype [lindex $modeldata 1]
    if { [string range $modeltype 0 4] == "emane" } {
	return true
    } else {
	return false
    }
}

# return the EMANE node (WLAN) having the lowest node number
# the EMANE global config will be stored with this node
proc minEmaneNode {} {
    global node_list
    set min ""
    foreach node $node_list {
	if { ![isEmane $node] } { continue }
        set nodenum [string range $node 1 end]
	if { $min == "" || $nodenum < $min } {
	    set min $nodenum
	}
    }
    if { $min != "" } { set min "n$min" }
    return $min
}