#!/bin/bash
#\
exec wish -f "$0" ${1+"$@"}
#
# wacomcpl-exec -- Wacom Control Panel Utility to Change Configuration Setting.
#
# Author	: Ping Cheng <pinglinux@gmail.com>
# Creation date	: 04/05/2003
# Updated       : 2003 - 2010
#
# Based on calibrate 1998-99 Patrick Lecoanet --
#
# There are three ways to run this program:
#  wacomcpl:  full control panel features with pressure and button configuration, 
#		tablet and screen mapping, as well as calibration. 
#
#  wacomcpl calibrate dev: fesatures calibration for a LCD device (identified as dev)
#
#  wacomcpl calibrate dev on: fesatures calibration for a LCD device (identified as dev)
#			      and display warnings when abnormal coordinates are detected
#
# This code is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This code 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
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this code; if not, write to the Free
# Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# 		WARNING
# This code is a minefield. A few hints on how to deal with this:
# - TCL cannot return arrays or pass them as arguments to a function,
# one has to serialize the array and pass it [http://wiki.tcl.tk/3262]
# The original author(s) have chosen not to do so, instead relying on global
# variables. Some functions still pass parameters, but arrays are all global
# variables. Instead global vars are set that are then used by the caller.
#
# - Wacomcpl sometimes passes variables around, but most variables are
# simply declared as global and changed wherever they are needed. Plus, most
# functions simply import everything as global, even the variables they
# don't need. See the lookup table below.
#
# - Variable naming: a change in the wacom driver to _not_ store the
# defaults for everything and the world means that wacomcpl can only "reset"
# (to pre-saved status) but not "load defaults" since it doesn't know the
# defaults. Those bits and pieces that claim to load defaults simply reset.
#
# Variable lookup table for global but undeclared variables (this list is
# incomplete):
# bName():
#	boolean array, keys are "pressure", "button", etc. The values are
#	used to enable/disable buttons
# calibX_org, calibX_org, calibX_btm, calibY_btm:
#	The 4 coordinates of the screen to be calibrated to (in screen
#	coords)
# calibResults():
#	Array: keys "xDev", "yDev", value for each an int[2] for the actual
#	values, calibResults(xDev, 0) == min_x, calibResults(xDev, 1) == max_x
# clickCheck
#	If 1, wacomcpl displays a "is it better now?" window after
#	calibration
# currentb:
# currentk:
#	The currently selected button to be dealt with, or key.
# currentW:
# 	Current window, value either .keyboard or .allothers, possibly
# 	because keyboard is a subwindow of the other window.
# db():
# 	array, index is button number (see also modeToggleB), value is the
# 	GUI name for this button.
# db1, db2, db3, ... db14:
# 	Probably the predecessors of db(), but who cares. Unused.
# dm():
#	array, two values, "Relative" and "Absolute"
# getOption(device name, option name):
#	Stores the value of the option for the device, after a call to
#	updateDeviceOptionProc (see there for more details)
# hasPad():
# hasTouch():
# hasGesture():
# isLCD():
#	boolean array, index is the model number
# Option():
#	Option values to be retrieved by updateDeviceOptionProc  (see there for
#	more details)
# numPadButtons():
# numPadRelW():
# numPadAbsW():	(this one is always filled with 0)
# numPadRings():
# numPadStrips():
#	numeric array, index is the model number, value is number of
#	buttons/rings
# numScreens():
#	Array, key: device name, value is number of screens for the device
# sm():
#	Array, two values "Side Switch Only", "Side Switch + Tip"
# snd:
#	Screen "default" setting (the one selected on dialog init)
# spName():
#	Strip names, numeric array button number : strip name
# tvID:
#	int, current twinview settings
# tvd:
#	TwinView "default" setting (the one selected on dialog init)
# wName():
#	Array, filled with the button labels to be used. These names are
#	all filled statically once (with one exception). When used, the code
#	uses wName(index) to get the right label. No joke.
# xDevMin, xDevMax:
# yDevMin, yDevMax:
#	minx, maxx, miny, maxy in device coordinates

package require LIBWACOMXI

set currentScreen 0
set desktopWidth [ winfo screenwidth . ]
set desktopHeight [ winfo screenheight . ]
# Dimensions of the current screen if tied to a screen
set screenWidth [ winfo screenwidth . ]
set screenHeight [ winfo screenheight . ]
set screenXBottom [ winfo screenwidth . ]
set screenYBottom [ winfo screenheight . ]

# Screen Origin x/y
set screenX_org 0
set screenY_org 0
set swapThresh 100
# Size of the calibration windows (they're square)
set size 200
# Size of the little circle inside the calibration window
set circle_size 10
# length of the crosshair lines around the circle in the calibration window
set line_size 30
set config_file "~/.wacomcplrc"

# Main window location and size
set origin_x [ expr ($desktopWidth/2) - ($desktopWidth/4) ]
set origin_y [ expr ($desktopHeight/2) - ($desktopHeight/4) ]
set windowSize "700x300"

set device ""
# In theory, it'd enable Help. Except that nothing ever set it...
set showHelp 0
# The following fooB keys are indices for buttons that don't reflect actual
# button mappings. because the wacom driver assumes 32 buttons as max, the
# ones > 32 are special button numbers that are then mapped elsewhere.
set modeToggleB 33
set ignoreButton 34
set doubleClickB 35
set displayToggleB 36
set keystrokeB 38

# Highest model number for any tablet. Needed for numPadButtons and
# friends.
set maxNumTablets 227
set numControls 0
set maxNumButtons 18
set maxNumStripEvents 4
# 1 if run as wacomcpl calibrate "device name"
set calibration_sequence_only 0
# 1 if run with exactly 3 arguments, prints out a warning about strange
# coordinates after calibration (if required)
set calibration_with_warning 0

# send the same data to the associated eraser if it is a stylus
proc updateWacomrcStylusAndEraser { device option value } {
	global getDeviceModel

	updateWacomrc $device $option $value

	set type $getDeviceModel($device,type)
	if { ![ string compare -nocase $type "stylus" ] } {
	    set eraser $getDeviceModel($device,eraser)
	    if { [ string compare $eraser $device ] && $eraser != "" } {
		updateWacomrc $eraser $option $value
	    }
	}
}

proc updateCurrentScreenInfo {} {
    global device numScreens currentScreen 
    global screenXBottom screenYBottom screenX_org screenY_org 
    global getScreenInfo screenWidth screenHeight

    if { $numScreens($device) != 1 } {
        set screenInfo $getScreenInfo($device,Screen$currentScreen)
	set screenXBottom [ lindex $screenInfo 0 ]
	set screenYBottom [ lindex $screenInfo 1 ]
	set screenX_org [ lindex $screenInfo 2 ]
	set screenY_org [ lindex $screenInfo 3 ]
	set screenWidth abs([ expr $screenXBottom-$screenX_org ])
	set screenHeight abs([ expr $screenYBottom-$screenY_org ])
   }
}

# Creates one of the pink calibration windows and draws the little circle
# into it.
# Arguments:
# 	name: the name this window will be referred to
# 	geometry: geometry string in the form +x+y
proc create_calibration_window {name geometry} {
    global size circle_size line_size

    set circleox [ expr $size/2-$circle_size/2 ]
    set circleoy $circleox
    set circlecx [ expr $circleox+$circle_size ]
    set circlecy $circlecx
    set vertx [ expr $size/2 ]
    set vertoy [ expr ($size-$line_size)/2 ]
    set vertcy [ expr $vertoy+$line_size ]
    set horizox [ expr ($size-$line_size)/2 ]
    set horizcx [ expr $horizox+$line_size ]
    set horizy [ expr $size/2 ]

    toplevel $name
    wm geometry $name $geometry
    wm overrideredirect $name true
    canvas $name.m -height $size -width $size -bg "#505075"
    $name.m create oval $circleox $circleoy $circlecx $circlecy -outline white
    $name.m create line $vertx $vertoy $vertx $vertcy -fill white
    $name.m create line $horizox $horizy $horizcx $horizy -fill white
    pack $name.m
}

# Write out the wacomcplrc file __and__ run xsetwacom to apply the settings
# to the device right now as well.
# If the file already exists, a backup copy is made and all existing options
# will be replaced and new options will be __appended__
proc updateWacomrc {device option value} {
    global config_file

    if { ![ file exists $config_file ] } {
	exec echo "xsetwacom set \"$device\" $option \"$value\"" > $config_file
    } else {
	file copy -force $config_file $config_file~
	exec sed -e "/xsetwacom set \"$device\" $option /d" < $config_file > /tmp/wacomcpl.tmp
	exec cat /tmp/wacomcpl.tmp > $config_file
	exec echo "xsetwacom set \"$device\" $option \"$value\"" >> $config_file
	file delete -force /tmp/wacomcpl.tmp
    }
    if { $option == "PressCurve" } {
	set index 0
	set v1 [ lindex $value $index ]
    	for { set i 2 } { $i < 5 } { incr i 1 } {
	    set index [ expr $index+1 ]
	    set v$i [ lindex $value $index ]
	}
	exec xsetwacom set "$device" $option $v1 $v2 $v3 $v4
    } else {
	exec xsetwacom set "$device" $option $value
    }
}

proc verifycalibResultsTopLeft { } {
    global device getOptionDefault xDevMin yDevMin
    global screenWidth screenHeight size end

    set tol_w [expr ($getOptionDefault($device,BottomY) - $getOptionDefault($device,TopY)) * $size / $screenHeight]
    set tol_h [expr ($getOptionDefault($device,BottomX) - $getOptionDefault($device,TopX)) * $size / $screenWidth]

    set tol_top [expr $getOptionDefault($device,TopY)]
    set tol_bottom [expr $tol_top + $tol_h]
    set tol_left [expr $getOptionDefault($device,TopX)]
    set tol_right [expr $tol_left + $tol_w]

    if { ($xDevMin < $tol_left) || ($xDevMin > $tol_right) ||
    	($yDevMin < $tol_top) || ($yDevMin > $tol_bottom) } {
    	.topleft.m configure -background "#ff0000"
    	set end 0
    	after 100 set end 1
    	vwait end	

    	.topleft.m configure -background "#505075"
    	set end 0
    	after 70 set end 1
    	vwait end	

    	.topleft.m configure -background "#ff0000"
    	set end 0
    	after 100 set end 1
    	vwait end

    	set xDevMin [expr ($getOptionDefault($device,TopX) + ($tol_w / 2))]
    	set yDevMin [expr ($getOptionDefault($device,TopY) + ($tol_h / 2))]
    }
}

proc verifycalibResultsBottomRight { } {
    global device getOptionDefault xDevMax yDevMax
    global screenWidth screenHeight size end

    set tol_w [expr ($getOptionDefault($device,BottomY) - $getOptionDefault($device,TopY)) * $size / $screenHeight]
    set tol_h [expr ($getOptionDefault($device,BottomX) - $getOptionDefault($device,TopX)) * $size / $screenWidth]

    set tol_bottom [expr $getOptionDefault($device,BottomY)]
    set tol_top [expr $tol_bottom - $tol_h]
    set tol_right [expr $getOptionDefault($device,BottomX)]
    set tol_left [expr $tol_right - $tol_w]

    if { ($xDevMax < $tol_left) || ($xDevMax > $tol_right) ||
    	($yDevMax < $tol_top) || ($yDevMax > $tol_bottom) } {
    
    	.bottomright.m configure -background "#ff0000"
    	set end 0
    	after 100 set end 1
    	vwait end	

    	.bottomright.m configure -background "#505075"
    	set end 0
    	after 70 set end 1
    	vwait end	

    	.bottomright.m configure -background "#ff0000"
    	set end 0
    	after 100 set end 1
    	vwait end
    
    	set xDevMax [expr ($getOptionDefault($device,BottomX) - ($tol_w / 2))]
    	set yDevMax [expr ($getOptionDefault($device,BottomY) - ($tol_h / 2))]
    }
}

