#!/usr/bin/env wish

# epspdf conversion utility, GUI frontend

#####
# Copyright (C) 2006, 2008, 2009, 2010, 2011, 2013 Siep Kroonenberg
# n dot s dot kroonenberg at rug dot nl
#
# This program is free software, licensed under the GNU GPL, >=2.0.
# This software comes with absolutely NO WARRANTY. Use at your own risk!
#####

package require Tk

# tired of writing out this combined test again and again
set classic_unix [expr {$::tcl_platform(platform) eq "unix" && \
 $::tcl_platform(os) ne "Darwin"}]

# combo boxes and -ignorestderr introduced in Tk8.5
set ge_85 [expr {[string index $::tcl_patchLevel 2] > 4}]

# normally, epspdf.tlu should be in the same directory
# and texlua should be on the searchpath.
# However, the Windows installer version wraps this script in a starpack.

# logging: to a log window, not to a file
proc write_log {s} {
  if {[winfo exists .log_t.text]} {
    .log_t.text configure -state normal
    .log_t.text insert end "$s\n"
    .log_t.text yview moveto 1
    .log_t.text configure -state disabled
  # } else {
  #  puts $s
  }
}

### calling epspdf.tlu #########################

# Get full path of epspdf.tlu. It should be in the same directory as
# either this script or of the starpack containing this script,
# For non-windows versions, epspdftk might be called via a symlink.
# Pdftops done elsewhere.

proc set_progs {} {
  set scriptfile [file normalize [info script]]
  set syml 0
  if {$::tcl_platform(platform) eq "unix" && \
      ! [catch {file readlink [$scriptfile]}]} {
    set syml 1
  }
  set eproot [file dirname $scriptfile]
  if {$::tcl_platform(platform) eq "unix" && \
      ! [catch {file readlink $scriptfile}]} {
    # evaluate readlink from symlink directory
    set savedir [pwd]
    cd $eproot
    set eproot [file dirname [file normalize [file readlink $scriptfile]]]
    cd $savedir
  }
  if {! [file exists [file join $eproot "epspdf.tlu"]]} {
    # starpack edition?
    set starred 0
    foreach l [info loaded] {
      if {[lindex $l 1] eq "tclkitpath"} {
        set starred 1
        break
      }
    }
    if {$starred} {
      set eproot [file dirname [file normalize [info nameofexecutable]]]
      # here no testing for symlink
    }
  }
  set ::texlua "texlua"
  set ::epspdf_tlu [file join $eproot "epspdf.tlu"]
  if {! [file exists $::epspdf_tlu]} {
    tk_messageBox -type ok -icon error -message "Epspdf.tlu not found"
    exit 1
  }

  # no luck with other platforms
  if {$::tcl_platform(platform) eq "windows"} {
    wm iconbitmap . [file join $eproot "epspdf.ico"]
  }
}

set_progs

# call epspdf.tlu with parameter list $args (should be a list)
# Return codes success/failure
# We also need stdout output.
# Tcl idiom: res is a variable _name_.
# The upvar construct makes it a reference parameter.

#proc run_epspdf {res args} {
#  upvar $res result
#  if {$::ge_85} {
#    set failed [catch [linsert $args 0 \
#        exec -ignorestderr $::texlua $::epspdf_tlu --gui=gui] result]
#  } else {
#    set failed [catch [linsert $args 0 \
#        exec $::texlua $::epspdf_tlu --gui=gui] result]
#  }
#  if {$failed} {
#    # wm deiconify .log_t
#    tk_messageBox -icon error -type ok -message "Error; see log window"
#  }
#
#  # write to log textbox
#  write_log $result
#
#  # it is up to the caller to do anything else about failure or not.
#  # the user, at least, has been warned.
#  return [expr ! $failed]
#}

### configured and automatic settings ##################################

# is_prog used for checking configured viewers under non-osx unix
proc is_prog {x} {
  if {[expr {$::tcl_platform(platform) ne "unix"}]} {return 0}
  # avoid current directory except with explicit directory
  if {[expr {[string first "/" $x] >= 0 && \
                 [file executable [file normalize $x]]}]} {
    return 1
  }
  # also check search path
  set p [split $::env(PATH) ":"] ; # no need to accomodate msw
  foreach d $p {
    if {[expr {$d ne "" && [file executable [file join $d $x]]}]} {
      return 1
    }
  }
  return 0
}

