# browser.tcl -- Make TkDVI browser windows.
# Copyright © 2000 Anselm Lingnau <lingnau@tm.informatik.uni-frankfurt.de>.
# See file COPYING for conditions on use and distribution.
# $Id: browser.tcl,v 1.12 2000/07/07 09:02:32 lingnau Exp $

package provide tkdvi::browser 0.2

namespace eval ::tkdvi::browser:: {
    variable Configure
    variable State
    variable Options
    variable lastCloseExits 1

    namespace export browser

    array set Options {
	file {File {}}
	mode {Mode single}
	size {Size a4}
	shrink {Shrink 8}
	titleformat {TitleFormat {tkdvi: %f}}
	fullscreen {FullScreen 0}
	toplevel {TopLevel 1}
	scrollbars {ScrollBars xy}
	menubar {MenuBar 1}
	toolbar {ToolBar 1}
	pageselector {PageSelector 1}
	b1action {B1Action magnify}
	alignmentruler {AlignmentRuler 1}
	measuretape {MeasureTape 1}
	measureunit {MeasureUnit cm}
	sourcespecials {SourceSpecials 0}
	freehandcolor {FreehandColor red}
    }

    foreach {option cv} [array get Options] {
	foreach {optClass value} $cv break
	option add *TkDVIBrowser.$optClass $value widgetDefault
    }
}

proc ::tkdvi::browser::Defaults {name} {
    variable Configure
    variable State
    variable Options

    set Configure($name-file) {}
    foreach {option cv} [array get Options] {
	foreach {optClass value} $cv break
	set Configure($name-$option) [option get $name $option $optClass]
    }

    trace variable Configure($name-file) w \
	    [namespace code [list OpenFile $name]]

    trace variable Configure($name-mode) w \
	    [namespace code [list Images $name -1]]
    trace variable Configure($name-size) w \
	    [namespace code [list Images $name -1]]
    trace variable Configure($name-shrink) w \
	    [namespace code [list Images $name -1]]
    
    trace variable Configure($name-titleformat) w \
	    [namespace code [list TitleFormat $name]]

    trace variable Configure($name-fullscreen) w \
	    [namespace code [list Dimensions $name]]

    trace variable Configure($name-scrollbars) w \
	    [namespace code [list WidgetSetup $name]]
    trace variable Configure($name-toolbar) w \
	    [namespace code [list WidgetSetup $name]]
    trace variable Configure($name-menubar) w \
	    [namespace code [list WidgetSetup $name]]
    trace variable Configure($name-pageselector) w \
	    [namespace code [list WidgetSetup $name]]

    trace variable Configure($name-b1action) w \
	    [namespace code [list Pagebindings $name]]

    set State($name-numArg) 0
    set State($name-bmFinalized) 0
}

proc ::tkdvi::browser::browser {name args} {
    variable Configure
    variable State

    # Widget commands live in the global namespace
    if {"" != [namespace eval :: [list info command $name]]} {
	return -code error "command name \"$name\" already exists"
    }

    set Configure($name-toplevel) 1
    set idx [lsearch -exact $args -toplevel]
    if {$idx >= 0 && [expr {$idx+1}] < [llength $args]} {
	set Configure($name-toplevel) [lindex $args [expr {$idx+1}]]
	set args [lreplace $args $idx [expr {$idx+1}]]
    }

    if {$Configure($name-toplevel)} {
	if {[catch "toplevel $name -class TkDVIBrowser" result]} {
	    return -code error $result
	}
    } else {
	if {[catch "frame $name -class TkDVIBrowser" result]} {
	    return -code error $result
	}
    }
    rename $name "$name-tkdvi"
    set State($name-top) $name
    set State($name-topcmd) "$name-tkdvi"

    Defaults $name

    proc ::$name {method args} [format {
	if {[catch {::tkdvi::browser::Methods %s $method $args} result]} {
	    return -code error $result
	} else {
	    return $result
	}
    } $name]

    set State($name-canvas) [canvas $State($name-top).c -background white \
	    -xscrollcommand [list $State($name-top).sx set] \
	    -yscrollcommand [list $State($name-top).sy set]]
    set State($name-scrollx) [scrollbar $State($name-top).sx \
	    -orient horizontal -command [list $State($name-canvas) xview]]
    set State($name-scrolly) [scrollbar $State($name-top).sy \
	    -orient vertical -command [list $State($name-canvas) yview]]
    set State($name-pageselector) \
	    [::tkdvi::pagesel::pagesel $State($name-top).pg \
	        -command [list ::$name gotopage %p] -background white]

    set State($name-toolbar) [frame $State($name-top).tb]
    entry $State($name-toolbar).e -width 15 -background white -justify center \
	    -textvariable ::tkdvi::browser::State($name-pageNo) -state disabled
    set State($name-pageNoEntry) $State($name-toolbar).e
    button $State($name-toolbar).bb -bitmap @$::tkdvi(tcllib)/arrfirst.xbm \
	    -command [list ::$name gotopage 0]
    button $State($name-toolbar).b -bitmap @$::tkdvi(tcllib)/arrprev.xbm \
	    -command [list ::$name nextpage -1]
    button $State($name-toolbar).f -bitmap @$::tkdvi(tcllib)/arrnext.xbm \
	    -command [list ::$name nextpage 1]
    button $State($name-toolbar).ff -bitmap @$::tkdvi(tcllib)/arrlast.xbm \
	    -command [list ::$name gotopage last]
    frame $State($name-toolbar).s1 -width 4 -borderwidth 2 -relief sunken
    radiobutton $State($name-toolbar).sg -bitmap @$::tkdvi(tcllib)/single.xbm \
	    -indicatoron false -selectcolor {} -padx 2 -pady 2 \
	    -variable ::tkdvi::browser::Configure($name-mode) -value single
    radiobutton $State($name-toolbar).db -bitmap @$::tkdvi(tcllib)/spread.xbm \
	    -indicatoron false -selectcolor {} -padx 2 -pady 2 \
	    -variable ::tkdvi::browser::Configure($name-mode) -value spread
    radiobutton $State($name-toolbar).ov -bitmap @$::tkdvi(tcllib)/over.xbm \
	    -indicatoron false -selectcolor {} -padx 2 -pady 2 \
	    -variable ::tkdvi::browser::Configure($name-mode) -value overview
    frame $State($name-toolbar).s2 -width 4 -borderwidth 2 -relief sunken
    radiobutton $State($name-toolbar).mag -bitmap @$::tkdvi(tcllib)/magnif.xbm \
	    -indicatoron false -selectcolor {} -padx 2 -pady 2 \
	    -variable ::tkdvi::browser::Configure($name-b1action) \
	    -value magnify
    radiobutton $State($name-toolbar).draw -bitmap @$::tkdvi(tcllib)/draw.xbm \
	    -indicatoron false -selectcolor {} -padx 2 -pady 2 \
	    -variable ::tkdvi::browser::Configure($name-b1action) \
	    -value draw
    frame $State($name-toolbar).s3 -width 4 -borderwidth 2 -relief sunken
    entry $State($name-toolbar).shrink -width 2 -background white \
	    -justify center -state disabled \
	    -textvariable ::tkdvi::browser::Configure($name-shrink)
    button $State($name-toolbar).plus -bitmap @$::tkdvi(tcllib)/plus.xbm \
	    -command [namespace code [list IncrShrink $name]]
    button $State($name-toolbar).minus -bitmap @$::tkdvi(tcllib)/minus.xbm \
	    -command [namespace code [list IncrShrink $name -1]]
    pack $State($name-toolbar).e -side left
    pack $State($name-toolbar).bb $State($name-toolbar).b \
	    $State($name-toolbar).f $State($name-toolbar).ff \
	    -ipadx 2 -ipady 2 -side left
    pack $State($name-toolbar).s1 -padx 2 -side left
    pack $State($name-toolbar).sg $State($name-toolbar).db \
	    $State($name-toolbar).ov -ipadx 2 -ipady 2 -side left
    pack $State($name-toolbar).s2 -padx 2 -side left
    pack $State($name-toolbar).mag $State($name-toolbar).draw \
	    -ipadx 2 -ipady 2 -side left
    pack $State($name-toolbar).s3 -padx 2 -side left
    pack $State($name-toolbar).shrink -side left
    pack $State($name-toolbar).plus $State($name-toolbar).minus \
	    -ipadx 2 -ipady 2 -side left

    set State($name-menu) $State($name-top).m
    ::tkdvi::menu::menubar $State($name-menu) $State($name-top) 

    if {[catch "Opconfigure $name $args" result]} {
	return -code error $result
    }

    WidgetSetup $name

    Canvasbindings $name

    InitMeasure $name

    focus $State($name-canvas)

    return $name
}