proc verifycalibResults { } {
    global device getOptionDefault xDevMin xDevMax yDevMin yDevMax
    global screenWidth screenHeight size clickCheck tvID

    # Verifying the end results
    if { $tvID == 1 } { # a Nvidia Xinerama setup
	set tol_offset [expr ($getOptionDefault($device,BottomY) / 4)]
	set tol_left [expr ($getOptionDefault($device,BottomY) - $tol_offset) / 2]
	set tol_right [expr ($getOptionDefault($device,BottomY) + $tol_offset) / 2]
	set tol_bottom [expr ($getOptionDefault($device,BottomY) - $tol_offset)]

	# set tablet Y values to normal when offset is by whole of the tablet size.
	# Whole has to be first and we do it two times to make sure.
	if { $yDevMin > $tol_bottom } {
	    set yDevMin [expr ($yDevMin - $getOptionDefault($device,BottomY))]
	    set yDevMax [expr ($yDevMax - $getOptionDefault($device,BottomY))]
	} 
	if { $yDevMin > $tol_bottom } {
	    set yDevMin [expr ($yDevMin - $getOptionDefault($device,BottomY))]
	    set yDevMax [expr ($yDevMax - $getOptionDefault($device,BottomY))]
	} elseif { ($yDevMin > $tol_left) && ($yDevMin < $tol_right) } {
	    # set tablet Y values to normal when offset is by half tablet size
	    set yDevMin [expr ($yDevMin - ($getOptionDefault($device,BottomY) / 2))]
	    set yDevMax [expr ($yDevMax - ($getOptionDefault($device,BottomY) / 2))]
	}

	set tol_offset [expr ($getOptionDefault($device,BottomX) / 4)]
	set tol_left [expr ($getOptionDefault($device,BottomX) - $tol_offset) / 2]
	set tol_right [expr ($getOptionDefault($device,BottomX) + $tol_offset) / 2]
	set tol_bottom [expr ($getOptionDefault($device,BottomX) - $tol_offset)]

	# set tablet X values to normal when offset is by whole of the tablet size. 
	# Whole has to be first and we do it two times to make sure.
	if { $xDevMin > $tol_bottom } {
	    set xDevMin [expr ($xDevMin - $getOptionDefault($device,BottomX))]
	    set xDevMax [expr ($xDevMax - $getOptionDefault($device,BottomX))]
	} 
	if { $xDevMin > $tol_bottom } {
	    set xDevMin [expr ($xDevMin - $getOptionDefault($device,BottomX))]
	    set xDevMax [expr ($xDevMax - $getOptionDefault($device,BottomX))]
	} elseif { ($xDevMin > $tol_left) && ($xDevMin < $tol_right) } {
	    # set tablet X values to normal when offset is by half tablet size
	    set xDevMin [expr ($xDevMin - ($getOptionDefault($device,BottomX) / 2))]
	   set xDevMax [expr ($xDevMax - ($getOptionDefault($device,BottomX) / 2))]
	}
    } else {
	set tol_w [expr ($getOptionDefault($device,BottomY) - $getOptionDefault($device,TopY)) * $size / $screenHeight]
	set tol_h [expr ($getOptionDefault($device,BottomX) - $getOptionDefault($device,TopX)) * $size / $screenWidth]

	set tol_top [expr $getOptionDefault($device,TopY)]
	set tol_bottom [expr $tol_top + $tol_h]
	set tol_left [expr $getOptionDefault($device,TopX)]
	set tol_right [expr $tol_left + $tol_w]

	if { ($xDevMin < $tol_left) || ($xDevMin > $tol_right) ||
		($yDevMin < $tol_top) || ($yDevMin > $tol_bottom) } {
	    set clickCheck 1
	}

	set tol_w [expr ($getOptionDefault($device,BottomY) - $getOptionDefault($device,TopY)) * $size / $screenHeight]
	set tol_h [expr ($getOptionDefault($device,BottomX) - $getOptionDefault($device,TopX)) * $size / $screenWidth]

	set tol_bottom [expr $getOptionDefault($device,BottomY)]
	set tol_top [expr $tol_bottom - $tol_h]
	set tol_right [expr $getOptionDefault($device,BottomX)]
	set tol_left [expr $tol_right - $tol_w]

	if { ($xDevMax < $tol_left) || ($xDevMax > $tol_right) ||
		($yDevMax < $tol_top) || ($yDevMax > $tol_bottom) } {
	    set clickCheck 1
	}
    }
}

# Scale our screen's topX into device coordinates and return the x/y offset in
# device coordinates.
# The driver uses the device width on the current screen
# and extends the axis range to match that. We just have to know that...
# Returns a two-element list with x/y offset.
proc calculate_screen_offset { device xDevMin xDevMax yDevMin yDevMax } {
    global getOption
    global desktopWidth desktopHeight screenWidth screenHeight
    global screenX_org screenY_org

    # We got those ones just after the xydefaults call
    set dev_min_x $getOption($device,TopX)
    set dev_min_y $getOption($device,TopY)
    set dev_max_x $getOption($device,BottomX)
    set dev_max_y $getOption($device,BottomY)
    set dev_width [ expr $dev_max_x - $dev_min_x ]
    set dev_height [ expr $dev_max_y - $dev_min_y ]
    set ratio_x [ expr 1.0 * $desktopWidth / $screenWidth ]
    set ratio_y [ expr 1.0 * $desktopHeight / $screenHeight ]

    # calculate the axis ranges the driver uses
    set total_dev_width [ expr $dev_width * $ratio_x ]
    set total_dev_height [ expr $dev_width * $ratio_x ]

    # device coordinate offset of current screen
    set offset_x [ expr $total_dev_width * $screenX_org/$desktopWidth ]
    set offset_y [ expr $total_dev_height * $screenY_org/$desktopWidth ]

    set offset_x [ expr int($offset_x) ]
    set offset_y [ expr int($offset_y) ]

    return [ list $offset_x $offset_y ]
}


# Callback function if one of the calibration windows registered a button
# event.
# Arguments:
#   which: state machine.
#		0 .. top left calibration window clicked
#		1 .. bottom right calibration window clicked
#   xDev, yDev: x/y coordinates in device coordinates
proc calibrationSequence {which xDev yDev} {
    global device calibResults screenY_org screenX_org orig_device
    global workingTags screenTags size numScreens
    global swapThresh screenWidth screenHeight getOptionDefault
    global getDeviceModel screenXBottom screenYBottom
    global calibX_org calibY_org calibX_botm calibY_botm
    global xDevMin xDevMax yDevMin yDevMax
    global calibration_sequence_only clickCheck 
    global calibration_with_warning
    global currentScreen numScreens

    set scaling [exec xsetwacom get "$device" xscaling]
    if { $scaling == 1 } {
	set xNewDev [expr (($xDev - $calibX_org) * $getOptionDefault($device,BottomX))]
	set xDev [expr ($xNewDev / ($calibX_botm - $calibX_org))]
	set yNewDev [expr (($yDev - $calibY_org) * $getOptionDefault($device,BottomY))]
	set yDev [expr ($yNewDev / ($calibY_botm - $calibY_org))]
    }

    set calibResults(xDev,$which) $xDev
    set calibResults(yDev,$which) $yDev

    # Top left calibration window clicked.
    if { $which == 0 } {
	wacomxi::bindevent .topleft.m "$orig_device" <ButtonRelease> ""

	set xDevMin $calibResults(xDev,0)
	set yDevMin $calibResults(yDev,0)

	if { $calibration_with_warning != 0 } {
		verifycalibResultsTopLeft
	}

	.topleft.m configure -background "#505075"
	.topleft.m configure -cursor "watch #505075 #505075"
	.bottomright.m configure -background "#df94df"
	#hide the cursor
	.bottomright.m configure -cursor "center_ptr #df94df #df94df"
	wacomxi::bindevent .bottomright.m "$orig_device" <ButtonRelease> \
		{calibrationSequence 1 %0 %1}

    # Bottom right calibration window clicked
    } elseif { $which == 1 } {
	wacomxi::bindevent .bottomright.m "$orig_device" <ButtonRelease> ""	
	# Offset to screen edges (__not__ related to window borders at all
	set borderOffset [expr ($size / 2 )]

	set xDevMax $calibResults(xDev,1)
	set yDevMax $calibResults(yDev,1)
	.bottomright.m configure -background "#505075"
	.bottomright.m configure -cursor "watch #505075 #505075"

	# A direction verification of the click
	set clickCheck 0
	if { $calibResults(xDev,1) < $calibResults(xDev,0) } {
	    set clickCheck 1
	    set xDevMin $calibResults(xDev,1)
	    set xDevMax $calibResults(xDev,0)
	}
	if { $calibResults(yDev,1) < $calibResults(yDev,0) } {
	    set clickCheck 1
	    set yDevMin $calibResults(yDev,1)
	    set yDevMax $calibResults(yDev,0)
	}

	if { $calibration_with_warning != 0 } {
	    verifycalibResultsBottomRight
	    .bottomright.m configure -background "#505075"
	} else {
	    verifycalibResults
	}

	set widthDev [expr $xDevMax - $xDevMin]
	set heightDev [expr $yDevMax - $yDevMin]
 	set widthX [expr $screenWidth - $size]
 	set heightX [expr $screenHeight - $size]

	set xDevMin [expr $xDevMin - ($borderOffset * $widthDev / $widthX)]
	set xDevMax [expr $xDevMax + ($borderOffset * $widthDev / $widthX)]
	set yDevMin [expr $yDevMin - ($borderOffset * $heightDev / $heightX)]
	set yDevMax [expr $yDevMax + ($borderOffset * $heightDev / $heightX)]

	if { $numScreens($device) > 1} {
	    updateWacomrcStylusAndEraser $device "Screen_No" $currentScreen

	    # xDevMin/... are now in device coordinates relative to the screen.
	    # we need to get them into device coordinates relative to the
	    # device (i.e. 0..maxx)
	    set offsets [ calculate_screen_offset $device $xDevMin $xDevMax $yDevMin $yDevMax ]

	    set off_x [ lindex $offsets 0 ]
	    set off_y [ lindex $offsets 1 ]
	    set xDevMin [ expr $xDevMin - $off_x ]
	    set xDevMax [ expr $xDevMax - $off_x ]
	    set yDevMin [ expr $yDevMin - $off_y ]
	    set yDevMax [ expr $yDevMax - $off_y ]
	}

	updateWacomrcStylusAndEraser $device "topx" $xDevMin
	updateWacomrcStylusAndEraser $device "topy" $yDevMin
	updateWacomrcStylusAndEraser $device "bottomx" $xDevMax
	updateWacomrcStylusAndEraser $device "bottomy" $yDevMax

	if { $clickCheck == 1 } {
	    messageWindow "Warning !!!" \
		"\n\nIf the cursor follows the tip of your\n\
		tool better than before, you are fine.\n\
		Otherwise, please rerun the calibration\n\
		steps and make sure you are clicking\n\
		on the center of the pink crosshairs."
	}

	destroy .topleft .bottomright
	if { $numScreens($device) > 1 } {
            if { $calibration_sequence_only == 0 } {
		.screen.list.label configure -text ""
 		bindtags .screen.list.list $screenTags
	    } else {
		.list.label configure -text ""
 		bindtags .list.list $screenTags
	    }
	} else {
            if { $calibration_sequence_only == 1 } {
                exit 0
	    } else {
		closeTabWindow
	    }
	}
    }
}