# create a global empty settings array
array set ::settings [list]

set ::is_tl 1

# ask epspdf.tlu for currently configured settings.
# this does not include automatically configured or transient settings.
# the availability of viewers is handled here.
proc getsettings {} {
  if [catch {exec $::texlua $::epspdf_tlu --gui=config_w} set_str] {
    error "Epspdf configuration error: $set_str"
  }
  # write_log "settings from epspdf.tlu:\n$set_str\n"
  set l [split $set_str "\r\n"]
  if {$::tcl_platform(platform) eq "windows"} {
    set ::is_tl 1
    set settings(pdftops_prog) ""
  }
  foreach e $l {
    # puts "settings: $e"
    # $e is either a string "var = value"
    # or the empty string between <cr> and <lf>
    set i [string first "=" $e]
    if {$i>0} {
      # write_log $e
      set para [string trim [string range $e 0 [expr $i-1]]]
      set val [string trim [string range $e [expr $i+1] end]]
      if {$val eq "true"} {set val 1}
      if {$val eq "false"} {set val 0}
      if {$para eq "tl_w"} {
        set ::is_tl 0
        write_log "TL for Windows not detected by epspdf"
      } else {
        set ::settings($para) $val
        # write_log "setting $para is $val"
      }
    }
  }

  # unix: viewer settings
  # configured viewer, if valid, heads the list
  if {$::classic_unix} {

    set ::ps_viewers {}
    if {$::settings(ps_viewer) ne "" && [is_prog $::settings(ps_viewer)]} {
      lappend ::ps_viewers $::settings(ps_viewer)
    }
    foreach v {evince okular gv kghostview ghostview} {
      if {$v ne $::settings(ps_viewer) && [is_prog $v]} {
        lappend ::ps_viewers $v
      }
    }
    # puts [join $::ps_viewers " "]

    set ::pdf_viewers {}
    if {$::settings(pdf_viewer) ne "" && [is_prog $::settings(pdf_viewer)]} {
      lappend ::pdf_viewers $::settings(pdf_viewer)
    }
    foreach v {evince okular kpdf xpdf epdfview acroread \
        gv kghostview ghostview} {
      if {$v ne $::settings(pdf_viewer) && [is_prog $v]} {
        lappend ::pdf_viewers $v
      }
    }
    # puts [join $::pdf_viewers " "]

    if {[llength ::pdf_viewers] == 0 && [llength ::ps_viewers] != 0} {
      lappend ::pdf_viewers [lindex $::ps_viewers 0]
    }
    if {[llength ::pdf_viewers] == 0} {
      tk_messageBox -message "No viewers found"
    } elseif {[llength ::ps_viewers] == 0} {
      tk_messageBox -message "No PostScript viewers found"
    }
    if {[llength ::pdf_viewers] > 0} {
      set ::settings(pdf_viewer) [lindex $::pdf_viewers 0]
    }
    if {[llength ::ps_viewers] > 0} {
      set ::settings(ps_viewer) [lindex $::ps_viewers 0]
    }
  }
}

getsettings

proc write_settings {} {
  set s ""
  foreach el [array names ::settings] {
    set s "$s$el = $::settings($el)\n"
  }
  # write_log "\nsettings for epspdf.tlu\n$s\nend writing settings\n"
  if [catch {exec $::texlua $::epspdf_tlu --gui=config_r << $s} result] {
    error "Epspdf configuration error: $result"
  }
}

# directory and other file data

if {$::argc > 0 && [file isdirectory [lindex $::argv 0]]} {
  set ::gfile(dir) [lindex $::argv 0]
} elseif {[file isdirectory $::settings(default_dir)]} {
  set ::gfile(dir) $::settings(default_dir)
} else {
  set ::gfile(dir) $::env(HOME)
}
set ::gfile(path) ""
set ::gfile(type) ""
set ::gfile(name) ""
set ::gfile(npages) ""

# transient options
array set ::options [list gray "color" format "pdf" bbox 0 clean 1 \
  pages "single" page 1]

proc viewable {} {
  if { $::gfile(name) eq "" || ! [file exists $::gfile(path)] || \
      $::gfile(type) eq "other" || $::gfile(type) eq ""} {
    return 0
  } elseif {! $::classic_unix} {
    return 1
  } elseif {$::settings(ps_viewer) ne "" && [regexp {ps} $::gfile(type)]} {
    return 1
  } elseif {$::settings(pdf_viewer) ne "" && $::gfile(type) eq "pdf"} {
    return 1
  } else {
    return 0
  }
}