proc ::tkdvi::browser::TitleFormat {name args} {
    variable Configure
    variable State

    if {$Configure($name-toplevel) == 0} {
	return
    }
    set title $Configure($name-titleformat)
    regsub %f $title [file tail [file rootname \
	    [$State($name-code) info filename]]] title
    wm title $State($name-top) $title
}

proc ::tkdvi::browser::WidgetSetup {name args} {
    variable Configure
    variable State

    if {$Configure($name-toplevel)} {
	if {$Configure($name-menubar) == 1} {
	    $State($name-topcmd) configure -menu $State($name-menu)
	} else {
	    $State($name-topcmd) configure -menu {}
	}
    }
    if {$Configure($name-toolbar)} {
	grid $State($name-toolbar) -row 0 -column 0 -columnspan 3 -sticky we
    } else {
	grid remove $State($name-toolbar)
    }
    grid $State($name-canvas)  -row 1 -column 1 -sticky nsew
    if {[string match *y $Configure($name-scrollbars)]} {
	grid $State($name-scrolly) -row 1 -column 2 -sticky ns
    } else {
	grid remove $State($name-scrolly)
    }
    if {[string match x* $Configure($name-scrollbars)]} {
	grid $State($name-scrollx) -row 2 -column 1 -sticky we
    } else {
	grid remove $State($name-scrollx)
    }
    grid rowconfigure    $State($name-top) 1 -weight 1
    grid columnconfigure $State($name-top) 1 -weight 1
    if {$Configure($name-pageselector)} {
	grid $State($name-pageselector) -row 1 -column 0 -rowspan 2 -sticky ns
    } else {
	grid remove $State($name-pageselector)
    }
}

proc ::tkdvi::browser::OpenFile {name args} {
    variable Configure
    variable State

    if {[string length $Configure($name-file)] == 0} {
	return
    }
    set State($name-code) $Configure($name-file)
    $State($name-code) configure \
	    -reloadcommand [list ::tkdvi::browser::ReloadFinish $name]

    # The `Bookmarks ...' menu item will be reenabled later if there are
    # actually any bookmarks in the file.

    foreach {menu idx} [::tkdvi::menu::finditem "Bookmarks *"] {
	$menu entryconfigure $idx -state disabled
    }

    TitleFormat $name
    $State($name-pageselector) flushpages
    $State($name-pageselector) addpages [$State($name-code) info pagenumbers]
    SetPageNoDisplay $name 0	
    Images $name 0
}

namespace eval ::tkdvi::browser {
    variable DisplayModes
    variable DisplayModeKeys
    if {![info exists DisplayModeKeys]} {
	set DisplayModeKeys {single spread over2x2 overview}
	set DisplayModes(single) [list {Single page} 1 1 \
		{$Configure($name-shrink)}]
	set DisplayModes(spread) [list {Two-page spread} 2 1 \
		{$Configure($name-shrink)}]
	set DisplayModes(over2x2) [list {4-page overview} 2 2 \
		{2*$Configure($name-shrink)}]
	set DisplayModes(overview) [list {16-page overview} 4 4 \
		{4*$Configure($name-shrink)}]
    }
}

proc PostDispModesMenu {name menu} {
    variable Configure
    variable DisplayModes
    variable DisplayModeKeys
    $menu delete 0 end
    foreach k $DisplayModeKeys {
	set name [lindex $DisplayModes($k) 0]
	$menu add radiobutton -label $name
	    -variable Configure($name-mode) -value $k
    }
}

# Creates an array of images on the widget's canvas as specified by the
# current display mode.

proc ::tkdvi::browser::Images {name {startPage -1} args} {
    variable Configure
    variable State
    variable DisplayModes
    variable DisplayModeKeys

    if {$Configure($name-shrink) < 1} {
	set Configure($name-shrink) 1
    }

    # Keep existing first page first if at all possible

    if {$startPage == -1} {
	if {[info exists State($name-dvis)]} {
	    set startPage $State($name-[lindex $State($name-dvis) 0],p)
	} else {
	    set startPage 0
	}
    }
    set State($name-dvis) {}

    $State($name-canvas) delete all

    # Get display mode particulars

    if {![info exists DisplayModes($Configure($name-mode))]} {
	return -code error \
		"Display mode \"$Configure($name-mode)\" doesn't exist"
    }
    foreach {junk horizPages vertPages shrinkExpr} \
	    $DisplayModes($Configure($name-mode)) break
    set shrink [expr $shrinkExpr]
    set overviewMode [string match over* $Configure($name-mode)]

    # Create the DVI images

    set offset 0
    set yy 0
    for {set y 0} {$y < $vertPages} {incr y} {
	set xx 0
	for {set x 0} {$x < $horizPages} {incr x} {
	    set dvi dvi$offset
	    CreateDviImage $name $State($name-canvas) $dvi $xx $yy $shrink \
		    [expr {$startPage + $offset}]
	    if {$overviewMode} {
		$State($name-$dvi,i) configure -layer 9999
	    }
	    incr xx [image width $State($name-$dvi,i)]
	    incr offset
	    lappend State($name-dvis) $dvi
	}
	incr yy [image height $State($name-dvi0,i)]
    }

    set State($name-hPages) $horizPages
    set State($name-vPages) $vertPages
    set State($name-totalWidth) $xx
    set State($name-totalHeight) $yy

    Dimensions $name
    Pagebindings $name
}