proc Calibration {} {
    global numScreens device screenTags getOption Option 
    global tvID calibration_sequence_only

    # mainly to get the defaults
    set Option(1) "TopX"
    set Option(2) "TopY"
    set Option(3) "BottomX"
    set Option(4) "BottomY"
    set Option(5) "Mode"
    set Option(6) "TwinView"

    updateDeviceOptionProc $device 6
    set mode $getOption($device,Mode)
    set tvID $getOption($device,TwinView)

    if { $calibration_sequence_only == 0 } {
	if { $mode != 1 && $mode != "Absolute" } {
	    disableButtons
	    messageWindow "Warning " "\n\nDevice $device is in relative mode. \n\
		You can calibrate $device only when it is in absolute mode. \n\
		Please swith $device's mode and try again. "
	    closeTabWindow
	    return
	}
    }

#    bindtags .workingDev.list .
    if { $numScreens($device) > 1 } {
	displayScreenList $device
	if { $calibration_sequence_only == 0 } {
		set screenTags [ bindtags .screen.list.list]
		.screen.list.title configure -text "Select $device associated Screen:"
		wm state .screen normal
	} else {
		set screenTags [ bindtags .list.list]
		.list.title configure -text "Select $device associated Screen:"
		wm state . normal
	}
    } else {
	updateCurrentScreenInfo
	startCalibration
    }

    if { $calibration_sequence_only == 0 } {
	disableButtons
    }
}

# Initialises the two pink calibration windows, set them to the
# active/inactive color and registers for mouse events.
# When a mouse event is registred, calibrationSequence() is called
proc startCalibration {} {
    global device size numScreens tvID orig_device
    global calibResults calibX_org calibY_org calibX_botm calibY_botm
    global screenX_org screenY_org screenXBottom screenYBottom
    global screenWidth screenHeight calibration_sequence_only
    global Option getOption
    
    if { $numScreens($device) > 1 } {
	if { $calibration_sequence_only == 0 } {
	     bindtags .screen.list.list .
	} else {
	     bindtags .list.list .
	}
    }

    # convert underscores back to spaces for wacomxi.c
    regsub -all {_} $device " " orig_device

    if { $tvID == 1 } { # Nvidia Xinerama setup starts at (0,0) even when it is not in xorg.conf
	set calibX_org 0
	set calibY_org 0
	set calibX_botm $screenWidth
	set calibY_botm $screenHeight
    } else {
	set calibX_org $screenX_org
	set calibY_org $screenY_org
	set calibX_botm $screenXBottom
	set calibY_botm $screenYBottom
    }
    # calculate the x/y for the bottom right calibration window
    set y_coor [ expr $calibY_botm-$size*(abs($calibY_botm)/$calibY_botm) ]
    set x_coor [ expr $calibX_botm-$size*(abs($calibX_botm)/$calibX_botm) ]

    create_calibration_window .topleft +$calibX_org+$calibY_org
    create_calibration_window .bottomright +$x_coor+$y_coor
    update
    #
    # Start calib sequence
    catch {unset calibResults}
    # Reset the property, the driver will reset it to the device-specific
    # values. Read those values in, we need them later.
    exec xsetwacom set "$device" xydefault 0

    set Option(1) "TopX"
    set Option(2) "TopY"
    set Option(3) "BottomX"
    set Option(4) "BottomY"
    updateDeviceOptionProc $device 4

    .topleft.m configure -background "#df94df"
    # hide the cursor
    .topleft.m configure -cursor "center_ptr #df94df #df94df"
    wacomxi::bindevent .topleft.m "$orig_device" <ButtonRelease> \
		{calibrationSequence 0 %0 %1}
    .bottomright.m configure -background "#505075"
    #hide the cursor
    .bottomright.m configure -cursor "watch #505075 #505075"
    helpWindow "Help Window " \
		"\n\nPlease click on the center of \n\
		the pink crosshair using $device \n\
		Please don't click on anything else \n\
		by $device before you finish"
}

# Displays a help window, except that showHelp is always 0 so it never does
# anything. Woo!
proc helpWindow { tString mString } {
    global showHelp

    if { $showHelp }  {
	messageWindow $tString $mString
    }
}

proc messageWindow { tString mString } {
    toplevel .mWindow
    wm title .mWindow $tString
    wm transient .mWindow .
    text .mWindow.text -background gray -width 40 -height 10
    button .mWindow.dismiss -text "Dismiss" \
		-command "destroy .mWindow; set ok 1"
    .mWindow.text insert end $mString
    pack .mWindow.text .mWindow.dismiss

    tkwait variable ok 
}

# Callback when a screen has been selected on the screen selection list.
# Sets the device to the current screen, then starts calibration
proc updateScreenList {} {
    global currentScreen numScreens screenWidth screenHeight
    global device screenX_org screenY_org origin_x origin_y
    global calibration_sequence_only

    if { $numScreens($device) > 1 } {
	if { $calibration_sequence_only == 0 } {
	    set cScreen [ .screen.list.list get anchor ]
	} else {
	    set cScreen [ .list.list get anchor ]
	}

	for { set i 0 } { $i < $numScreens($device) } { incr i 1 } {
	    if { $cScreen == "Screen$i" } {
		set currentScreen $i
		set i $numScreens($device)
	    }
	}
	exec xsetwacom set "$device" Screen_No $currentScreen
    }
    updateCurrentScreenInfo
    set origin_x [ expr $screenX_org+$screenWidth/2-$screenWidth/4 ]
    set origin_y [ expr $screenY_org+$screenHeight/2-$screenHeight/4 ]
    wm geometry . =+$origin_x+$origin_y

    if { $calibration_sequence_only == 0 } {
	.screen.list.label configure -text $cScreen
	set o_x [ expr $origin_x+100 ]
	set o_y [ expr $origin_y+20 ]
	wm geometry .screen =+$o_x+$o_y
    } else {
	.list.label configure -text $cScreen
    }

    startCalibration
}

# Disable the buttons set to 1 in $bName(). This function only disables,
# because clearly, having this function enable buttons as well would just be
# too straightforward. So we duplicate the enable code in closeTabWindow()
proc disableButtons {} {
    global bName numButton numStrips currentW currentb

    if { $currentb } {
	for { set i 1 } { $i <= [ expr ($numButton+$numStrips) ] } { incr i 1 } {
	    .allothers.f.$i configure -state disabled
	}
	.allothers.f.ok configure -state disable
	.allothers.f.default configure -state disable
	.allothers.f.cancel configure -state disable
    } else {
	if { $bName(pressure) } {
	    .panel.pressure configure -state disabled
	}
	if { $bName(calibrate) } {
	    .panel.calibrate configure -state disabled
	}
	if { $bName(button) } {
	    .panel.button configure -state disabled
	}
	if { $bName(mapping) } {
	    .panel.mapping configure -state disabled
	}
	if { $bName(advanced) } {
	    .panel.advanced configure -state disabled
	}
    }
}

# Retrieve the option numbered $i from the given device $dev.
# To get this method to do anything useful, you need to store the options
# you want in the global Option array and then pass the _number_ of options
# you want into here. The result is then in the global getOption(device
# name, option name). e.g. if you need options Foo and Bar, you'd do
#	set Option(1) "Foo"
#	set Option(2) "Bar"
#	updateDeviceOptionProc $device $2
#	set foo_value $getOption($device,Foo)
#
# I'm not kidding.
proc updateDeviceOptionProc { dev i } {
    global getOption getOptionDefault Option oldKeys modeToggleB
    global displayToggleB ignoreButton keystrokeB
    global numStrips numControls numButton spName

    for { set j 1 } { $j < [ expr $i+1 ] } { incr j 1 } {
	# not all properties exist on all devices, skip the missing ones
	if { [ catch { set value [ exec xsetwacom get "$dev" $Option($j) ] } ] } {
	    continue
	}

	# NumScreens is special
	if { ![ string compare -nocase -length 10 $Option($j) "NumScreen" ] } {
	    set getOption($dev,NumScreen) [ getNumScreen $dev ]

	# PressCurve is special, returns 4 values
	} elseif { ![ string compare -nocase -length 10 $Option($j) "PressCurve" ] } {
	    # Wacomcpl doesn't export the pressure curve as curve but merely
	    # as a setting between 1 and 7 that maps into a set of ranges.
	    # the default value for this is 4 (0 0 100 100).

	    # ignore non-existing properties
	    if { [ llength $value ] == 0 } {
		# 0 is an invalid value for pressure curve, we use it in the
		# GUI
		set getOption($dev,PressCurve) 0
		set getOptionDefault($dev,PressCurve) 0
		continue
	    }

	    set getOption($dev,PressCurve) 4
	    set getOptionDefault($dev,PressCurve) 4

	    # PressCurve returns a space-separated list of the 4 values.
            # linuxwacom's xsetwacom returned one value, shifted into a
            # 4-byte value like this: x0 | y0 | x1 | y1
            # now we just take x0 and y0
	    set p0 [ expr ( [ lindex $value 0 ] ) & 0xFF ]
	    set p1 [ expr ( [ lindex $value 1 ] ) & 0xFF ]
	    if { ($p1 > 5) && ($p1 < 35) } {
		set getOption($dev,PressCurve) 3
	    }
	    if { ($p1 >= 35) && ($p1 < 65) } {
		set getOption($dev,PressCurve) 2
	    }
	    if { ($p1 >= 65) && ($p1 < 95) } {
		set getOption($dev,PressCurve) 1
	    }
	    if { ($p0 > 5) && ($p0 < 35) } {
		set getOption($dev,PressCurve) 5
	    }
	    if { ($p0 >= 35) && ($p0 < 65) } {
		set getOption($dev,PressCurve) 6
	    }
	    if { ($p0 >= 65) && ($p0 < 95) } {
		set getOption($dev,PressCurve) 7
	    }
	} else {
	    set match 0
	    # are they button/wheel/ring/strip?
	    for { set k 1 } { $k <= $numControls } { incr k 1 } {
		if { ![string compare -nocase $Option($j) $spName($k)] } {
		    set match 1
		    break
		}
	    }
	    set getOption($dev,$Option($j)) $value
	    set getOptionDefault($dev,$Option($j)) $value
	    if { $match } {
		if { ($value > $ignoreButton) || ($value == 0) } {
		    if { ![string compare -nocase -length 4 CORE $value ]  ||
			 ![string compare -nocase -length 3 KEY $value ] ||
			 ![string compare -nocase -length 6 BUTTON $value ] } {
			set getOption($dev,$Option($j)) $keystrokeB
			set oldKeys($Option($j)) $value
		    }
		    if { ![string compare -nocase -length 10 MODETOGGLE $value ] } {
			set getOption($dev,$Option($j)) $modeToggleB
		    }
		    if { ![string compare -nocase -length 13 DISPLAYTOGGLE $value ] } {
			set getOption($dev,$Option($j)) $displayToggleB
		    }
		    if { $value == 0 } {
			set getOption($dev,$Option($j)) $ignoreButton
		    }
		}
	    }
	}
    }
}

# Create the device panel, based on the model some buttons are enabled,
# disabled. The 0 1 0 0 etc decides which buttons are to be disabled.
proc CreateDevicePanel { type model } {
    global hasPad isLCD hasTouch hasGesture

    set type [ string tolower $type ]

    if { ![ string compare $type "pad" ] } {
	if { $hasPad($model) } {
	    createPanel 0 1 0 0
	}
    } elseif { ![ string compare $type "touch" ] } {
	if { $hasTouch($model) } {
	    if { $hasGesture($model) } {
		createPanel 1 1 0 1
	    } else {
		createPanel 1 0 0 1
	    }
	}
    } elseif { $isLCD($model) } { 
	if { ![ string compare $type "stylus" ] } {
	    createPanel 1 1 0 1
	} elseif { [ string compare $type "cursor" ] } {
	    createPanel 1 1 0 0
	}
    } elseif { [ string compare $type "cursor" ] } {
	createPanel 1 1 1 0
    } else {
	createPanel 0 1 1 0
    }
}