### groundwork for GUI #################################

# padding for buttons and frames
proc packb {b args} {
  eval [linsert $args 0 pack $b -padx 3 -pady 3]
  $b configure -border 2
}

proc packf {f args} {
  eval [linsert $args 0 pack $f -padx 3 -pady 3]
}

# dummy widget for vertical spacing
set idummy -1
proc spacing {w} {
  incr ::idummy
  pack [label $w.$::idummy -text " "]
}

# bold font
font create boldfont
# create dummy label widget to get at the default label font properties
label .dummy
font configure boldfont -family [font actual [.dummy cget -font] -family]
font configure boldfont -size [font actual [.dummy cget -font] -size]
font configure boldfont -weight bold
destroy .dummy

# bitmaps for mycombo (no combobox in core tk8.4)
image create bitmap dwnarrow -data {
#define dwnarrow_width 15
#define dwnarrow_height 10
static unsigned char dwnarrow_bits[] = {
  0x00, 0x00, 0x00, 0x00, 0xfc, 0x1f, 0xf8, 0x0f, 0xf0, 0x07, 0xe0, 0x03,
  0xc0, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00 };
}

image create bitmap uparrow -data {
#define uparrow_width 15
#define uparrow_height 10
static unsigned char uparrow_bits[] = {
   0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07,
   0xf8, 0x0f, 0xfc, 0x1f, 0x00, 0x00, 0x00, 0x00 };
}

# mycombo
if {$::ge_85} {
  proc update_combo {w vls} {
    upvar $vls vs
    set new [$w get]
    if {$new ni $vs} {
      if {[is_prog $new]} {
        set vs [linsert $vs 0 $new]
        $w configure -values $vs
      } else {
        tk_messageBox -title Error -icon error -message "$vl Not a program"
      }
    }
  }
} else {
  proc mycombo {w} {
    # entry widget and dropdown button
    frame $w
    frame $w.ef
    entry $w.ef.e -width 30 -borderwidth 1
    pack $w.ef.e -side left
    button $w.ef.b -image dwnarrow -command "toggle_list $w" -borderwidth 1
    pack $w.ef.b -side right
    pack $w.ef
    # 'drop-down' listbox; width should match entry widget above
    toplevel $w.lf -bd 0
    listbox $w.lf.l -yscrollcommand "$w.lf.s set" -height 4 -width 30 \
      -bd 1 -relief raised
    grid $w.lf.l -column 0 -row 0 -sticky news
    scrollbar $w.lf.s -command "$w.lf.l yview" -bd 1
    grid $w.lf.s -column 1 -row 0 -sticky ns
    grid columnconfigure $w.lf 0 -weight 1
    wm overrideredirect $w.lf 1
    wm transient $w.lf
    wm withdraw $w.lf
    # next two bindings:
    # final parameter: unmap/toggle listbox
    bind $w.lf.l <KeyRelease-space> {update_e %W [%W get active] 1}
    bind $w.lf.l <KeyRelease-Tab> {update_e %W [%W get active] 1}
    bind $w.lf.l <1> {update_e %W [%W index @%x,%y] 0}
    bind $w.lf.l <Double-1> {update_e %W [%W index @%x,%y] 1}
    bind $w.ef.e <Return> {update_l %W}
    bind $w.ef.e <Tab> {update_l %W}
    return $w
  }

  # toggle state of listbox.
  # this involves calculating the place where it should appear
  # and toggling the arrow image.
  proc toggle_list {w} {
    # $w.ef is the frame with the entry widget
    # $w.lf is the toplevel with the listbox
    # which needs to turn up right below $w.ef
     if {[wm state $w.lf] eq "withdrawn" || [wm state $w.lf] eq "iconified"} {
       set lfx [winfo rootx $w.ef]
       set lfy [expr [winfo rooty $w.ef] + [winfo height $w.ef]]
       wm geometry $w.lf [format "+%d+%d" $lfx $lfy]
       wm deiconify $w.lf
       $w.ef.b configure -image uparrow
     } else {
       wm withdraw $w.lf
       $w.ef.b configure -image dwnarrow
     }
  }

  # note: in tcl/tk 8.5, values of (some) widget variables can be accessed
  # directly and explicit use of upvar is unnecessary.

  # list => entry; optionally toggle list display
  proc update_e {v i toggle} {
    set w [winfo parent [winfo parent $v]]
    set lv [$w.lf.l cget -listvariable]
    upvar $lv l
    set tv [$w.ef.e cget -textvariable]
    upvar $tv t
    set t [lindex $l $i]
    if {$toggle} {toggle_list $w}
  }

  # entry => list
  proc update_l {v} {
    set w [winfo parent [winfo parent $v]]
    set tv [$w.ef.e cget -textvariable]
    upvar $tv t
    set lv [$w.lf.l cget -listvariable]
    upvar $lv l
    set found [lsearch $l $t]
    if { $found < 0} {
      set ok [$v validate]
      if {$ok} {
        lappend l $t
        set l [lsort $l]
      } else {
        tk_messageBox -message "Not a program"
      }
    }
    set the_index [lsearch $l $t]
    $w.lf.l see $the_index
    $w.lf.l activate $the_index
    wm withdraw $w.lf
    $w.ef.b configure -image dwnarrow
  }
}