proc ::tkdvi::browser::CreateDviImage {name canvas key x y shrink page} {
    variable Configure
    variable State

    set State($name-$key,i) [::image create dvi -file $State($name-code) \
	    -shrink $shrink -size $Configure($name-size) \
	    -specialcommand \
	        [namespace code [list SpecialCmd $name $key %x %y %c %s]] \
	    -precommand [namespace code [list PreCmd $name $key %p]] \
	    -postcommand [namespace code [list PostCmd $name $key %p]]]
    $State($name-$key,i) page =$page
    set State($name-$key,x) $x
    set State($name-$key,y) $y
    set State($name-$key) [$canvas create image \
	    $State($name-$key,x) $State($name-$key,y) \
	    -image $State($name-$key,i) -anchor nw -tags dvi]
    set State($name-$key,p) $page
    set State($name-$key,s) $shrink
}

proc ::tkdvi::browser::Dimensions {name args} {
    variable Configure
    variable State

    set width 99999
    set height 99999
    set canvas $State($name-canvas)
    set wd $State($name-totalWidth)
    set ht $State($name-totalHeight)
    if {$wd > $width} {
	set wd $width
    }
    if {$ht > $height} {
	set ht $height
    }
    $canvas configure -scrollregion [list 0 0 $wd $ht]
    if {$Configure($name-fullscreen)} {
	set w [winfo screenwidth $State($name-top)]
	set h [winfo screenheight $State($name-top)]
	set State($name-fullScreenPrevW) [$canvas cget -width]
	set State($name-fullScreenPrevH) [$canvas cget -height]
	regexp {\+([0-9]+)\+([0-9]+)} [wm geometry $State($name-top)] \
		State($name-fullScreenGeometry)
	# wm geometry $State($name-top) +0+0
    } elseif {[info exists State($name-fullScreenGeometry)]} {
	# wm geometry $State($name-top) $State($name-fullScreenGeometry)
	set w $State($name-fullScreenPrevW)
	set h $State($name-fullScreenPrevH)
	unset State($name-fullScreenGeometry)
    } else {
	set limit [expr {0.9*[winfo screenwidth $State($name-top)]}]
	set w [expr {$wd > $limit ? $limit : $wd}]
	set limit [expr {0.9*[winfo screenheight $State($name-top)]}]
	set h [expr {$ht > $limit ? $limit : $ht}]
    }
    $canvas configure -width $w -height $h
    return [list $wd $ht]
}

proc ::tkdvi::browser::Pagebindings {name args} {
    variable State
    variable Configure

    set canvas $State($name-canvas)

    set events {
	<ButtonPress-1> 200 150
	<Shift-ButtonPress-1> 400 250
	<Control-ButtonPress-1> 700 500
    }

    set overviewMode [string match over* $Configure($name-mode)]
    set shrink [$State($name-[lindex $State($name-dvis) 0],i) cget -shrink]
    foreach item $State($name-dvis) {
	if {[string compare $Configure($name-b1action) magnify] == 0} {
	    foreach {event wd ht} $events {
		$canvas bind $State($name-$item) $event \
			[namespace code \
			[list PostMagnifier $name $item %x %y %X %Y $wd $ht 1]]
	    }
	    $canvas bind $State($name-$item) <B1-Motion> [namespace code \
		    [list TrackMagnifier $name %x %y %X %Y]]
	    $canvas bind $State($name-$item) <ButtonRelease-1> \
		    [namespace code [list UnpostMagnifier $name]]
	} else {
	    $canvas bind $State($name-$item) <ButtonPress-1> \
		    [namespace code [list FreehandDraw $name $item %x %y]]
	    $canvas bind $State($name-$item) <B1-Motion> \
		    [namespace code [list FreehandDraw $name $item %x %y]]
	    $canvas bind dvi <ButtonRelease-1> \
		    [namespace code [list FreehandFinish $name]]
	}
	$canvas bind $State($name-$item) <ButtonPress-3> \
		[namespace code \
		[list B3Pressed $name %s $item $shrink $overviewMode %x %y %X %Y]]
	$canvas bind $State($name-$item) <B3-Motion> \
		[namespace code [list B3Moved $name $item $shrink $overviewMode %x %y]]
	$canvas bind $State($name-$item) <ButtonRelease-3> \
		[namespace code [list B3Released $name %s $item $overviewMode]]
    }
}

proc ::tkdvi::browser::B3Pressed {name state item shrink overviewMode x y X Y} {
    variable Configure
    variable State

    if {$state == 0} {
	::tk_popup $State($name-menu) $X $Y
    } elseif {!$overviewMode} {
	if {$state & 1} {	# Shift
	    PostMeasure $name $shrink $x $y
	} elseif {$state & 4} {	# Control
	    PostAlignment $name $x $y
	} elseif {$state & 64} {	# Meta
	    SrcSGo $name $x $y
	}
    }
}

proc ::tkdvi::browser::B3Moved {name item shrink overviewMode x y} {
    variable Configure
    variable State

    if {!$overviewMode} {
	TrackMeasure $name $shrink $x $y
	TrackAlignment $name $x $y
    }
}

proc ::tkdvi::browser::B3Released {name state item overviewMode} {
    variable Configure
    variable State

    if {$overviewMode && $state != 0} {
	PopupFromOverview $name $item
    } else {
	UnpostMeasure $name
	UnpostAlignment $name
    }
}

proc ::tkdvi::browser::PopupFromOverview {name item} {
    variable State

    set new [::tkdvi::cloneWindow $name -mode single]
    $new gotopage $State($name-$item,p)
}

proc ::tkdvi::browser::Canvasbindings {name} {
    variable State
    variable Configure

    set canvas $State($name-canvas)

    # Navigation
    bind $canvas <Left> [list $canvas xview scroll -1 pages]
    bind $canvas <Right> [list $canvas xview scroll 1 pages]
    bind $canvas <Down> [list $canvas yview scroll 1 pages]
    bind $canvas <Up> [list $canvas yview scroll -1 pages]
    
    bind $canvas <minus> [list ::$name nextpage -1]
    bind $canvas <p> [list ::$name nextpage -1]
    bind $canvas <b> [list ::$name nextpage -1]
    bind $canvas <plus> [list ::$name nextpage 1]
    bind $canvas <n> [list ::$name nextpage 1]
    bind $canvas <asterisk> [list ::$name nextpage 10]
    bind $canvas <underscore> [list ::$name nextpage -10]
    bind $canvas <space> [list ::$name nextpage 1 1]

    bind $canvas <Home> [list ::$name gotopage 0]
    bind $canvas <End> [list ::$name gotopage \
	    [expr {[$State($name-code) info pages]-1}]]

    bind $canvas <g> [list ::$name gotopage]

    # Scanning
    bind $canvas <ButtonPress-2> [namespace code [list StartScan $name %x %y]]
    bind $canvas <B2-Motion> [namespace code [list ContinueScan $name %x %y]]
    bind $canvas <ButtonRelease-2> [namespace code [list EndScan $name]]

    # Extras
    bind $canvas <q> { exit }
    bind $canvas <c> [list ::$name close]
    bind $canvas <r> [list ::$name reload]
    bind $canvas <w> [list ::tkdvi::cloneWindow $name]

    bind $canvas <o> [list ::$name configure -mode overview]
    bind $canvas <s> [list ::$name configure -mode single]
    bind $canvas <d> [list ::$name configure -mode spread]

    bind $canvas <f> \
	    [namespace code [list ToggleConfigureVariable $name fullscreen]]

    bind $canvas <C> [namespace code [list FreehandClear $name]]
    bind $canvas <Delete> [namespace code [list FreehandDeleteItem $name]]

    bind $canvas <z> [namespace code [list SrcSDump $name]]

    foreach i {1 2 3 4 5 6 7 8 9} {
	bind $canvas <Key-$i> [namespace code [list NumArg $name $i]]
	bind $canvas <Control-Key-$i> [list ::$name configure -shrink $i]
    }
    bind $canvas <Key-0> [namespace code [list NumArg $name 0]]
    bind $canvas <Control-Key-0> [list ::$name configure -shrink 10]

    bind $canvas <Control-plus> \
	    [list incr ::tkdvi::browser::Configure($name-shrink) -1]
    bind $canvas <Control-minus> \
	    [list incr ::tkdvi::browser::Configure($name-shrink)]

    foreach {k v} {m menubar t toolbar p pageselector} {
	bind $canvas <Control-Key-$k> \
	    [namespace code [list ToggleConfigureVariable $name $v]]
    }

    bind $canvas <Enter> [list ::focus $canvas]

    bind $State($name-top) <Expose> [namespace code [list ReloadCheck $name]]

    bind $State($name-top) <Destroy> [namespace code [list DestroyCheck $name %W]]
}