# Callback for the device list on the left panel
proc updateDevice {} {
    global device orig_device getDeviceModel

    set olddev $device
    set device [ .workingDev.list get anchor ]
    if { $device != $olddev && $olddev != ""} {
	# convert underscores back to spaces for wacomxi.c
   	regsub -all {_} $olddev " " orig_device

 	# Clear old state related to preceding device
	wacomxi::bindevent . $orig_device <ButtonPress> ""
	wacomxi::bindevent . $orig_device <ButtonRelease> ""
    }

    if { $device != ""} {
	#
	# Update the entry indicator
	#
	.workingDev.label configure -text $device
	set model $getDeviceModel($device,model)
	set type $getDeviceModel($device,type)
	destroy .panel
	CreateDevicePanel $type $model
    } else {
	#
	# Update the entry indicator
	#
	.workingDev.label configure -text $device
    }
}

proc createDeviceList {} {
    global getDeviceModel deviceList

    set infoString [exec xsetwacom list]
    set deviceList ""

    set index [ string first "\n" $infoString ]
    set dev [ string range $infoString 0 $index ]


    while { $dev != "" } {
	set type [ lindex $dev end ]
	set dev [ lrange $dev 0 end-1 ]

	if { [catch { exec xsetwacom get "$dev" TabletID } model] == 0 } {
	    set getDeviceModel($dev,type) $type
	    set getDeviceModel($dev,model) $model
	    set deviceList "$deviceList \"$dev\""
	    set index [ expr $index+1 ]
	}

	set infoString [ string range $infoString $index end ]
	set index [ string first "\n" $infoString ]
	if { $index == -1 } { set index [ string length $infoString ] }
	set dev [ string range $infoString 0 $index ]
    }

    foreach dev $deviceList {
	# initial related erasers for styli
	set getDeviceModel($dev,eraser) ""
    }

    foreach dev $deviceList {
	set type $getDeviceModel($dev,type)
	if { ![string compare -nocase $type "eraser"] } {
	    set model $getDeviceModel($dev,model)
	    foreach dev1 $deviceList {
		set type1 $getDeviceModel($dev1,type)
		set model1 $getDeviceModel($dev1,model)
		if { ( $model == $model1 ) &&
			 ![string compare -nocase $type1 "stylus"] } {
		    set getDeviceModel($dev1,eraser) $dev
		}
	    }
	}
    }
}

proc createDeviceListPanel {} {
    global deviceList

    frame .workingDev
    label .workingDev.title -text "Select the Device:"
    label .workingDev.label -background gray
    listbox .workingDev.list -width 40 -height 12 \
	    -yscrollcommand ".workingDev.sb set"
    scrollbar .workingDev.sb -width 10 \
	    -command ".workingDev.list yview"
    grid .workingDev.title -row 0 -column 0 -columnspan 8 -sticky we
    grid .workingDev.label -row 1 -column 0 -columnspan 8 -sticky we
    grid .workingDev.list -row 2 -column 0
    grid .workingDev.sb -row 2 -sticky nse

    createDeviceList
    foreach dev $deviceList {
	.workingDev.list insert end $dev
	createScreenList $dev
    }
    bind .workingDev.list <ButtonRelease-1> updateDevice
}

# NumScreens was an exported property by the wacom driver. Values were
# ScreenCount(3) for non-NVIDIA TwinView, and 2 for NV-TV. We can get
# that through other means.
# I don't know how to get the ScreenCount with Tk though, so we parse
# the xdpyinfo output. Note that this duplicates the linuxwacom bug - on
# RandR 1.2, screenInfo.numScreens is always 1 though there may be two
# phys. monitors.
proc getNumScreen { dev } {
    set num_screen [exec xdpyinfo | grep "number of screens" | sed -e "s/number of screens:\[ \]\*//"]

    if { $num_screen == 1 } {
	set twinview [ exec xsetwacom get "$dev" TwinView ]
	if { $twinview != "none" } {
	    set num_screen 2
	}
    }

    return $num_screen
}

proc createScreenList { dev } {
    global numScreens currentScreen getScreenInfo

    set num_screen [ getNumScreen $dev ]

    set numScreens($dev) $num_screen
    if { [ catch {
	for { set i 0 } {$i < $numScreens($dev)} { incr i 1 } {
	    set s1 [ exec xsetwacom get "$dev" SBottomX$i ]
	    set s2 [ exec xsetwacom get "$dev" SBottomY$i ]
	    set s3 [ exec xsetwacom get "$dev" STopX$i ]
	    set s4 [ exec xsetwacom get "$dev" STopY$i ]
	}
	set getScreenInfo($dev,Screen$i) "$s1 $s2 $s3 $s4"
    } msg ] } {
	# if the above fails, the driver only sees one screen but we
	# pretend to have 2 (NVIDIA TwinView). The above fails because
	# "propert offset doesn't exist" so we have to fake the actual
	# values by querying TVResolution0/1 that was hopefully magically
	# set by the user beforehand...
	set tvres [ exec xsetwacom get "$dev" "TVResolution" ]
	set s0x [ lindex $tvres 0 ]
	set s0y [ lindex $tvres 1 ]
	set s1x [ lindex $tvres 2 ]
	set s1y [ lindex $tvres 3 ]

	if { $s0x == 0 && $s0y == 0 && $s1x == 0 && $s1y == 0 } {
	    messageWindow "Error" "\n\n Device $dev configured \n\
			  for TwinView but option TVResolution \n\
			  is not set.\n\
			  Please set TVResolution and try again."
			  exit 1
	}

	# getScreenInfo for some reason stores as bottom x/y top x/y instead
	# of the other way round.  Usage of s() is top x/y bottom x/y though.
	for { set i 0 } { $i < 8 } { incr i 1 } {
	    set s($i) 0
	}

	set tv_config [ exec xsetwacom get "$dev" "TwinView" ]

	switch $tv_config {
	    "normal" { return }
	    "horizontal" {
		set s(2) $s0x
		set s(3) $s0y
		set s(4) [ expr $s0x ]
		set s(5) 0
		set s(6) [ expr $s0x + $s1x ]
		set s(7) $s1y
	    }
	    "leftof" {
		set s(6) $s0x
		set s(7) $s0y
		set s(0) [ expr $s0x ]
		set s(1) 0
		set s(2) [ expr $s0x + $s1x ]
		set s(3) $s1y
	    }
	    "vertical" {
		set s(2) $s0x
		set s(3) $s0y
		set s(4) 0
		set s(5) [ expr $s0y ]
		set s(6) [ expr $s0x ]
		set s(7) [ expr $s0y + $s1y ]
	    }
	    "belowof" {
		set s(6) $s0x
		set s(7) $s0y
		set s(0) 0
		set s(1) [ expr $s0y ]
		set s(2) [ expr $s0x ]
		set s(3) [ expr $s0y + $s1y ]
	    }
	}

	set getScreenInfo($dev,Screen0) "$s(2) $s(3) $s(0) $s(1)"
	set getScreenInfo($dev,Screen1) "$s(6) $s(7) $s(4) $s(5)"
    }
}

# Callback for "Close" button on screen list window
proc screenCancel {} {
    global calibration_sequence_only

    destroy .topleft .bottomright
    if { $calibration_sequence_only == 0 } {
	closeTabWindow
	destroy .screen
    } else {
	exit 0
    }
}

# This one actually does what it says on the box.
proc displayScreenList { dev } {
    global numScreens currentScreen calibration_sequence_only

    if {  $numScreens($dev) <= 1  } {
	return
    }

    if { $calibration_sequence_only == 0} {
	toplevel .screen
	wm title .screen "Screen List Window"
	wm transient .screen .
	wm geometry .screen =250x200
	wm state .screen withdraw
	button .screen.cancel -text "Close" -command screenCancel
	grid .screen.cancel -row 10
	frame .screen.list
	label .screen.list.title -text "Select the Screen:"
	label .screen.list.label -background gray
	listbox .screen.list.list -width 12 -height 5 -yscrollcommand ".screen.list.sb set"
	scrollbar .screen.list.sb -width 10 -command ".screen.list yview"
	grid .screen.list.title -row 2 -column 0 -columnspan 3 -sticky we
	grid .screen.list.label -row 3 -column 0 -columnspan 3 -sticky we
	grid .screen.list.list -row 4 -column 0
	grid .screen.list.sb -row 4 -column 1 -sticky nse
	for { set i 0 } { $i < $numScreens($dev) } { incr i } {
	    .screen.list.list insert end "Screen$i"
        }
	bind .screen.list.list <ButtonRelease-1> updateScreenList
	pack .screen.list .screen.cancel
    } else {
	button .cancel -text "Close" -command screenCancel
	grid .cancel -row 10
	frame .list
	label .list.title -text "Select the Screen:"
	label .list.label -background gray
	listbox .list.list -width 12 -height 5 -yscrollcommand ".list.sb set"
	scrollbar .list.sb -width 10 -command ".list yview"
	grid .list.title -row 2 -column 0 -columnspan 3 -sticky we
	grid .list.label -row 3 -column 0 -columnspan 3 -sticky we
	grid .list.list -row 4 -column 0
	grid .list.sb -row 4 -column 1 -sticky nse
	for { set i 0 } { $i < $numScreens($dev) } { incr i } {
	    .list.list insert end "Screen$i"
	}
	bind .list.list <ButtonRelease-1> updateScreenList
	pack .list .cancel
    }
}

# Callback for "Save" button on Button config window.
proc updateButton {} {
    global device getDeviceModel sm getOption spName isLCD
    global dm currentW oldKeys numButton cKeys numStrips startS
    global modeToggleB ignoreButton keystrokeB
    global displayToggleB

    set type $getDeviceModel($device,type)
    set model $getDeviceModel($device,model)

    for { set i 1 } { $i <= [ expr ($numButton + $numStrips)] } { incr i 1 } {
	set k $i
	if { $i > $numButton } {
	    set k [ expr ($startS-1+$i) ]
	}
	switch [ $currentW.f.$i cget -text ] {
	    "Left"
		{ set j 1 
		  set v "1" }
	    "Middle"
		{ set j 2 
		 set v "2" }
	    "Right"
		{ set j 3 
		 set v "3" }
	    "Fourth"
		{ set j 4 
		 set v "4" }
	    "Fifth"
		{ set j 5 
		 set v "5" }
	    "Mode Toggle"
		{ set j $modeToggleB
		 set v "ModeToggle 1" }
	    "Display Toggle"
		{ set j $displayToggleB
		 set v "DisplayToggle 1" }
	    "KeyStroke"
		{ set j $keystrokeB 
		 if { $cKeys($spName($k)) == "" } {
		     set v $oldKeys($spName($k))
		 } else {
		    set v $cKeys($spName($k)) 
		    set oldKeys($spName($k)) $cKeys($spName($k))
		 } }
	    "Ignore"
		{ set j $ignoreButton
		 set v "0" }
	}
	set getOption($device,$spName($k)) $j
	updateWacomrc $device $spName($k) $v

	# reset key strokes 
	if { $j != $keystrokeB } {
	    set $oldKeys($spName($k)) ""
	    set cKeys($spName($k)) ""
	}
    }

    if { !$isLCD($model) && [string compare -nocase $type "pad"] } { 
	set mode [ $currentW.f.mode cget -text ]
	updateWacomrc $device mode $mode
	if { $mode == $dm(1) } {
	    set getOption($device,Mode) 0
	} else {
	    set getOption($device,Mode) 1
	}
    }

    if { ![ string compare -nocase $type "stylus" ] } {
	    set smode [ $currentW.f.smode cget -text ]
	    if { $smode == $sm(1) } {
		updateWacomrc $device TPCButton off
		set getOption($device,TPCButton) 0
	    } else {
		updateWacomrc $device TPCButton on
		set getOption($device,TPCButton) 1
	    }
    }
    closeSubWindow
}