# end mycombo

### and now the actual GUI ###################################

wm title . "PostScript- and pdf conversions"

# help toplevel

proc readhelp {} {
  .help_t.text configure -state normal
  set helpfile [regsub {\.tlu$} $::epspdf_tlu {.help}]
  if {[catch {set fid [open $helpfile r]}]} {
    .help_t.text insert end "No helpfile $helpfile found\n"
  } else {
    while {[gets $fid line] >= 0} {.help_t.text insert end "$line\n"}
    close $fid
  }
  .help_t.text configure -state disabled
  .help_t.text yview moveto 0
}

toplevel .help_t
wm title .help_t "EpsPdf help"
packb [button .help_t.done -text "Close" -command {wm withdraw .help_t}] \
 -side bottom -anchor e -padx 50
text .help_t.text -wrap word -width 60 -height 20 -setgrid 1 \
  -yscrollcommand ".help_t.scroll set"
scrollbar .help_t.scroll -command ".help_t.text yview"
pack .help_t.scroll -side right -fill y
pack .help_t.text -expand 1 -fill both
readhelp
.help_t.text configure -state disabled
wm withdraw .help_t

# log toplevel

toplevel .log_t
wm title .log_t "Epspdf log"
packb [button .log_t.b -text "Close" -command {wm withdraw .log_t}] \
 -side bottom -anchor e -padx 50
text .log_t.text -wrap word -relief flat -height 15 -width 60 \
     -setgrid 1 -yscrollcommand ".log_t.scroll set"
scrollbar .log_t.scroll -command ".log_t.text yview"
pack .log_t.scroll -side right -fill y
pack .log_t.text -expand 1 -fill both
.log_t.text configure -state disabled
wm withdraw .log_t

### configure toplevel ###################

toplevel .config_t
wm title .config_t "Configure epspdf"

# The settings array is edited directly in the configuration screen:
# for most control widgets, we need a global external variable anyway,
# so we better use the global that is already there.
# If the configuration screen is closed via the cancel button,
# the original values will be re-read from disk.
# If it is closed via the ok button, the new settings are written to disk.

# viewers (not on windows or osx)

if {$::classic_unix} {
  packf [frame .config_t.viewf] -ipadx 4 -fill x
  grid [label .config_t.viewf.title -font boldfont -text "Viewers"] \
    -row 0 -column 0 -sticky w
  grid [label .config_t.viewf.lb_pdf -text "Pdf"] \
    -row 1 -column 0 -sticky w
  grid [label .config_t.viewf.lb_ps -text "PostScript"] \
    -row 2 -column 0 -sticky w
  if {$::ge_85} {
    grid [ttk::combobox .config_t.viewf.pdf] -row 1 -column 1 -sticky e
    .config_t.viewf.pdf configure -values $::pdf_viewers
    .config_t.viewf.pdf configure -textvariable ::settings(pdf_viewer)
    bind .config_t.viewf.pdf <Return> {update_combo %W $::pdf_viewers}
    grid [ttk::combobox .config_t.viewf.ps] -row 2 -column 1 -sticky e
    .config_t.viewf.ps configure -values $::ps_viewers
    .config_t.viewf.ps configure -textvariable ::settings(ps_viewer)
    bind .config_t.viewf.ps <Return> {update_combo %W $::ps_viewers}
  } else {
    grid [mycombo .config_t.viewf.pdf] -row 1 -column 1 -sticky e
    .config_t.viewf.pdf.lf.l configure -listvariable ::pdf_viewers
    .config_t.viewf.pdf.ef.e configure -textvariable ::settings(pdf_viewer)
    .config_t.viewf.pdf.ef.e configure -vcmd {is_prog %P} -validate none
    grid [mycombo .config_t.viewf.ps] -row 2 -column 1 -sticky e -pady 4
    .config_t.viewf.ps.lf.l configure -listvariable ::ps_viewers
    .config_t.viewf.ps.ef.e configure -textvariable ::settings(ps_viewer)
    .config_t.viewf.ps.ef.e configure -vcmd {is_prog %P} -validate none
  }
  grid columnconfigure .config_t.viewf 1 -weight 1 -pad 2

  spacing .config_t
}