proc ::tkdvi::browser::DestroyCheck {name widget} {
    variable State
    variable lastCloseExits

    if {$lastCloseExits == 0 \
	    || [string compare $State($name-top) $widget] != 0} {
	return
    }
    foreach w [winfo children .] {
	if {[string match .dvi* $w]} {
	    lappend dviw $w
	}
    }
    if {[llength $dviw] == 1} {
	exit
    }
}

proc ::tkdvi::browser::ReloadCheck {name} {
    variable State
    if {[$State($name-code) changed]} {
	::$name reload
    }
}

proc ::tkdvi::browser::ReloadFinish {name} {
    variable State

    $State($name-pageselector) flushpages
    $State($name-pageselector) addpages [$State($name-code) info pagenumbers]

    foreach item $State($name-dvis) {
	$State($name-$item,i) page =$State($name-$item,p)
    }
    SetPageNoDisplay $name $State($name-dvi0,p)
}

proc ::tkdvi::browser::ToggleConfigureVariable {name variable} {
    variable Configure
    set Configure($name-$variable) [expr {1-$Configure($name-$variable)}]
}

# Update numeric argument if a numeric key has been pressed

proc ::tkdvi::browser::NumArg {name value} {
    variable State
    set State($name-numArg) [expr {10*$State($name-numArg)+$value}]
}

# Use this procedure to access the numeric argument (if any)

proc ::tkdvi::browser::CheckNumArg {name} {
    variable State
    set result 1
    if {$State($name-numArg) != 0} {
	set result $State($name-numArg)
    }
    return $result
}

# Reset the numeric argument to 0

proc ::tkdvi::browser::ResetNumArg {name} {
    variable State
    set State($name-numArg) 0
}

# This procedure increments or decrements the shrink factor, taking care
# of not letting it drop below 1.

proc ::tkdvi::browser::IncrShrink {name {amount 1}} {
    variable Configure
    if {[expr {$Configure($name-shrink) + $amount}] > 0} {
	incr Configure($name-shrink) $amount
    }
}

# Set the (physical, not TeX) page number display and update the page
# selector to show the given page as current.

proc ::tkdvi::browser::SetPageNoDisplay {name pageNo {currLayer 0} {layers 0}} {
    variable State
    if {$layers == 0 || $currLayer == 9999} {
	set State($name-pageNo) \
		[format {%s of %d} [expr {$pageNo+1}] \
		[$State($name-code) info pages]]
    } else {
	set State($name-pageNo) \
		[format {%s of %d [%d/%d]} [expr {$pageNo+1}] \
		[$State($name-code) info pages] [expr {$currLayer+1}] $layers]
    }
    $State($name-pageselector) current $pageNo
}

# Method dispatcher

proc ::tkdvi::browser::Methods {name method argList} {
    variable State
    switch -exact -- $method {
	canvas -
	top -
	topcmd -
	scrollx -
	scrolly {
	    return $State($name-$method)
	}
	nextpage -
	gotopage -
	print -
	open -
	close -
	reload -
	configure -
	cget {
	    if {[catch "Op$method $name $argList" result]} {
		regsub -- "Op$method" $result "$name $method" result
		return -code error $result
	    } else {
		return $result
	    }
	}
	default {
	    return -code error "\"$name $method\" is not defined"
	}
    }
}

proc ::tkdvi::browser::ConfigureInfo {name option} {
    variable Configure
    variable Options

    if {[info exists Configure(${name}${option})]} {
	set oo [string range $option 1 end]
	foreach {optClass defValue} $Options($oo) break
	return \
	    [list $option $oo $optClass $defValue $Configure(${name}${option})]
    }
    return -code error "option \"$option\" doesn't exist"
}

proc ::tkdvi::browser::Opconfigure {name args} {
    variable Configure
    variable Options

    if {[llength $args] == 0} {
	set result {}
	foreach v [array names Configure $name-*] {
	    lappend result [ConfigureInfo $name \
		    [string range $v [string length $name] end]]
	}
	return $result
    }

    if {[llength $args] == 1} {
	return [ConfigureInfo $name [lindex $args 0]]
    }

    if {[llength $args] % 2 != 0} {
	return -code error \
		"\"$name configure $args\": argument count must be even"
    }
    foreach {k v} $args {
	switch -exact -- $k {
	    -toplevel {
		return -code error "option \"-toplevel\" cannot be changed"
	    }
	    default {
		if {[info exists Configure(${name}${k})]} {
		    set Configure(${name}${k}) $v
		} else {
		    return -code error "unknown option \"$v\""
		}
	    }
	}
    }
}

proc ::tkdvi::browser::Opnextpage {name {offset 1} {nextLayer 0}} {
    variable State
    variable Configure

    if {abs($offset) == 1} {
	set offset [expr {$offset<0?-[CheckNumArg $name]:[CheckNumArg $name]}]
    }
    ResetNumArg $name

    set currLayer 0
    if {$nextLayer == 1} {
	set currLayer [$State($name-dvi0,i) cget -layer]
	set layers [$State($name-dvi0,i) layers]
	if {$layers == -1} {
	    update idletasks
	    set layers [$State($name-dvi0,i) layers]
	}
	if {$currLayer + 1 < $layers} {
	    incr currLayer
	    set offset 0
	} else {
	    set currLayer 0
	}
    } else {
	set currLayer 9999
    }

    set totalPages [$State($name-code) info pages]

    if {[string compare $Configure($name-mode) spread] == 0} {
	incr offset $offset
    } elseif {[string match over* $Configure($name-mode)]} {
	set viewPages [expr {$State($name-hPages)*$State($name-vPages)}]
	set offset [expr {$offset*$viewPages}]
	if {$totalPages <= $viewPages} {
	    set offset 0
	}
    }

    set lastPage [expr {$totalPages - 1}]
    set first 1
    foreach dvi $State($name-dvis) {
	if {$first} {
	    set newPage \
	      [expr {($State($name-$dvi,p)+$offset+$totalPages) % $totalPages}]
	    set first 0
	} else {
	    incr newPage
	}
	$State($name-$dvi,i) page =$newPage
	$State($name-$dvi,i) configure -layer $currLayer
	set State($name-$dvi,p) $newPage
    }
    update idletasks
    set layers [$State($name-dvi0,i) layers]
    SetPageNoDisplay $name $State($name-dvi0,p) $currLayer $layers
}