# Callback for "Reset" button on the buttons window. Resets the buttons to
# their previous state.
proc defaultButton {} {
    global db db1 db2 db3 db4 db5 db6 db7 db8 db9 db10
    global db11 db12 db13 db14 isLCD
    global dm sm dmv smv numButton startS numStrips spName
    global device getOptionDefault getDeviceModel
    global ignoreButton keystrokeB modeToggleB displayToggleB

    for { set i 1 } { $i <= $numButton } { incr i 1 } {
	set btn $getOptionDefault($device,$spName($i))
	if { $btn == 0 } { set btn $ignoreButton }
	if { ![string compare -nocase -length 4 CORE $btn ]  ||
		    ![string compare -nocase -length 3 KEY $btn ] ||
		    ![string compare -nocase -length 6 BUTTON $btn ] } {
	    set btn $keystrokeB
	}
	if { ![string compare -nocase -length 10 MODETOGGLE $btn ] } {
	    set btn $modeToggleB
	}
	if { ![string compare -nocase -length 13 DISPLAYTOGGLE $btn ] } {
	    set btn $displayToggleB
	}
	set db$i $db($btn)
    }

    if { $startS } {
	for { set i $startS } { $i <= [ expr ($startS+$numStrips-1) ] } { incr i 1 } {
	    set k [ expr ($numButton+$i-$startS+1) ]
	    set db$k $db($getOptionDefault($device,$spName($k)))
	}
    }

    set model $getDeviceModel($device,model)
    set type $getDeviceModel($device,type)

    if { !$isLCD($model) && [ string compare -nocase $type "pad" ] } { 
	# dmv is the return value of the tk_optionMenu that displays the
	# mode options. yes, indeed. whoop. dee. doo.
	set mode $getOptionDefault($device,Mode)
	if { $mode == "Absolute" } { set mode 1 }
	if { $mode == "Relative" } { set mode 0 }
	set dmv $dm([ expr $mode + 1 ])
    }

    if { ![ string compare -nocase $type "stylus" ] } {
	set tpcbutton $getOptionDefault($device,TPCButton)
	if { $tpcbutton == "on" } { set tpcbutton 1 }
	if { $tpcbutton == "off" } { set tpcbutton 0 }
	set smv $sm([ expr $tpcbutton + 1 ])
    }
}

# 'get' the number of buttons for the given tool type. Tool type is e.g.
# "eraser". Since the original authors seem to be unaware of how to return
# values, stuff is just packed into global arrays/variables.
proc getNumButton { type } {
    global device Option numButton getDeviceModel numPadButtons
    
    set Option(1) "Button1"
    set t 1
    if { [ string compare -nocase $type "eraser" ] } {
	set Option(2) "Button2"
	set Option(3) "Button3"
	set t [ expr ($t+2) ]
	if { [ string compare -nocase $type "stylus" ] } {
	    set Option(4) "Button4"
	    set Option(5) "Button5"
	    set t [ expr ($t+2) ]
	}
	if { ![ string compare -nocase $type "pad" ] } {
	    set model $getDeviceModel($device,model)
	    set t $numPadButtons($model)
	    for { set i 6 } { $i <= $t } { incr i 1 } {
		set Option($i) "Button$i"
	    }
	}
    }
    set numButton $t
}

# Initialize a few arrays that relate to the device's strips
proc setspName {} {
    global numButton spName startS device Option cKeys oldKeys
    global numPadRelW numPadStrips numPadRings getDeviceModel
    global numControls numStrips getOption

    for { set i 1 } { $i <= $numButton } { incr i 1 } {
	set spName($i) Button$i
    }

    set spName([expr ($numButton + 1) ]) "RelWUp"
    set spName([expr ($numButton + 2) ]) "RelWDn"
    set spName([expr ($numButton + 3) ]) "AbsWUp"
    set spName([expr ($numButton + 4) ]) "AbsWDn"
    set spName([expr ($numButton + 5) ]) "StripLUp"
    set spName([expr ($numButton + 6) ]) "StripLDn"
    set spName([expr ($numButton + 7) ]) "StripRUp"
    set spName([expr ($numButton + 8) ]) "StripRDn"

    set numControls [expr ($numButton+8)]
    for { set i 1 } { $i <= $numControls } { incr i 1 } {
	set Option($i) $spName($i)
    }

    # initial keys before we call updateDeviceOptionProc
    for { set i 1 } { $i <= $numControls } { incr i 1 } {
	set oldKeys($spName($i)) ""
	set cKeys($spName($i)) ""
    }

    updateDeviceOptionProc $device $numControls

    set s1 5
    set s2 8
    set model $getDeviceModel($device,model)
    if { $numPadRelW($model) } { # G4
	set s1 1
	set s2 2
    } elseif { $numPadStrips($model) == 1 } { # I3 4x5
	set s1 5
	set s2 6
    } elseif { $numPadRings($model) } { # Bamboo
	set s1 3
	set s2 4
    }
    set startS $s1
    set numStrips [expr ($s2 - $s1) + 1]

}

# Callback to set up the button window
proc initialButton {} {
    global device Option numButton getDeviceModel 
    global spName numStrips

    set type $getDeviceModel($device,type)
    set t $numButton

    if { [ string compare -nocase $type "pad" ] } {
	set t [expr ($t+1)]
	set Option($t) "Mode"
	set t [expr ($t+1)]
	set Option($t) "TPCButton"
    }
    updateDeviceOptionProc $device $t

    if { [ string compare -nocase $type "pad" ] } {
    	displayMode
    }

    # initial controls
    setspName

    for { set i 1 } { $i <= $numButton } { incr i 1 } {
	addMenu $i
    }

    # then touch strip
    if { ![ string compare -nocase $type "pad" ] } {
	expressKeys
    } else {
	set numStrips 0
    }
}

proc displayMode {} {
    global device getDeviceModel sm smv
    global dmv dm getOption currentW isLCD

    set model $getDeviceModel($device,model)
    set type $getDeviceModel($device,type)

    if { !$isLCD($model) && [ string compare -nocase $type "pad" ]  } {
	set mode $getOption($device,Mode)
	if { $mode == "Absolute" } { set mode 1 }
	if { $mode == "Relative" } { set mode 0 }

	set dmv $dm([ expr $mode+1 ])
	tk_optionMenu $currentW.f.mode dmv $dm(1) $dm(2)
	label $currentW.f.modeL -text "Positioning Mode: "
	grid $currentW.f.mode -row 4 -column 1
	grid $currentW.f.modeL -row 4 -column 0
    }

    if { ![ string compare -nocase $type "stylus" ] } {
	set tpcbutton $getOption($device,TPCButton)
	if { $tpcbutton == "on" } { set tpcbutton 1 }
	if { $tpcbutton == "off" } { set tpcbutton 0 }
	set smv $sm([ expr $tpcbutton + 1 ])
	tk_optionMenu $currentW.f.smode smv $sm(1) $sm(2)
	label $currentW.f.smodeL -text "Side Switch Mode: "
	grid $currentW.f.smode -row 4 -column 4 
	grid $currentW.f.smodeL -row 4 -column 3
    }
}

# Sets up a subwindow. This function is used as the -command argument on the
# button creation, so tcl calls it exactly as specified in the command
# string.
# Arguments are three functions:
# 	okF is bound to the Save button
# 	deF (if nonzero) is bound to the Reset button
# 	initial is called from displaySubWindow to setup the window
# winID: the index into the global wNames() that contains the window
# 	  title.
# cbutton: if set, the window seems to be the keystroke window, otherwise
# 	   it's the normal window. also copied into the global currentb
# 	   variable
# kcurrent: just copied into the global currentk variable
proc displaySubWindow { okF deF initial winID cbutton kcurrent} {
    global currentb currentW wName currentk

    set currentb $cbutton
    set currentk $kcurrent
    if { $cbutton } {
	set currentW .keystrokes
   } else {
	set currentW .allothers
    }
    toplevel $currentW
    wm geometry $currentW =650x425
    wm title $currentW $wName($winID)
    wm transient $currentW .
    wm state $currentW normal
    frame $currentW.f
    button $currentW.f.ok -text "Save" -command $okF
    button $currentW.f.cancel -text "Cancel" -command closeSubWindow

    set columnN 0
    if { $deF != 0 } {
	button $currentW.f.default -text "Reset" -command $deF
	grid $currentW.f.default -row 8 -column 0 -columnspan 3 -padx 10 -pady 10
	#
	# Suppress tags on listboxes to prevent changing the
	# device and disable other controls
	bindtags .workingDev.list .
	set columnN 4
    }

    disableButtons
    $initial

    grid $currentW.f -row 0 -column 0 -sticky nw
    grid $currentW.f.ok -row 8 -column [expr ($columnN + 4)] -columnspan 3 -padx 10 -pady 10
    grid $currentW.f.cancel -row 8 -column $columnN -columnspan 3 -padx 10 -pady 10
}

proc closeSubWindow {} {
    global currentW
    destroy $currentW
    closeTabWindow
}

proc closeTabWindow {} {
    global bName workingTags currentW numButton 
    global currentb cKeys spName numStrips 

    if { $bName(keystroke) } {
	set bName(keystroke) 0
	set currentW .allothers
	for { set i 1 } { $i <= [ expr ($numButton+$numStrips) ] } { incr i 1 } {
	    $currentW.f.$i configure -state normal
	}
	$currentW.f.default configure -state normal
	$currentW.f.ok configure -state normal
	$currentW.f.cancel configure -state normal
	set currentb 0
    } else {
	if { $bName(pressure) } {
	    .panel.pressure configure -state normal
	}
	if { $bName(calibrate) } {
	    .panel.calibrate configure -state normal
	}
	if { $bName(button) } {
	    .panel.button configure -state normal
	}
	if { $bName(mapping) } {
	    .panel.mapping configure -state normal
	}
	if { $bName(advanced) } {
	    .panel.advanced configure -state normal
	}
	for { set i 1 } { $i <= [ expr ($numButton+$numStrips) ] } { incr i 1 } {
	    set cKeys($spName($i)) ""
	}
	bindtags .workingDev.list $workingTags
    }
}

proc expressKeys { } {
    global db db1 db2 db3 db4 db5 db6 db7 db8 db9 db10
    global db11 db12 db13 db14 startS 
    global currentW device numButton spName numStrips
    global getDeviceModel cKeys oldKeys getOption
    global displayToggleB ignoreButton keystrokeB
    global numPadRings numPadStrips numPadAbsW numPadRelW

    set name(1) "Wheel Up"
    set name(2) "Wheel Down"
    set name(3) "Ring Anticlockwise"
    set name(4) "Ring Clockwise"
    set name(5) "Left Strip Up"
    set name(6) "Left Strip Down"
    set name(7) "Right Strip Up"
    set name(8) "Right Strip Down"

    set s2 [expr ($startS + $numStrips) - 1]
    for { set i $startS } { $i <= $s2 } { incr i 1 } {
	set cur [ expr ($numButton + $i - $startS + 1) ]
	set curOption [ expr ($numButton + $i) ]
	set opt $getOption($device,$spName($curOption))
	if { $opt == "" } { set opt $ignoreButton }

	set db$cur $db($opt) 
	#reset keys
	if { $opt != $keystrokeB } {
	    set cKeys($spName($curOption)) ""
	    set oldKeys($spName($curOption)) ""
	}
	set bmenu [ tk_optionMenu $currentW.f.$cur db$cur $db(1) $db(2) \
	    $db(3) $db(4) $db(5) $db($ignoreButton) ]
	$bmenu insert 7 radiobutton -label "$db($keystrokeB)" \
	    -variable menvar -command "displaySubWindow \ updateKeys 0 initialKeys 5 $cur $curOption"

	label $currentW.f.name$cur -text "$name($i): "
	set t2 [expr ($numButton+1)/2+1]
	set t1 0
	if { [expr ($s2 - $startS) > 1] } {
	    if { $i == [expr ($startS+1)] || \
		    $i == [expr ($startS+3)] } { 
		set t2 [expr ($numButton+1)/2+2]
	    }
	    if { [expr ($i - $startS) > 1] } {
		set t1 2
	    }
	} else {
	    if { [expr ($i - $startS) > 0] } {
		set t1 2
	    }
	}
	grid $currentW.f.$cur -row $t2 -column [expr $t1+1] 
	grid $currentW.f.name$cur -row $t2 -column $t1
    }
}