# settings for conversion to pdf

packf [frame .config_t.pdff] -ipadx 4 -fill x
pack [label .config_t.pdff.title -font boldfont -text "Conversion to pdf"] \
  -anchor w

pack [label .config_t.pdff.l_target -text "Target use"] -anchor w
pack [frame .config_t.pdff.f_targets] -fill x
foreach t {default printer prepress screen ebook} {
  pack [radiobutton .config_t.pdff.f_targets.$t \
      -variable ::settings(pdf_target) \
      -text $t -value $t] -side left -padx 2 -pady 4 -anchor w
}

pack [label .config_t.pdff.l_version -text "Pdf version"] -anchor w
pack [frame .config_t.pdff.f_version] -fill x
foreach t {1.2 1.3 1.4 default} {
  regsub {\.} $t _ tp ; # replace dot in name: dots are path separators!
  pack [radiobutton .config_t.pdff.f_version.$tp \
      -variable ::settings(pdf_version) \
      -text $t -value $t] -side left -padx 2 -pady 4 -anchor w
}

#pack [label .config_t.pdff.l_gs \
#  -text "Custom Ghostscript/ps2pdf parameters"] -anchor w
#pack [entry .config_t.pdff.e_gs -border 1] -fill x -padx 2 -pady 2
#.config_t.pdff.e_gs configure -textvariable settings(pdf_custom)

spacing .config_t

# settings for conversion to EPS and PostScript

packf [frame .config_t.psf] -ipadx 4 -fill x
pack [label .config_t.psf.l_ps -text "Conversion to EPS and PostScript" \
          -font boldfont] -anchor w
if {! $::is_tl} {
  if {[string tolower [string range $::settings(pdftops_prog) end-3 end]] ne \
          ".exe"} {set ::settings(pdftops_prog) ""}
  pack [label .config_t.psf.l_pdftops -text "Find pdftops"] -anchor w
  pack [frame .config_t.psf.findf] -anchor w
  pack [entry .config_t.psf.findf.e -width 40] -side left -padx 4
  .config_t.psf.findf.e configure -textvariable ::settings(pdftops_prog)
  packb [button .config_t.psf.findf.b -text "Browse..." \
     -command find_pdftops] -side left
}

proc find_pdftops {} {
  set try [tk_getOpenFile -title "Find pdftops.exe" \
       -filetypes {{"Programs" {.exe}}} -initialdir "c:/"]
  if {$try ne ""} {
    .config_t.psf.findf.e delete 0 end
    .config_t.psf.findf.e insert 0 $try
  }
}

pack [checkbutton .config_t.psf.c \
          -text "Use pdftops if available (recommended)"] -anchor w
.config_t.psf.c configure -variable ::settings(use_pdftops) \
  -onvalue 1 -offvalue 0

spacing .config_t

## hires boundingbox setting
#
#packf [frame .config_t.hiresf] -ipadx 4 -fill x
#pack [label .config_t.hiresf.title -font boldfont -text "Hires BoundingBox"] \
#  -anchor w
#pack [label .config_t.hiresf.l -text "Uncheck to prevent clipping"] \
#  -anchor w
#
#pack [checkbutton .config_t.hiresf.c \
#          -text "Use hires boundingbox if possible"] -anchor w
#.config_t.hiresf.c configure \
#  -variable ::settings(ignore_hires_bb) -onvalue 0 -offvalue 1
#
#spacing .config_t