proc ::tkdvi::browser::Opgotopage {name {page -1}} {
    variable State
    variable Configure

    set lastPage [expr {[$State($name-code) info pages] - 1}]

    if {[string compare $page last] == 0} {
	set page $lastPage
    } elseif {$page == -1} {
	set page [expr {[CheckNumArg $name]+1}]
    }
    ResetNumArg $name

    if {$page > $lastPage} {
	return
    }

    set currLayer 9999
    set i 0
    foreach dvi $State($name-dvis) {
	set newPage [expr {$page + $i}]
	$State($name-$dvi,i) page =$newPage
	$State($name-$dvi,i) configure -layer $currLayer
	set State($name-$dvi,p) $newPage
	incr i
    }
    SetPageNoDisplay $name $State($name-dvi0,p)
}

proc ::tkdvi::browser::Opprint {name args} {
    variable State

    set fileName [$State($name-code) info filename]
    set pages {}
    if {[llength $args] > 0} {
	if {[lindex $args 0] == "-marked"} {
	    set pages "-pp [join [$State($name-pageselector) mark list] ,]"
	}
    }
    # TODO: Fancy dialog here
    set cmd [format {dvips %s %s} $pages $fileName]
    puts stderr "Print command: $cmd"
    # eval exec $cmd >& /dev/null
}

proc ::tkdvi::browser::Opopen {name {file {}} args} {
    variable Configure
    variable State

    if {[string length $file] == 0} {
	set types {
	    {{DVI Files} {.dvi}}
	    {{All Files} *}
	}
	set file [tk_getOpenFile -filetypes $types -title "Open DVI File"]
    }
    if {[string length $file] > 0} {
	if {[catch {set f [::dvi::code create -file $file]} msg]} {
	    puts stderr "$file: $msg"
	} elseif {[catch {$name configure -file $f} msg]} {
	    puts stderr "$file ($f): $msg"
	} else {
	    OpenFile $name
	}
    }
}

proc ::tkdvi::browser::Opclose {name args} {
    variable Configure
    variable State

    destroy $State($name-top)
    rename $name {}
    foreach n [array names Configure($name-*)] {
	unset $n
    }
    foreach n [array names State($name-*)] {
	unset $n
    }
}

proc ::tkdvi::browser::Opreload {name args} {
    variable Configure
    variable State

    $State($name-code) reload
}

# Special commands