proc addMenu { curb } {
    global db db1 db2 db3 db4 db5 db6 db7 db8 db9 db10 
    global db11 db12 db13 db14 currentW device spName
    global getOption getDeviceModel cKeys oldKeys 
    global keystrokeB
    global displayToggleB ignoreButton modeToggleB

    set model $getDeviceModel($device,model)
    set type $getDeviceModel($device,type)

    set opt $getOption($device,$spName($curb))
    if { $opt == "" } { set opt $curb }
    set db$curb $db($opt) 
    #reset keys
    if { $opt != $keystrokeB } {
	set cKeys($spName($curb)) ""
	set oldKeys($spName($curb)) ""
    }

    if { [string compare -nocase $type "pad"] } {
	set bmenu [ tk_optionMenu $currentW.f.$curb db$curb $db(1) $db(2) \
		$db(3) $db(4) $db(5) $db($modeToggleB) \
		$db($displayToggleB) $db($ignoreButton) ]
	set i 9
    } else {
	set bmenu [ tk_optionMenu $currentW.f.$curb db$curb $db(1) $db(2) \
		$db(3) $db(4) $db(5) $db($displayToggleB) \
		$db($ignoreButton) ]
	set i 8
    }

    set i [expr ($i+1)]
    $bmenu insert $i radiobutton -label "$db($keystrokeB)" \
	-variable menvar -command "displaySubWindow \
	updateKeys 0 initialKeys 5 $curb $curb"

    label $currentW.f.name$curb -text "Button $curb: "
    set t2 [expr ($curb-1)/2]
    if { [expr ($curb/2)] == [expr ($curb+1)/2] } {
	set t1 2
    } else {
	set t1 0
    }

    grid $currentW.f.$curb -row $t2 -column [expr ($t1+1)]
    grid $currentW.f.name$curb -row $t2 -column $t1
}

# sets up the mapping window to change the top/bottom x/y parameters and the
# device mode
proc initialT {} {
    global device getDeviceModel currentW
    global getOption getOptionDefault Option

    set Option(1) "TopX"
    set Option(2) "TopY"
    set Option(3) "BottomX"
    set Option(4) "BottomY"
    set Option(5) "Mode"

    updateDeviceOptionProc $device 7

    set mode $getOption($device,Mode)
    frame $currentW.f.group -bd 10 -bg beige -width 150 -height 150

    if { $mode == 1 || $mode == "Absolute" } {
	label $currentW.f.groupL -text "Mapping: "
	for { set i 1 } { $i < 5 } { incr i 1 } {
	    addMapScale $i
	}
	grid $currentW.f.group -row 0 -column 5
	grid $currentW.f.groupL -row 0 -column 2
    }

    # FIXME: speed levels were removed, should be controlled by the X server
    # properties.

}

# map the sliders to the matching TopX/BottomX, etc option
proc addMapScale { t } {
    global getOption Option getOptionDefault device currentW

    label $currentW.f.group.l$t -text "$Option($t): "
    grid $currentW.f.group.l$t -row [ expr $t-1 ] -column 6
    set j $t
    if { $j < 3 } { set j [expr $j+2] }
    scale $currentW.f.group.scale$t -orient horizontal -length 200 \
		-from -200 -to [expr $getOptionDefault($device,$Option($j))+200]
    grid $currentW.f.group.scale$t -row [ expr $t-1 ] -column 8
    $currentW.f.group.scale$t set $getOption($device,$Option($t))
}

# Callback for mapping (topX/BottomX,..) and mode, except that topX etc is
# updated on-the-fly
proc updateT {} {
    global getOption getOptionDefault Option device currentW

    set mode $getOption($device,Mode)
    if { $mode == 1 || $mode == "Absolute" } {
	for { set i 1 } { $i < 5 } { incr i 1 } {
	    set value [ $currentW.f.group.scale$i get ]
	    updateWacomrc $device $Option($i) $value
	    set getOption($device,$Option($i)) $value
	}
    }

    closeSubWindow
}

# Mode "reset" callback
proc defaultT {} {
    global getOption getOptionDefault Option device currentW

    set mode $getOption($device,Mode)
    if { $mode == 1 || $mode == "Absolute" } {
	for { set i 1 } { $i < 5 } { incr i 1 } {
	    $currentW.f.group.scale$i set $getOptionDefault($device,$Option($i))
	}
    }
}

# Set up the "Feel" window
proc initialTip {} {
    global device getDeviceModel getOption Option currentW

    set Option(1) "PressCurve"
    set Option(2) "ClickForce"
    set Option(3) "RawSample"
    set Option(4) "Suppress"
    updateDeviceOptionProc $device 4

    frame $currentW.f.group -bd 10 -bg beige -width 150 -height 150

    # 0 is a special value, see updateDeviceOptionProc.
    # Only display Tip sensitivity for those that have PressCurve
    if { $getOption($device,PressCurve) != 0 } {
	label $currentW.f.group.groupl1 -text "Tip Sensitivity: "
	grid $currentW.f.group.groupl1 -row 0 -column 0
	scale $currentW.f.group.scale1 -orient horizontal -length 100 \
	-from 1 -to 7
	grid $currentW.f.group.scale1 -row 0 -column 8
	set curve $getOption($device,PressCurve)

	$currentW.f.group.scale1 set $curve
	label $currentW.f.group.l2 -text "Soft"
	grid $currentW.f.group.l2 -row 1 -column 7
	label $currentW.f.group.l3 -text "Firm"
	grid $currentW.f.group.l3 -row 1 -column 9
    }

    label $currentW.f.group.groupl2 -text "Click Force: "
    grid $currentW.f.group.groupl2 -row 3 -column 0
    label $currentW.f.group.l4 -text "Low"
    grid $currentW.f.group.l4 -row 4 -column 7
    label $currentW.f.group.l5 -text "High"
    grid $currentW.f.group.l5 -row 4 -column 9
    scale $currentW.f.group.scale2 -orient horizontal -length 100 \
		-from 1 -to 21
    grid $currentW.f.group.scale2 -row 3 -column 8
    $currentW.f.group.scale2 set $getOption($device,ClickForce)

    label $currentW.f.group.groupl3 -text "Smoothness: "
    grid $currentW.f.group.groupl3 -row 5 -column 0
    label $currentW.f.group.l6 -text "Low"
    grid $currentW.f.group.l6 -row 6 -column 7
    label $currentW.f.group.l7 -text "High"
    grid $currentW.f.group.l7 -row 6 -column 9
    scale $currentW.f.group.scale3 -orient horizontal -length 100 \
               -from 1 -to 20
    grid $currentW.f.group.scale3 -row 5 -column 8
    $currentW.f.group.scale3 set $getOption($device,RawSample)

    label $currentW.f.group.groupl4 -text "Suppress Points: "
    grid $currentW.f.group.groupl4 -row 7 -column 0
    label $currentW.f.group.l8 -text "Low"
    grid $currentW.f.group.l8 -row 8 -column 7
    label $currentW.f.group.l9 -text "High"
    grid $currentW.f.group.l9 -row 8 -column 9
    scale $currentW.f.group.scale4 -orient horizontal -length 100 \
               -from 1 -to 20
    grid $currentW.f.group.scale4 -row 7 -column 8
    $currentW.f.group.scale4 set $getOption($device,Suppress)

    grid $currentW.f.group -row 0 -column 5 
}

# Callback for setting the "Feel" values.
proc updateTip {} {
    global device currentW getOption

    # 0 is a special value for PressCurve, see updateDeviceOptionProc
    # Map the numeric values of the selection to the actual 4-value pressure
    # curve
    if { $getOption($device,PressCurve) != 0 } {
	    switch [ $currentW.f.group.scale1 get ] {
		1
		{ set curve "0 75 25 100" }
		2
		{ set curve "0 50 50 100" }
		3
		{ set curve "0 25 75 100" }
		4
		{ set curve "0 0 100 100" }
		5
		{ set curve "25 0 100 75" }
		6
		{ set curve "50 0 100 50" }
		7
		{ set curve "75 0 100 25" }
	}
	updateWacomrc $device PressCurve $curve
	set getOption($device,PressCurve) $curve
    }
    updateWacomrc $device ClickForce [ $currentW.f.group.scale2 get ]
    set getOption($device,ClickForce) [ $currentW.f.group.scale2 get ]
    updateWacomrc $device RawSample [ $currentW.f.group.scale3 get ]
    set getOption($device,RawSample) [ $currentW.f.group.scale3 get ]
    updateWacomrc $device Suppress [ $currentW.f.group.scale4 get ]
    set getOption($device,Suppress) [ $currentW.f.group.scale4 get ]

    closeSubWindow
}

# Default "Feel" values
proc defaultTip {} {
    global currentW

    $currentW.f.group.scale1 set 4
    $currentW.f.group.scale2 set 6
}

# Initialize the TwinView/MM dialog box
proc initialSet {} {
    global device getDeviceModel getOption Option
    global tvd tvID snd currentW

    # used by both TwinView and non TwinView multimonitor setup
    label $currentW.f.sns -text "Display Mapping: "

    set Option(1) "NumScreen"
    set Option(2) "TwinView"
    set Option(3) "Screen_no"
    updateDeviceOptionProc $device 3

    set numS  $getOption($device,NumScreen)
    set tvID $getOption($device,TwinView)
    set snd $getOption($device,Screen_no)

    if { $snd == -1 || $snd == 255} {
	set snd "Desktop" 
    } else {
	set snd "Screen$snd"
    }

    if { $numS == 1 } {
	set sn0 "Desktop" 
	set sn1 "Screen0"
	set sn2 "Screen1"
	tk_optionMenu $currentW.f.snsmenu snd $sn0 $sn1 $sn2
    } else {
	set smenu [ tk_optionMenu $currentW.f.snsmenu snd "Desktop" ]
	pack $currentW.f.snsmenu -side left
	for { set i 0 } { $i < $numS } { incr i } {
	    $smenu insert $i radiobutton -label "Screen$i" \
		-variable smenvar -command \
		{ global smenvar; set snd $smenvar }
	}
    }

    grid $currentW.f.sns -row 5 -column 0 
    grid $currentW.f.snsmenu -row 5 -column 1
    if { $numS > 1 } {
	$currentW.f.snsmenu configure -state normal
    } else {
	$currentW.f.snsmenu configure -state disable 
    }

    label $currentW.f.tv -text "TwinView Setup: "
    set tv(0) "None"
    set tv(1) "Vertical"
    set tv(2) "Horizontal"
    set tv(3) "AboveOf"
    set tv(4) "LeftOf"

    for { set i 0 } { $i < 5 } { incr i 1 } {
	if { ![ string compare -nocase $tvID $tv($i) ] } {
	    set tvd $tv($i)
	    break
	}
    }

    tk_optionMenu $currentW.f.tvmenu tvd $tv(0) $tv(1) $tv(2) $tv(3) $tv(4)
    grid $currentW.f.tv -row 4 -column 0 
    grid $currentW.f.tvmenu -row 4 -column 1

    $currentW.f.tvmenu configure -state normal
    label $currentW.f.message1 -text "\nThe \"TwinView Setup\" option "
    label $currentW.f.message2 -text "is only for Nvidia graphic card."
    label $currentW.f.message3 -text "If your graphic card is not from Nvidia, "
    label $currentW.f.message4 -text "please leave the option as \"None\".\n"
 
    grid $currentW.f.message1 -row 0 -column 1
    grid $currentW.f.message2 -row 1 -column 1
    grid $currentW.f.message3 -row 2 -column 1
    grid $currentW.f.message4 -row 3 -column 1
}