# buttons for closing the configuration screen

pack [frame .config_t.buttonsf] -fill x
packb [button .config_t.buttonsf.done -text "Done" -command putsettings] \
 -side right
packb [button .config_t.buttonsf.cancel -text "Cancel" \
 -command cancelsettings] -side right

wm transient .config_t
wm overrideredirect .config_t
wm withdraw .config_t

proc edit_settings {} {
  wm deiconify .config_t
  raise .config_t
  grab set .config_t
}

# store new settings
proc putsettings {} {
  if {$::classic_unix && ! $::ge_85} {
    wm withdraw .config_t.viewf.pdf.lf
    wm withdraw .config_t.viewf.ps.lf
  }
  wm withdraw .config_t
  grab release .config_t
  write_settings
}

proc cancelsettings {} {
  if {$::classic_unix && ! $::ge_85} {
    wm withdraw .config_t.viewf.pdf.lf
    wm withdraw .config_t.viewf.ps.lf
  }
  wm withdraw .config_t
  grab release .config_t
  # re-read config file / reg entries
  getsettings
}

### main screen #############################

proc show_w {w} {
  wm deiconify $w
  raise $w
}

# buttons to call up configure-, log- and help screens

pack [frame .topf] -fill x
packb [button .topf.config_t -text "Configure" \
          -command edit_settings] -side left
packb [button .topf.help_t -text "Help" \
 -command {show_w .help_t}] -side right
packb [button .topf.logb -text "Show log" -command {show_w .log_t}] \
  -side right -anchor w

# file info in grid layout

packf [frame .infof -relief sunken -border 1] -fill x
grid [label .infof.dir_label -text "Directory" -anchor w] \
 -row 1 -column 1 -sticky w
grid [label .infof.dir_value -textvariable ::gfile(dir) -anchor w] \
 -row 1 -column 2 -sticky w

grid [label .infof.name_label -text "File" -anchor w] \
 -row 2 -column 1 -sticky w
grid [label .infof.name_value -textvariable ::gfile(name) -anchor w] \
 -row 2 -column 2 -sticky w

grid [label .infof.type_label -text "Type" -anchor w] \
 -row 3 -column 1 -sticky w
grid [label .infof.type_value -textvariable ::gfile(type) -anchor w] \
 -row 3 -column 2 -sticky w

grid [label .infof.npages_label -text "Pages" -anchor w] \
 -row 4 -column 1 -sticky w
grid [label .infof.npages_value -textvariable ::gfile(npages) -anchor w] \
 -row 4 -column 2 -sticky w

grid columnconfigure .infof 1 -weight 1 -pad 2
grid columnconfigure .infof 2 -weight 3 -pad 2

spacing .

# conversion options

pack [frame .optsf] -fill x

# grayscaling
pack [frame .optsf.gray] -side left -anchor nw
pack [label .optsf.gray.l -text "Grayscaling"] -anchor w
pack [radiobutton .optsf.gray.off -text "No color conversion" \
          -variable ::options(gray) -value "color"] -anchor w
pack [radiobutton .optsf.gray.gray -text "Grayscale" \
          -variable ::options(gray) -value "gray"] -anchor w
#pack [radiobutton .optsf.gray.gRAY -text "Try harder to grayscale" \
#          -variable ::options(gray) -value "gRAY"] -anchor w

# output format
pack [label .optsf.format] -side right -anchor ne
pack [label .optsf.format.l -text "Output format"] -anchor w
pack [radiobutton .optsf.format.pdf -text "pdf" -command set_widget_states \
          -variable ::options(format) -value "pdf"] -anchor w
pack [radiobutton .optsf.format.eps -text "eps" -command set_widget_states \
          -variable ::options(format) -value "eps"] -anchor w
pack [radiobutton .optsf.format.ps -text "ps" -command set_widget_states \
          -variable ::options(format) -value "ps"] -anchor w

spacing .

# boundingbox
pack [checkbutton .bbox -text "Compute tight boundingbox" \
 -variable ::options(bbox) -command set_widget_states] -anchor w

# page selection
pack [frame .pagesf] -fill x
pack [radiobutton .pagesf.all -text "Convert all pages" \
   -variable ::options(pages) -value "all" -command set_widget_states] \
   -side left
pack [radiobutton .pagesf.single -text "Page:" \
   -variable ::options(pages) -value "single" -command set_widget_states] \
   -side left