proc ::tkdvi::browser::PreCmd {name key page} {
    variable State
    $State($name-canvas) move p=$page -100000 -100000
    set State($name-srcYs) {}
    return 0
}
proc ::tkdvi::browser::PostCmd {name key page} {
    variable State
    $State($name-canvas) move p=$page 100000 100000
    foreach s [array names State $name-srcY,*] { unset $s }
    foreach s [array names State $name-src,*] { unset $s }
    unset State($name-srcYs)
    return 0
}
proc ::tkdvi::browser::SpecialCmd {name key x y cookie s} {
    variable State
    variable Configure

    if {$Configure($name-sourcespecials) \
	    && [regexp {src:(\d+) *(.*)} $s junk lineno fileName]} {
	if {![info exists State($name-src,$cookie)]} {
	    puts stderr "Source special: $x $y $fileName $lineno"
	    $State($name-canvas) create rectangle \
		    [expr {$x-1}] [expr {$y-1}] [expr {$x+1}] [expr {$y+1}] \
		    -outline red -tags [list src "${fileName}#${lineno}"]
	    lappend State($name-srcY,$y) $x
	    set State($name-src,$cookie) 1
	}
    } elseif {[scan $s {info %[^ ]} key] == 1} {
	set key [lindex $s 1]
	set State($name-info,$key) \
		[string range $s [expr {6+[string length $key]}] end]
    } elseif {!$State($name-bmFinalized) \
	    && [string compare $s "bookmark begin"] == 0} {
	BookmarkBegin $name
    } elseif {!$State($name-bmFinalized) \
	    && [string compare $s "bookmark end"] == 0} {
	set State($name-bmFinalized) 1
	BookmarkEnd $name
    } elseif {!$State($name-bmFinalized) \
	    && [regexp {bookmark add ([0-9]+) ([^ ]+) (.*)} \
	    $s junk level mark title]} {
	BookmarkAdd $name $level $mark $title
    } elseif {[scan $s {example %s %f %f} prog wd ht] == 3} {
	if {[$State($name-$key,i) cget -shrink] > $Configure($name-shrink)} {
	    return
	}

	if {![info exists State($name-tags,$cookie)]} {
	    set State($name-tags,$cookie) t$cookie
	    set wpx [expr \
		    {[dvi::pixels [$State($name-$key,i) cget -xresolution] \
		        ${wd}pt] / $State($name-$key,s)}]
	    set wpy [expr \
		    {[dvi::pixels [$State($name-$key,i) cget -yresolution] \
		        ${ht}pt] / $State($name-$key,s)}]
	    set file [file join [file dirname \
		    [$State($name-code) info filename]] $prog]
	    if {[catch {uplevel \#0 [list source $file]} msg]} {
		puts stderr $msg
	    }
	    set f [frame $State($name-canvas).$State($name-tags,$cookie) \
		    -width $wpx -height $wpy \
		    -background [$State($name-canvas) cget -background]]
	    pack propagate $f false
	    set fn [file rootname $prog]_init
	    if {[catch {uplevel \#0 $fn $f $wpx $wpy} msg]} {
		puts stderr $msg
	    }
	    $State($name-canvas) create window $x $y -anchor nw -window $f \
		    -tags [list $State($name-tags,$cookie) \
		        p=[$State($name-$key,i) page]]
	    $State($name-canvas) raise $State($name-tags,$cookie)
	    set State($name-frames,$cookie) $f
	}
    }
}

# Scanning

proc ::tkdvi::browser::StartScan {name x y} {
    variable State

    set State($name-scanCursor) [$State($name-canvas) cget -cursor]
    $State($name-canvas) configure -cursor fleur
    $State($name-canvas) scan mark $x $y
    set State($name-scanX) $x
    set State($name-scanY) $y
    set State($name-scanMoved) 0
}
proc ::tkdvi::browser::ContinueScan {name x y} {
    variable State

    if {($x != $State($name-scanX)) || ($y != $State($name-scanY))} {
	set State($name-scanMoved) 1
    }
    if {$State($name-scanMoved)} {
	$State($name-canvas) scan dragto $x $y
    }
}
proc ::tkdvi::browser::EndScan {name} {
    variable State

    $State($name-canvas) configure -cursor $State($name-scanCursor)
}

# Magnifying glass

proc ::tkdvi::browser::PostMagnifier {name item x y wx wy width height shrink} {
    variable State
    variable Configure

    toplevel $name.mag -borderwidth 2 -relief ridge
    wm overrideredirect $name.mag true
    canvas $name.mag.c -background white -width $width -height $height
    pack $name.mag.c

    wm geometry $name.mag \
	    [format {+%d+%d} [expr {$wx-$width/2}] [expr {$wy-$height/2}]]

    set State($name-magItem) $item
    set State($name-magImg) [::image create dvi -file $State($name-code) \
	    -shrink $shrink -size $Configure($name-size) -layer 9999]
    $State($name-magImg) page =$State($name-$item,p)
    set State($name-magDvi) [$name.mag.c create image 0 0 \
	    -image $State($name-magImg) -anchor nw]
    set x [expr {[$State($name-canvas) canvasx $x]-$State($name-$item,x)}]
    set y [expr {[$State($name-canvas) canvasy $y]-$State($name-$item,y)}]
    set State($name-magShrink) [$State($name-$item,i) cget -shrink]
    $name.mag.c move all \
	    [expr {-$x*$State($name-magShrink)+$width/2}] \
	    [expr {-$y*$State($name-magShrink)+$height/2}]
    set State($name-magX) $x
    set State($name-magY) $y
    set State($name-magW) $width
    set State($name-magH) $height
}
proc ::tkdvi::browser::TrackMagnifier {name x y wx wy} {
    variable State

    set item $State($name-magItem)
    set x [expr {[$State($name-canvas) canvasx $x]-$State($name-$item,x)}]
    set y [expr {[$State($name-canvas) canvasy $y]-$State($name-$item,y)}]
    $name.mag.c move all \
	    [expr {($State($name-magX)-$x)*$State($name-magShrink)}] \
	    [expr {($State($name-magY)-$y)*$State($name-magShrink)}]
    wm geometry $name.mag \
	    [format {+%d+%d} [expr {$wx-$State($name-magW)/2}] \
	    [expr {$wy-$State($name-magH)/2}]]
    set State($name-magX) $x
    set State($name-magY) $y
}
proc ::tkdvi::browser::UnpostMagnifier {name} {
    variable State

    if {[info exists State($name-magImg)]} {
	::image delete $State($name-magImg)
	destroy $name.mag
	unset State($name-magImg)
    }
}

# Freehand annotating of pages (Mickey Mouse version)

proc ::tkdvi::browser::FreehandClear {name} {
    variable State
    $State($name-canvas) delete withtag annote
}
proc ::tkdvi::browser::FreehandDraw {name key x y} {
    variable Configure
    variable State

    set page $State($name-$key,p)
    set x [$State($name-canvas) canvasx $x]
    set y [$State($name-canvas) canvasy $y]
    if {[info exists State($name-fhItem)]} {
	eval $State($name-canvas) coords $State($name-fhItem) \
		[$State($name-canvas) coords $State($name-fhItem)] $x $y
    } else {
	set State($name-fhItem) \
		[$State($name-canvas) create line $x $y $x $y \
		-width 2 -fill $Configure($name-freehandcolor) \
		-tags [list p=$page annote]]
    }
}
proc ::tkdvi::browser::FreehandFinish {name} {
    variable State
    if {[info exists State($name-fhItem)]} {
	unset State($name-fhItem)
    }
}
proc ::tkdvi::browser::FreehandDeleteItem {name} {
    variable State
    if {[lsearch -exact \
	    [$State($name-canvas) itemcget current -tags] annote] >= 0} {
	$State($name-canvas) delete current
    }
}

# Measuring ruler

namespace eval ::tkdvi::browser {
    variable UnitNameList
    variable UnitNames
    variable UnitFmts
    set UnitNameList {in cm mm pt bp pc dd cc sp px}
    if {![info exists UnitNames]} {
	set UnitNames [list {in Inch} {cm Centimetres} {mm Millimetres} \
	    {pt {Printer's points (1/72.27in)}} {bp {Big points (1/72in)}} \
	    {pc Picas} {dd {Didot points}} {cc {Ciceros (12dd)}} \
	    {sp {Scaled points (1/65536pt)}} {px {Device pixels}}]
    }
    if {![info exists UnitFmts]} {
	array set UnitFmts {
	    in "%4.2fin"
	    cm "%4.2fcm"
	    mm "%5.1fmm"
	    pt "%6.1fpt"
	    bp "%6.1fbp"
	    pc "%5.1fpc"
	    dd "%6.1fdd"
	    cc "%5.1fcc"
	    sp "%8.0fsp"
	    px "%4.0fpx"
	}
    }
}
proc ::tkdvi::browser::SetPosFmt {name name1 name2 op} {
    variable UnitFmts
    variable State
    upvar [set name1]($name2) unit
    set State($name-posFmt) $UnitFmts($unit)
}
proc ::tkdvi::browser::InitMeasure {name} {
    variable Configure
    variable State

    set State($name-resolution) \
	    [$State($name-[lindex $State($name-dvis) 0],i) cget -xresolution]
    set State($name-posFmt) {%4.2fcm}

    trace variable Configure($name-measureunit) w \
	    [namespace code [list SetPosFmt $name]]
}
proc ::tkdvi::browser::PostMeasure {name shrink x y} {
    variable Configure
    variable State

    if {!$Configure($name-measuretape)} {
	return
    }
    set x [$State($name-canvas) canvasx $x]
    set y [$State($name-canvas) canvasy $y]
    set State($name-measItem) \
	    [$State($name-canvas) create line $x $y $x $y -fill black]
    set State($name-measCursor) \
	    [$State($name-canvas) cget -cursor]
    $State($name-canvas) configure -cursor circle
    set State($name-measX) $x
    set State($name-measY) $y
    set State($name-measSave) $State($name-pageNo)
    set State($name-pageNo) \
	    [format "$State($name-posFmt),$State($name-posFmt)" \
	    [dvi::distance $State($name-resolution) \
	        [expr {$shrink*$x}] $Configure($name-measureunit)] \
	    [dvi::distance $State($name-resolution) \
	        [expr {$shrink*$y}] $Configure($name-measureunit)]]
}
proc ::tkdvi::browser::TrackMeasure {name shrink x y} {
    variable Configure
    variable State

    if {[info exists State($name-measItem)]} {
	set x [$State($name-canvas) canvasx $x]
	set y [$State($name-canvas) canvasy $y]
	set coords [$State($name-canvas) coords $State($name-measItem)]
	set coords [lreplace $coords 2 3 $x $y]
	eval $State($name-canvas) coords $State($name-measItem) $coords
	set dx [expr {$State($name-measX)-$x}]
	set dy [expr {$State($name-measY)-$y}]
	set State($name-pageNo) [format "d=$State($name-posFmt)" \
		[dvi::distance $State($name-resolution) \
		    [expr {$shrink*hypot($dx,$dy)}] \
		    $Configure($name-measureunit)] \
		$Configure($name-measureunit)]
    }
}

proc ::tkdvi::browser::UnpostMeasure {name} {
    variable State

    if {[info exists State($name-measItem)]} {
	$State($name-canvas) delete $State($name-measItem)
	$State($name-canvas) configure -cursor $State($name-measCursor)
	set State($name-pageNo) $State($name-measSave)
    }
}

# Alignment ruler

proc ::tkdvi::browser::PostAlignment {name x y} {
    variable Configure
    variable State

    if {!$Configure($name-alignmentruler)} {
	return
    }
    set c $State($name-canvas)
    set x [$c canvasx $x]
    set y [$c canvasy $y]
    set sr [$c cget -scrollregion]
    set State($name-alignWd) [lindex $sr 2]
    set State($name-alignHt) [lindex $sr 3]
    set State($name-alignHRuler) \
	    [$c create line 0 $y $State($name-alignWd) $y -width 0 -fill red]
    set State($name-alignVRuler) \
	    [$c create line $x 0 $x $State($name-alignHt) -width 0 -fill red]
    set State($name-alignCursor) [$State($name-canvas) cget -cursor]
    $State($name-canvas) configure -cursor circle
    set State($name-alignX) $x
    set State($name-alignY) $y
}
proc ::tkdvi::browser::TrackAlignment {name x y} {
    variable State

    if {[info exists State($name-alignVRuler)]} {
	set x [$State($name-canvas) canvasx $x]
	set y [$State($name-canvas) canvasy $y]
	
	$State($name-canvas) coords $State($name-alignHRuler) \
		0 $y $State($name-alignWd) $y
	$State($name-canvas) coords $State($name-alignVRuler) \
		$x 0 $x $State($name-alignHt)
    }
}
proc ::tkdvi::browser::UnpostAlignment {name} {
    variable State

    if {[info exists State($name-alignVRuler)]} {
	set c $State($name-canvas)
	$c delete $State($name-alignHRuler) $State($name-alignVRuler)
	$c configure -cursor $State($name-alignCursor)
    }
}

# General Preferences

proc ::tkdvi::browser::GeneralPrefs {name} {
    variable Configure
    variable State
    variable Dialog
    variable DisplayModeKeys
    variable UnitNameList
    variable UnitNames

    set p $State($name-top).prefs
    set State($name-prefs) $p

    toplevel $p
    wm title $p "General Preferences"
    set row 0
    
    set Dialog($name-defaultmode) [option get $name mode Mode]
    set Dialog($name-measureunit) $Configure($name-measureunit)

    label $p.layout-l -text {Default Display Mode: } -anchor e
    eval OptionMenu $p.layout-m \
	    tkdvi::browser::Dialog($name-defaultmode) $DisplayModeKeys
    grid $p.layout-l -row $row -column 0 -sticky e -ipadx 5 -ipady 5
    grid $p.layout-m -row $row -column 1 -sticky we -padx 2 -pady 2
    incr row

    label $p.unit-l -text {Measurement unit: } -anchor e
    eval OptionMenu $p.unit-m \
	    tkdvi::browser::Dialog($name-measureunit) $UnitNames
    grid $p.unit-l -row $row -column 0 -sticky e -ipadx 5 -ipady 5
    grid $p.unit-m -row $row -column 1 -sticky we -padx 2 -pady 2
    incr row

    frame $p.sep -relief sunken -borderwidth 1 -height 2
    grid $p.sep -row $row -column 0 -columnspan 2 -sticky we
    incr row

    frame $p.buttons
    button $p.buttons.ok -text OK \
	    -command [namespace code [list GPApply $name 1]]
    button $p.buttons.apply -text Apply \
	    -command [namespace code [list GPApply $name]]
    button $p.buttons.cancel -text Cancel -command [list destroy $p]
    pack $p.buttons.ok $p.buttons.apply $p.buttons.cancel \
	    -side left -fill x -padx 10 -pady 5
    grid $p.buttons -row $row -column 0 -columnspan 2
}

proc tkdvi::browser::GPApply {name {quit 0}} {
    variable Configure
    variable State
    variable Dialog

    set Configure($name-measureunit) $Dialog($name-measureunit)
    option add *TkDVIBrowser.mode $Dialog($name-defaultmode) interactive

    if {$quit} {
	destroy $State($name-prefs)
    }
}

# General Info (à la PDF)

proc tkdvi::browser::InfoGeneral {name} {
    variable State

    set State($name-info,file) [$State($name-code) info filename]
    set State($name-info,comment) [$State($name-code) info comment]
    set State($name-info,modified) \
	    [clock format [file mtime $State($name-info,file)]]
    set State($name-info,size) [file size $State($name-info,file)]

    set keys {file title subject author keywords producer modified size}

    destroy $State($name-top).info
    set t [toplevel $State($name-top).info]
    wm title $t [format {Info: %s} \
	    [file tail [file rootname $State($name-info,file)]]]

    set row 0
    foreach kk [concat $keys [array names State $name-info,*]] {
	regsub "^$name-info," $kk {} k
	if {[info exists seen($k)] || ![info exists State($name-info,$k)]} {
	    continue
	}
	if {[catch {set title [string totitle $k]}]} {
	    set title $k
	}
	label $t.k$row -anchor e -text "$title:"
	label $t.v$row -anchor w -text $State($name-info,$k)
	grid $t.k$row -row $row -column 0 -sticky we -pady 3
	grid $t.v$row -row $row -column 1 -sticky we -pady 3
	set seen($k) 1
	incr row
    }

    frame $t.sep -relief sunken -borderwidth 1 -height 2
    grid $t.sep -row $row -column 0 -columnspan 2 -sticky we -pady 3
    incr row

    frame $t.button
    button $t.button.ok -text OK -command [list destroy $t]
    pack $t.button.ok -pady 5
    grid $t.button -row $row -column 0 -columnspan 2 -sticky we
}

# Font Info (not really)

proc ::tkdvi::browser::InfoFonts {name} {
    variable State

    foreach f [$State($name-dvi0,i) fonts] {
	foreach {num info} $f break
	# puts stderr "$num => $info"
	puts stderr [format "%d: %s %f" $num [lindex $info 0] [expr {double([lindex $info 2])/double([lindex $info 3])}]]
    }
}

# Bookmarks

proc ::tkdvi::browser::BookmarkBegin {name} {
    variable State

    if {[info exists State($name-bookmarks)]} {
	destroy $State($name-bookmarks)
    }
    set State($name-bookmarks) [toplevel $State($name-top).bookmarks]
    wm withdraw $State($name-bookmarks)
    wm title $State($name-bookmarks) \
	    [format {Bookmarks: %s} [file tail \
	    [file rootname [$State($name-code) info filename]]]]
    set t [text $State($name-bookmarks).t -width 40 -height 20 \
	    -cursor arrow -background white -font {Helvetica 12} \
	    -yscrollcommand [list $State($name-bookmarks).sy set]]
    $t tag configure level1 -lmargin1 0 -lmargin2 30
    $t tag configure level2 -lmargin1 20 -lmargin2 50
    $t tag configure level3 -lmargin1 40 -lmargin2 70
    $t tag configure level4 -lmargin1 60 -lmargin2 90
    $t tag configure level5 -lmargin1 80 -lmargin2 110
    bindtags $t [list bookmark Text]
    bind bookmark <1> [namespace code [list BookmarkGoto $name]]
    grid $t -row 0 -column 0 -sticky nwse
    scrollbar $State($name-bookmarks).sy -orient vertical \
	    -command [list $t yview]
    grid $State($name-bookmarks).sy -row 0 -column 1 -sticky ns
    grid rowconfigure $State($name-bookmarks) 0 -weight 1
    grid columnconfigure $State($name-bookmarks) 0 -weight 1
    frame $State($name-bookmarks).f
    button $State($name-bookmarks).f.close -text Close \
	    -command [list wm withdraw $State($name-bookmarks)]
    pack $State($name-bookmarks).f.close
    grid $State($name-bookmarks).f -row 1 -column 0 -columnspan 2
}
proc ::tkdvi::browser::BookmarkEnd {name} {
    variable State
    $State($name-bookmarks).t configure -state disabled
#    if {[info exists State($name-bookmarkMenu)]} {
#	foreach {menu idx} $State($name-bookmarkMenu) break
#	$menu entryconfigure $idx -state normal
#    }
    foreach {menu idx} [::tkdvi::menu::finditem "Bookmarks *"] {
	$menu entryconfigure $idx -state normal
    }
}
proc ::tkdvi::browser::BookmarkAdd {name level mark title} {
    variable State
    regsub -all {\\([0-7][0-7][0-7])} $title {[format "%c" 0\1]} title
    regsub -all {\\\(} $title {(} title
    regsub -all {\\\)} $title {)} title
    set title [subst -nobackslashes -novariables $title]
    $State($name-bookmarks).t insert end \
	    $title [list level$level m-$mark] "\n"
}
proc ::tkdvi::browser::BookmarkGoto {name} {
    variable State
    foreach tag [$State($name-bookmarks).t tag names current] {
	if {[string match m-* $tag]} {
	    set anchor [string range $tag 2 end]
	    set page [$State($name-code) findanchor $anchor]
	    if {$page >= 0} {
		::$name gotopage $page
	    }
	}
    }
}
proc ::tkdvi::browser::BookmarkShow {name} {
    variable State
    wm deiconify $State($name-bookmarks)
}

# Source specials

proc ::tkdvi::browser::SrcSDump {name} {
    variable State
    puts stderr "Source specials:"
    set ys [array names State $name-srcY,*]
    foreach y [lsort $ys] {
	puts stderr "$y => $State($y)"
    }
}
proc ::tkdvi::browser::SrcSGo {name x y} {
    variable Configure
    variable State

    if {!$Configure($name-sourcespecials)} {
	return
    }

    set x [$State($name-canvas) canvasx $x]
    set y [$State($name-canvas) canvasy $y]
    if {[llength $State($name-srcYs)] == 0} {
	set ll {}
	set ys [array names State $name-srcY,*]
	if {[llength $ys] == 0} {
	    return
	}
	foreach yy $ys {
	    lappend ll [string range $yy [expr {[string last , $yy]+1}] end]
	}
	set State($name-srcYs) [lsort -integer $ll]
	lappend State($name-srcYs) 99999999
    }

    # Look for the current line (coordinate yyy where mouse y coordinate
    # is less than yyy but greater than y coordinate of previous line).
    # Check for source special on that line but to the left of mouse x
    # coordinate; if there isn't one, check previous line with very large
    # hypothetical mouse x coordinate. BUG: Doesn't seem to work well for
    # multi-column documents.

    for {set i 0} {$i < [expr {[llength $State($name-srcYs)]-1}]} {incr i} {
	if {[lindex $State($name-srcYs) $i] > $y} break
    }
    while {$i > 0} {
	set yyy [lindex $State($name-srcYs) $i]
	if {$yyy < 99999999} {
	    puts stderr "Likely Y: $yyy"
	    set ys [concat -1 $State($name-srcY,$yyy) 99999999]
	    for {set j 0} {$j < [expr {[llength $ys]}]} {incr j} {
		if {[lindex $ys $j] > $x} break
	    }
	    if {[lindex $ys [expr {$j-1}]] >= 0} {
		set xxx [lindex $ys [expr {$j-1}]]
		break
	    }
	}
	set x 99999998
	incr i -1
    }
    puts stderr "Likely X: $xxx"

    # Find appropriate file name and line number and emit command to
    # go there.

    foreach item [$State($name-canvas) find withtag src] {
	foreach {ix iy} [$State($name-canvas) coords $item] break
	set ix [expr {round($ix)+1}]
	set iy [expr {round($iy)+1}]
	if {$ix  == $xxx && $iy == $yyy} {
	    puts stderr [$State($name-canvas) itemcget $item -tags]
	    break
	}
    }
}
	
# About box

proc ::tkdvi::browser::About {} {
    global tkdvi aboutOK tcl_platform
    set top [tkdvi::dialog::new {} About]
    set c [tkdvi::dialog::content $top]
    set b [tkdvi::dialog::buttons $top]
    set i [image create photo -file [file join $tkdvi(tcllib) tkdvi.gif]]
    label $c.icon -image $i
    pack $c.icon -padx 5 -pady 5 -side left
    set text "TkDVI, a Tcl/Tk DVI Previewer\n"
    append text "Version [tkdvi::version]\n"
    append text "Copyright © 2000 Anselm Lingnau\n\n"
    append text "Tcl/Tk [info patchlevel]"
    if {[info exists tcl_platform(os)]} {
	append text " on $tcl_platform(os) $tcl_platform(osVersion)"
	append text " ($tcl_platform(machine))"
    }
    append text "\n\nFor information, see\n"
    append text "http://tm.informatik.uni-frankfurt.de/~lingnau/tkdvi/"
    message $c.msg -aspect 500 -text $text
    pack $c.msg -padx 5 -pady 5 -side right -expand yes -fill both
    button $b.ok -text OK -command {set aboutOK 1}
    pack $b.ok -side left -expand yes
    focus $b.ok
    wm protocol $top WM_DELETE_WINDOW [list $b.ok invoke]
    tkdvi::dialog::wait $top aboutOK
    destroy $top
}


proc ::tkdvi::browser::OptionMenu {w varName firstValue args} {
    upvar #0 $varName var
 
    if {![info exists var]} {
        set var $firstValue
    }
    menubutton $w -textvariable $varName -indicatoron 1 -menu $w.menu \
            -relief raised -bd 2 -highlightthickness 2 -anchor c \
            -direction flush
    menu $w.menu -tearoff 0
    if {[llength $firstValue] > 1} {
	foreach {value label} $firstValue break
    } else {
	set label $firstValue
	set value $firstValue
    }
    $w.menu add radiobutton -label $label -variable $varName -value $value
    foreach i $args {
	if {[llength $i] > 1} {
	    foreach {value label} $i break
	} else {
	    set label $i
	    set value $i
	}
        $w.menu add radiobutton -label $label -variable $varName \
		-value $value
    }
    return $w.menu
}