# Guess the TwinView resolution from the xdpyinfo XINERAMA output, then load
# that into the device
proc guessTVResolution { device tv} {
    # xdpyinfo output is e.g. #head0: 1280x1024 @ 0,0
    # extract the resolution into a "1280 1024 0 0" string
    set regex { s/.* \([0-9]\+\)x\([0-9]\+\) @ \([0-9]\+\),\([0-9]\+\).*/\1 \2 \3 \4/ }

    set head0 [ exec xdpyinfo -ext XINERAMA | grep "head #0" | sed -e $regex ]
    set head1 [ exec xdpyinfo -ext XINERAMA | grep "head #1" | sed -e $regex ]

    catch {
	set tvres0 [split $head0 " "]
	set tvres1 [split $head1 " "]

	set w1 [ lindex $tvres0 0 ]
	set h1 [ lindex $tvres0 1 ]
	set w2 [ lindex $tvres1 0 ]
	set h2 [ lindex $tvres1 1 ]

	set off1 [ lindex $tvres0 2 ]
	set off2 [ lindex $tvres0 3 ]

	if { $off1 != 0 || $off2 != 0 } {
	    # first screen has non-zero offset, swap around
	    set tmp $w1
	    set w1 $w2
	    set w2 $tmp

	    set tmp $h1
	    set h1 $h2
	    set h2 $tmp
	}

	exec xsetwacom set "$device" TVResolution "$w1 $h1 $w2 $h2"
	updateWacomrcStylusAndEraser $device TVResolution "$w1 $h1 $w2 $h2"
    }
}

# Callback for saving TwinView/MM mappings
proc updateSet {} {
    global device currentW tvd snd tvID

    # See procCreateScreenList for comment to next line
    set numS [ getNumScreen $device ]

    if { $numS > 1 } {
	set sn [ $currentW.f.snsmenu cget -text ]
	if { ![ string compare $sn "Desktop" ] } {
	    set sn -1
	} else {
	    for { set i 0 } { $i < $numS } { incr i 1 } {
		if { $sn == "Screen$i" } {
		    set sn $i
		}
	    }
	}
	updateWacomrcStylusAndEraser $device Screen_No $sn
	set getOption($device,Screen_No) $sn
    }

    set tv [ $currentW.f.tvmenu cget -text ]
    if { ![ string compare $tv "None" ] } {
	set tv "none"
    }
    if { ![ string compare $tv "Horizontal" ] } {
	set tv "horizontal"
    }
    if { ![ string compare $tv "Vertical" ] } {
	set tv "vertical"
    }
    if { ![ string compare $tv "LeftOf" ] } {
	set tv "leftof"
    }
    if { ![ string compare $tv "AboveOf" ] } {
	set tv "aboveof"
    }

    if { $tv != $tvID } {
	updateWacomrcStylusAndEraser $device TwinView $tv
	set getOption($device,TwinView) $tv
	if { $tv != "none" } {
	    guessTVResolution $device $tv
	} else {
	    updateWacomrcStylusAndEraser $device TVResolution "0 0 0 0"
	}
    }

    closeSubWindow
}

# Default Twinview/MM values
proc defaultSet {} {
    global tvd snd

    set tvd "None"
    set snd "Desktop"
}

proc initialKeys {} {
    global device getDeviceModel getOption cKeys spName 
    global Option oldKeys currentW bName currentb currentk

    frame $currentW.f.panelt
    set bName(keystroke) 1
    text $currentW.f.panelt.input -width 40 -height 10 \
	-yscrollcommand "$currentW.f.panelt.srl_y set" 

    if { $cKeys($spName($currentk)) != "" } {
	$currentW.f.panelt.input insert end $cKeys($spName($currentk))
    } else {
	if { $oldKeys($spName($currentk)) != "" } {
	    $currentW.f.panelt.input insert end $oldKeys($spName($currentk))
	    set cKeys($spName($currentk)) $oldKeys($spName($currentk))
	}
    }
    scrollbar $currentW.f.panelt.srl_y -width 10 -command \
	"$currentW.f.panelt.input yview"
    label $currentW.f.panelt.title1 -text "Entered keystrokes (type the keys if they are not in the lists):"
    grid $currentW.f.panelt.title1 -row 0 -column 0 -sticky we
    grid $currentW.f.panelt.input -row 1 -column 0 -sticky we
    grid $currentW.f.panelt.srl_y -row 1 -sticky nse

    frame $currentW.f.panel
    set mod(1) "SHIFT"
    set mod(2) "CTRL"
    set mod(3) "ALT"
    set mod(4) "META"
    set mod(5) "SUPER"
    set mod(6) "HYPER"
    set mmenu [ tk_optionMenu $currentW.f.panel.modmenu mod0 "Modifiers" ]
    pack $currentW.f.panel.modmenu -side left
    label $currentW.f.panel.title2 -text "Select special keys below:"
    grid $currentW.f.panel.title2 -row 0 -column 2 
    grid $currentW.f.panel.modmenu -row 1 -column 2 
    $mmenu delete 0
    for { set i 1 } { $i <= 6 } { incr i 1 } {
	$mmenu insert $i radiobutton -label $mod($i) \
	-variable menvar -command \
        { $currentW.f.panelt.input insert end " "; \
	global menvar; \
	$currentW.f.panelt.input insert end $menvar; \
	$currentW.f.panelt.input insert end " " }
    }

    set fk(1) "F1"
    set fk(2) "F2"
    set fk(3) "F3"
    set fk(4) "F4"
    set fk(5) "F5"
    set fk(6) "F6"
    set fk(7) "F7"
    set fk(8) "F8"
    set fk(9) "F9"
    set fk(10) "F10"
    set fk(11) "F11"
    set fk(12) "F12"
    set fmenu [ tk_optionMenu $currentW.f.panel.fmenu fk0 "Function Keys" ]
    pack $currentW.f.panel.fmenu -side left
    grid $currentW.f.panel.fmenu -row 2 -column 2
    $fmenu delete 0
    for { set i 1 } { $i <= 12 } { incr i 1 } {
	$fmenu insert $i radiobutton -label $fk($i) \
	-variable fmenvar -command \
        { $currentW.f.panelt.input insert end " "; \
	global fmenvar; \
	$currentW.f.panelt.input insert end $fmenvar; \
	$currentW.f.panelt.input insert end " " }
    }

    set fs(1) "quotedbl"
    set fs(2) "Pause"
    set fs(3) "ScrollLock"
    set fs(4) "SysReq"
    set fs(5) "End"
    set fs(6) "Insert"
    set fs(7) "Delete"
    set fs(8) "Enter"
    set fs(9) "Home"
    set fs(10) "backslash"
    set fs(11) "break"
    set fs(12) "print"
    set fsmenu [ tk_optionMenu $currentW.f.panel.fsmenu fs0 "Special Keys 1" ]
    pack $currentW.f.panel.fsmenu -side left
    grid $currentW.f.panel.fsmenu -row 3 -column 2 
    $fsmenu delete 0
    for { set i 1 } { $i <= 12 } { incr i 1 } {
	$fsmenu insert $i radiobutton -label $fs($i) \
	-variable fsmenvar -command \
        { $currentW.f.panelt.input insert end " "; \
	global fsmenvar; \
	$currentW.f.panelt.input insert end $fsmenvar; \
	$currentW.f.panelt.input insert end " " }
    }

    set sk(1) "Esc"
    set sk(2) "Tab"
    set sk(3) "CapsLock"
    set sk(4) "PageUp"
    set sk(5) "PageDown"
    set sk(6) "Left"
    set sk(7) "Up"
    set sk(8) "Down"
    set sk(9) "Right"
    set sk(10) "space"
    set sk(11) "NumLock"
    set sk(12) "BackSpace"
    set skmenu [ tk_optionMenu $currentW.f.panel.skmenu sk0 "Special Keys 2" ]
    pack $currentW.f.panel.skmenu -side left
    grid $currentW.f.panel.skmenu -row 4 -column 2 
    $skmenu delete 0
    for { set i 1 } { $i <= 12 } { incr i 1 } {
	$skmenu insert $i radiobutton -label $sk($i) \
	-variable skmenvar -command \
        { $currentW.f.panelt.input insert end " "; \
	global skmenvar; \
	$currentW.f.panelt.input insert end $skmenvar; \
	$currentW.f.panelt.input insert end " " }
    }

    set kp(1) "PgUp"
    set kp(2) "PgDn"
    set kp(3) "KPLeft"
    set kp(4) "KPUp"
    set kp(5) "KPDown"
    set kp(6) "KPRight"
    set kp(7) "Plus"
    set kp(8) "Minus"
    set kp(9) "Divide"
    set kp(10) "Multiply"
    set kp(11) "KPEnd"
    set kp(12) "Ins"
    set kp(13) "Del"
    set kp(14) "KPEnter"
    set kp(15) "KPHome"
    set kpmenu [ tk_optionMenu $currentW.f.panel.kpmenu kp0 "KeyPad Keys" ]
    pack $currentW.f.panel.kpmenu -side left
    grid $currentW.f.panel.kpmenu -row 5 -column 2 
    $kpmenu delete 0
    for { set i 1 } { $i <= 15 } { incr i 1 } {
	$kpmenu insert $i radiobutton -label $kp($i) \
	-variable kpmenvar -command \
        { $currentW.f.panelt.input insert end " "; \
	global kpmenvar; \
	$currentW.f.panelt.input insert end $kpmenvar; \
	$currentW.f.panelt.input insert end " " }
    }

    grid $currentW.f.panelt -row 0 -column 0
    grid $currentW.f.panel -row 0 -column 2 
}

proc updateKeys {} {
    global device oldKeys currentb cKeys spName currentW
    global db db1 db2 db3 db4 db5 db6 db7 db8 db9 db10
    global db11 db12 db13 db14 currentk
    global ignoreButton keystrokeB

    set keys [ $currentW.f.panelt.input get 1.0 end ]

    # remove newline characters 
    set index [ string last "\n" $keys ]
    while { $index != -1 } {
	set keys [ string replace $keys $index [expr ($index+1) ] ]
	set index [ string last "\n" $keys ]
    }

    if { $oldKeys($spName($currentk)) != $keys } {
	if { [string compare -nocase -length 8 $keys "core key"] } {
	    set cKeys($spName($currentk)) "core key $keys"
	} else {
	    set cKeys($spName($currentk)) $keys
	}
    }

    if { $cKeys($spName($currentk)) != "" } {
	set db$currentb $db($keystrokeB)
    } else {
	set db$currentb $db($ignoreButton)
    }

    if { [ string length $cKeys($spName($currentk)) ] > 240 } {
	helpWindow "Help Window " \
		"\n\nYou have entered more 240 keys. \n\
		wacomcpl only accepts 240 keys. \n\
		Please reduce your keys. "
    } else {
	closeSubWindow
    }
}