pack [entry .pagesf.e -width 6 -textvariable ::options(page)] -side left
#.pagesf.e configure -vcmd {page_valid %W} -validate focusout \
#  -invcmd "focusAndFlash %W [.pagesf.e cget -fg] [.pagesf.e cget -bg]"
.pagesf.e configure -vcmd {page_valid %W} -validate focusout \
  -invcmd "see_red %W"

spacing .

# temp files
pack [checkbutton .clean -text "Remove temp files" \
 -variable ::options(clean)] -anchor w

proc focusAndFlash {w fg bg {count 9}} {
  focus $w
  if {$count<1} {
    $w configure -foreground $fg -background $bg
  } else {
    if {$count%2} {
      $w configure -foreground $bg -background $fg
    } else {
      $w configure -foreground $fg -background $bg
    }
    after 200 [list focusAndFlash $w $fg $bg [expr {$count-1}]]
  }
}

proc see_red {w} {
  focus $w
  set orig [$w cget -bg]
  $w configure -bg red
  update idletasks
  after 1000
  $w configure -bg $orig
  update idletasks
}

proc page_valid {w} {
  # w entry widget
  set p [$w get]
  set isvalid 1
  #puts "check pageno $p"
  if {! [string is integer $p] || $p<=0} {
    set isvalid 0
  } elseif {$::gfile(npages) ne "" && [scan $p "%d"] > $::gfile(npages)} {
    set isvalid 0
  }
  return $isvalid
}

# end conversion options

pack [label .status -justify left] -side bottom -anchor w -fill x -expand 1

# main buttons

pack [frame .bottomf] -side bottom -fill x
packb [button .bottomf.view -text "View" -command view] -side left
packb [button .bottomf.open -text "Open" -command openDialog] \
 -side left -padx 2
packb [button .bottomf.convert -text "Convert and save..." \
 -command saveDialog] -side left -padx 2
packb [button .bottomf.done -text "Done" -command exit] -side right

proc view {} {
  if {! [viewable]} {
    tk_messageBox -icon warning -type ok \
      -message "No viewer for $::gfile(path)"
    return
  }
  if {$::tcl_platform(os) eq "Darwin"} {
    catch {exec open $::gfile(path) &}
  } elseif {$::tcl_platform(platform) ne "unix"} {
    # use shortname to sidestep quoting problems
    catch {exec cmd /x /c start [file attributes $::gfile(path) -shortname]}
  } else {
    if {$::gfile(type) eq "pdf"} {
      catch {exec $::settings(pdf_viewer) $::gfile(path) &}
    } elseif {$::gfile(type) ne "other" && $::gfile(type) ne ""} {
       catch {exec $::settings(ps_viewer) $::gfile(path) &}
    }
  }
}

proc openDialog {} {
  set types {
    {"PostScript and pdf" {.eps .epi .epsi .ps .prn .pdf}}
    {"Encapsulated PostScript" {.eps .epi .epsi}}
    {"General PostScript" {.ps .prn}}
    {"Pdf" {.pdf}}
    {"All files" *}
  }
  if {[file isdirectory $::gfile(dir)]} {
      set try [tk_getOpenFile -filetypes $types -initialdir $::gfile(dir)]
  } else {
      set try [tk_getOpenFile -filetypes $types]
  }
  if {$try != ""} {
    set ::gfile(path) [file normalize $try]
    set ::gfile(dir) [file dirname $::gfile(path)]
    set ::gfile(name) [file tail $::gfile(path)]
    set ::gfile(type) ""
    if {! [catch {exec $::texlua $::epspdf_tlu --gui=gui -i $::gfile(path)} \
          result]} {
      # parse output
      regexp {has type (\w+)(?:.+ (\d+) pages)?\.} $result \
        mtc ::gfile(type) ::gfile(npages)
      if {[regexp {^eps} $::gfile(type)]} {set ::gfile(npages) 1}
    }
    if {$::gfile(type) eq ""} {
      # unsupported type
      tk_messageBox -message "$try: unreadable or unsupported type" \
          -title "Error" -icon error
    }
    set ::settings(default_dir) $::gfile(dir)
    putsettings
  }
  set_widget_states
}

proc saveDialog {} {
  if {$::options(format) eq "pdf"} {
    set types {{"Pdf" {.pdf}}}
  } elseif {$::options(format) eq "ps"} {
    set types {{"PostScript" {.ps}}}
  } elseif {$::options(format) eq "eps"} {
    set types {{"Encapsulated PostScript" {.eps}}}
  }
  set try [tk_getSaveFile -filetypes $types -initialdir $::gfile(dir)]
  if {$try != ""} {
    # fix extension
    set try [file normalize $try]
    set idot [string last "." [file tail $try]]
    if {$idot == -1} {
      append try ".$::options(format)"
    } else {
      # don't bother checking whether the extension is already correct
      set try [string range $try 0 [string last "." $try]]
      append try $::options(format)
    }
    set try [file normalize $try]
    # epspdf can read persistent options from configuration.
    # only options from the options array need to be converted to parameters.
    set args [list]
    if {$::options(gray) eq "gray"} {
      lappend args "-g"
    }
    if {$::options(bbox)} {lappend args "-b"}
    if {! $::options(clean)} {lappend args "-d"}
    if {$::options(pages) eq "single"} {
      if {$::options(page) eq ""} {set ::options(page) 1}
      lappend args "-p" $::options(page)
    }
    lappend args $::gfile(path) $try
    .status configure -text "Working..." -justify "left"
    foreach b {view open convert done} {
      .bottomf.$b configure -state disabled
    }
    update idletasks; # force immediate redisplay main window

    if {$::ge_85} {
      set failed [catch [linsert $args 0 \
          exec -ignorestderr $::texlua $::epspdf_tlu --gui=gui] result]
    } else {
      set failed [catch [linsert $args 0 \
          exec $::texlua $::epspdf_tlu --gui=gui] result]
    }
    write_log $result
    if {$failed} {
      tk_messageBox -icon error -type ok -message "Error; see log window"
    } else {
      set ::gfile(path) [file normalize $try]
      set ::gfile(dir) [file dirname $::gfile(path)]
      set ::gfile(type) $::options(format)
      set ::gfile(name) [file tail $::gfile(path)]
      # parse result output
      regexp { is (\w+)(?: with (\d+) pages)?[\r\n]*$} \
        [string range $result [string last "File type of" $result] end] \
        mtc ::gfile(type) ::gfile(npages)
      if {$::gfile(type) eq "eps"} {set ::gfile(npages) 1}
      set ::settings(default_dir) $::gfile(dir)
      putsettings
      set ::options(page) 1
    }
    .status configure -text ""
    foreach b {view open convert done} {
      .bottomf.$b configure -state normal
    }
    focus .bottomf.view
    set_widget_states
  }
}

proc set_widget_states {} {
  # states (normal/disabled) for various widgets.
  # their values should already be mutually consistent.
  # widgets concerned:
  # view / save
  # output format / bbox / single page / page no.

  # view
  if {[viewable]} {
    .bottomf.view configure -state normal
  } else {
    .bottomf.view configure -state disabled
  }

  # convert
  .bottomf.convert configure -state normal
  if {$::gfile(path) eq "" || ! [file exists $::gfile(path)]} { \
    .bottomf.convert configure -state disabled
  }
  if {$::gfile(type) eq "other"} {
    .bottomf.convert configure -state disabled
  }
  if {$::gfile(npages) ne "" && $::options(pages) eq "single" && \
          $::options(page) > $::gfile(npages)} {
    .bottomf.convert configure -state disabled
  }

  # type
  if {[regexp {ps|pdf} $::gfile(type)]} {
    foreach f {pdf eps ps} {.optsf.format.$f configure -state normal}
  } else {
    foreach f {pdf eps ps} {.optsf.format.$f configure -state disabled}
  }

  # pages and page
  foreach f {all single} {.pagesf.$f configure -state normal}
  if {$::options(format) eq "eps"} {
    .pagesf.all configure -state disabled
    set ::options(pages) "single"
  }
  if {$::options(format) eq "ps"} {
    .pagesf.single configure -state disabled
    set ::options(pages) "all"
  }
  if {$::options(pages) eq "all"} {
    .pagesf.e configure -state disabled
  } else {
    .pagesf.e configure -state normal
  }

  # boundingbox
  if {$::options(format) eq "eps"} {
    .bbox configure -state normal
  } elseif {$::options(pages) eq "all"} {
    .bbox configure -state disabled
    set ::options(bbox) 0
  } else {
    .bbox configure -state normal
  }
  update idletasks
}

set_widget_states