proc touchState { theCButton } {
    global device touchButton getDeviceModel

    checkbutton $theCButton -text "Disable Touch" -anchor w \
	-variable touchButton -state normal -command switchTouch
}

proc gestureState { theCButton } {
    global device touchButton getDeviceModel hasGesture
    global gestureButton 

    # gesture supported and touch is on 
    if { [ exec xsetwacom get "$device" touch ] } {
	checkbutton $theCButton -text "Disable Touch Gesture" -anchor w \
	    -variable gestureButton -state normal -command switchGesture
    } else {
	checkbutton $theCButton -text "Disable Touch Gesture" -anchor w \
	    -variable gestureButton -state disable -command switchGesture
    }
}

proc switchTouch { } {
    global device touchButton gestureButton hasGesture getDeviceModel

    set model $getDeviceModel($device,model)
    if { $touchButton } {
	exec xsetwacom set "$device" touch 0
	updateWacomrc $device touch 0
	if { $hasGesture($model) } {
	    .panel.button configure -state disabled
	}
    } else {
	exec xsetwacom set "$device" touch 1
	updateWacomrc $device touch 1
	if { $hasGesture($model) } {
	    .panel.button configure -state normal
	}
   }
}

proc switchGesture { } {
    global device touchButton gestureButton

    if { $gestureButton } {
	exec xsetwacom set "$device" gesture 0
	updateWacomrc $device gesture 0
    } else {
	exec xsetwacom set "$device" gesture 1
	updateWacomrc $device gesture 1
   }
}

# the 4 parameters are booleans, specifying which buttons should be present.
proc createPanel { pressure button mapping calibrate } {
    global bName device getOption getDeviceModel currentb 
    global wName startS Option touchButton hasGesture gestureButton

    frame .panel
    set bName(pressure) $pressure
    set bName(button) $button
    set bName(mapping) $mapping
    set bName(calibrate) $calibrate
    set currentb 0
    set type $getDeviceModel($device,type)

    # buttons and expresskeys
    getNumButton $type
    setspName
    set SN [ exec xsetwacom get "$device" ToolSerial ]
    if { $SN && [string compare type "pad"] } {
	set hexSN [format %X $SN]
	label .panel.snt -text "Serial Number: "
	label .panel.sn -text "$hexSN"
	grid .panel.snt -row 0 -column 0 -columnspan 2 -padx 3 -pady 3
	grid .panel.sn -row 0 -column 3 -columnspan 2 -padx 3 -pady 3
    }

    if { $calibrate } {
	button .panel.calibrate -text "Calibration" \
	    -command Calibration -state normal
	grid .panel.calibrate -row 5 -column 3 -columnspan 2 -sticky news -padx 10
    }
    if { $pressure } {
	if { ![string compare $getDeviceModel($device,type) "touch"] } {
	    set touchButton 1
	    if { [exec xsetwacom get "$device" touch] } {
		set touchButton 0
	    }
	    touchState .panel.pressure 
	} else {
	    set wName(1) "Feel"
	    button .panel.pressure -text $wName(1) \
		-state normal -command "displaySubWindow \
	    updateTip defaultTip initialTip 1 0 0"
	}
	grid .panel.pressure -row 5 -column 0 -columnspan 2 -sticky news -padx 10
    }
    if { $button } {
	if { ![string compare $getDeviceModel($device,type) "touch"] } {
	    set model $getDeviceModel($device,model)
	    if { $hasGesture($model) } {
		set gestureButton 1
		if { [ exec xsetwacom get "$device" gesture ] } {
		    set gestureButton 0
		}
		gestureState .panel.button 
	    }
	} elseif { ![string compare $getDeviceModel($device,type) "pad"] } {
	    button .panel.button -text $wName(6) \
		-state normal -command "displaySubWindow \
		updateButton defaultButton initialButton 6 0 $startS"
	} else {
	    button .panel.button -text $wName(2) \
		-state normal -command "displaySubWindow \
		updateButton defaultButton initialButton 2 0 0"
	}
	grid .panel.button -row 6 -column 0 -columnspan 2 -sticky news -padx 10
    }
    if { $mapping } {
	button .panel.mapping -text $wName(3) \
	   -state normal -command "displaySubWindow \
	   updateT defaultT initialT 3 0 0"
	grid .panel.mapping -row 6 -column 3 -columnspan 2 -sticky news -padx 10
    }

    set bName(advanced) 0
    if { [string compare $getDeviceModel($device,type) "touch"] &&
	    [string compare $getDeviceModel($device,type) "pad"] } { #not a pad or touch
	set Option(1) "Mode"
	updateDeviceOptionProc $device 1
	set mode $getOption($device,Mode)
	if { ($pressure || $mapping) && ($mode == "Absolute" || $mode == "1") } {  #and in absolute mode
	    set bName(advanced) 1
	    button .panel.advanced -text $wName(4) \
		-state normal -command "displaySubWindow \
		updateSet defaultSet initialSet 4 0 0"
	    grid .panel.advanced -row 9 -column 2 -columnspan 2 -sticky news -padx 10
	}
    }

    grid .panel -row 0 -column 1 -columnspan 8 -sticky news -padx 10 -pady 40
}

# Initialize a few arrays based on the fixed model numbers so that later we
# can call isLCD(model number) and get the right value
proc updateModelInfo { } {
    global isLCD numPadButtons numPadRings hasPad hasTouch
    global numPadStrips numPadAbsW numPadRelW 
    global maxNumTablets hasGesture 

    for { set i 0 } { $i <= $maxNumTablets } { incr i 1 } {
	set isLCD($i) 0 
	set numPadButtons($i) 0
	set numPadRings($i) 0
	set numPadStrips($i) 0
	set numPadAbsW($i) 0
	set numPadRelW($i) 0
	set hasPad($i) 0
	set hasTouch($i) 0
	set hasGesture($i) 0
    }
    #PL
    for { set i 48} { $i <= 63 } { incr i 1 } {
	set isLCD($i) 1 
    }
    #TabletPC
    for { set i 144 } { $i <= 159 } { incr i 1 } {
	set isLCD($i) 1 
    }
    #TabletPC with 2FG touch
    set isLCD(226) 1 
    set isLCD(227) 1 

    #Cintiq
    for { set i 197 } { $i <= 206 } { incr i 1 } {
	set isLCD($i) 1 
    }

    # G4
    set numPadButtons(21) 2
    set numPadButtons(22) 2
    set numPadRelW(21) 1
    set numPadRelW(22) 1
    # Bamboo Fun
    set numPadButtons(23) 4 
    set numPadRings(23) 1
    set numPadButtons(24) 4
    set numPadRings(24) 1
    #Cintiq 21UX
    set numPadButtons(63) 8
    set numPadStrips(63) 2

    #Cintiq 21UX2
    set numPadButtons(204) 18
    set numPadStrips(204) 2

    # Bamboo
    for { set i 101 } { $i <= 105 } { incr i 1 } {
	set numPadButtons($i) 4
	set numPadRings($i) 1
   }

    # Bamboo Pen and Touch
    for { set i 208 } { $i < 212 } { incr i 1 } {
        set hasPad($i) 1
        set numPadButtons($i) 4
	set hasTouch($i) 1
	set hasGesture($i) 1
    }

    # I3
    set numPadButtons(176) 4
    set numPadStrips(176) 1
    for { set i 177 } { $i <= 182 } { incr i 1 } {
	set numPadButtons($i) 8
	set numPadStrips($i) 2
    }
    set numPadButtons(183) 4
    set numPadStrips(183) 1

    # Cintiq 20WSX
    set numPadButtons(197) 10
    set numPadStrips(197) 2

    # Hummingbird (Cintiq 12WX)
    set numPadButtons(198) 10
    set numPadStrips(198) 2

    # I4
    set numPadButtons(184) 7
    set numPadRings(184) 1
    for { set i 185 } { $i <= 187 } { incr i 1 } {
	set numPadButtons($i) 9
	set numPadRings($i) 1
    }

    for { set i 0 } { $i <= $maxNumTablets } { incr i 1 } {
	if { $numPadButtons($i) || $numPadRings($i) 
		|| $numPadStrips($i) || $numPadAbsW($i) 
		|| $numPadRelW($i) } {
	    set hasPad($i) 1
	}
    }

    # one finger 
    for { set i 147 } { $i <= 159 } { incr i 1 } {
	set hasTouch($i) 1
    }

    # 2FG
    for { set i 226 } { $i <= 227 } { incr i 1 } {
	set hasTouch($i) 1
	set hasGesture($i) 1
    }
}

proc createControls { } {
    global numScreens currentScreen cKeys spName
    global oldKeys numStrips maxNumButtons maxNumStripEvents
    global workingTags db dm sm wName bName numButton
    global ignoreButton displayToggleB
    global modeToggleB keystrokeB

    createDeviceListPanel 
    updateModelInfo

    set db(1) "Left"
    set db(2) "Middle"
    set db(3) "Right"
    set db(4) "Fourth"
    set db(5) "Fifth"

    set numB [ expr ($maxNumButtons + $maxNumStripEvents) ]
    for { set i 6 } { $i <= $numB } { incr i 1 } {
	set db($i) "Ignore"
    }

    set db($modeToggleB) "Mode Toggle"
    set db($displayToggleB) "Display Toggle"
    set db($ignoreButton) "Ignore"
    set db($keystrokeB) "KeyStroke"
    set dm(1) "Relative"
    set dm(2) "Absolute"

    set sm(1) "Side Switch Only"
    set sm(2) "Side Switch + Tip"

    set bName(keystroke) 0
    set wName(1) "Feel"
    set wName(2) "Tool Buttons"
    set wName(3) "Tracking"
    set wName(4) "Screen Mapping"
    set wName(5) "Keystrokes"
    set wName(6) "Tablet Controls"

#   Real help will be supported later
#    checkbutton .showHelp -text "Turn Help on" -anchor w \
#	    -variable showHelp -state normal  

    button .exit -text "Exit" -command "exit 0" -padx 40
    
#    grid .showHelp -row 0 -column 2 -sticky nw -padx 30
    grid .exit -row 20 -column 2 -sticky news -padx 20
    grid .workingDev -row 0 -rowspan 25 -column 0 -sticky news -padx 20

    set workingTags [ bindtags .workingDev.list]

    grid columnconfigure . 1 -weight 1
    grid rowconfigure . 7 -weight 5
}

proc CalibrateOnly { numArg argList } {
    global device calibration_sequence_only calibration_with_warning
    global deviceList

    set calibration_sequence_only 1
    set device [ lrange $argList 1 end ]

    createDeviceList

    set found 0
    foreach dev $deviceList {
	if { [ string compare $dev $device ] == 0 } {
	    set found 1
	    break;
	}
    }

    if { !$found } {
	    messageWindow "Error" "\n\n Device $device not found. \n"
	    exit 1
    }

    createScreenList $device
    if { $numArg == 3 } {
	set calibration_with_warning 1
    }
    Calibration
}

# Save to hostname-specific wacomcplrc
# Note: TCL's argv does not include command name, 0 is first argument
if { $argc > 0 && [lindex $argv 0] == "--use-hostname" } {
    set hostname [ info hostname ]
    set config_file "~/.wacomcplrc.$hostname"
    set argc [ expr ($argc - 1) ]
    set argv [ lrange $argv 1 end ]
}


if { $argc > 1 && [lindex $argv 0] == "calibrate" } {
    wm title . "Wacom Control Panel -Calibration"	
    CalibrateOnly $argc $argv
    wm geometry . =350x200+$origin_x+$origin_y
} else {
    wm title . "Wacom Control Panel"
    createControls
    wm geometry . =$windowSize+$origin_x+$origin_y
}

#
# Local Variables:
# mode: tcl
# End:
#

# vim: set noexpandtab shiftwidth=4:
