#!/xhbin/wish -f # A GUI BibTeX entry program. # Copyright 1999 Stephen Mann # This program may be freely used for non-profit use. # This program may be copied and modified as long as this copyright # notice remains intact. All other rights reserved. # $Id: bibenter,v 3.2 2000/01/01 23:10:54 smann Exp $ set modFlag 0 # Update the status string proc Status {s} { global status set status "$s" update } set book "bibname:author:editor:title:edition:publisher:address:volume:series:month:year:ISBN:LCCN:note" set article "bibname:author:title:journal:year:volume:number:pages:month:note" set booklet "bibname:title:author:howpublished:address:month:year:note" set inbook "bibname:author:editor:title:chapter:pages:publisher:year:volume:number:series:type:address:edition:month:ISBN:note" set incollection "bibname:author:title:booktitle:publisher:year:editor:volume:number:series:type:chapter:pages:address:edition:month:ISBN:note" set inproceedings "bibname:author:title:booktitle:year:editor:volume:number:series:pages:address:month:organization:publisher:note" set conference "bibname:author:title:booktitle:year:editor:volume:number:series:pages:address:month:organization:publisher:note" set manual "bibname:title:author:organization:address:edition:month:year:note" set mastersthesis "bibname:author:title:school:year:type:address:month:note" set misc "bibname:author:title:howpublished:month:year:note" set phdthesis "bibname:author:title:school:year:type:address:month:note" set proceedings "bibname:title:year:editor:volume:number:series:address:month:organization:publisher:note" set techreport "bibname:author:title:institution:year:type:number:address:month:note" set unpublished "bibname:author:title:note:month:year" set bib(Type) book set bib(fields) $book # Display text in the text window. The text can come either # from a file or from a pipe proc ScrolledText { fn title } { global text textlabel if [catch {open "$fn" r} fi] { set textlabel "Cannot open $fn for reading" return } set textlabel "Loading..." update $text.text delete 1.0 end $text.text insert 1.0 [read $fi] if ![regexp {|} $fn] { close $fi } set textlabel "$title" } proc HelpText { width height } { set help [toplevel .help -borderwidth 10] set f [frame $help.f] # The setgrid setting allows the window to be resized. text $f.text -width $width -height $height \ -setgrid true -wrap none \ -xscrollcommand [list $f.xscroll set] \ -yscrollcommand [list $f.yscroll set] scrollbar $f.xscroll -orient horizontal \ -command [list $f.text xview] scrollbar $f.yscroll -orient vertical \ -command [list $f.text yview] pack $f.xscroll -side bottom -fill x pack $f.yscroll -side right -fill y # The fill and expand are needed when resizing. pack $f.text -side left -fill both -expand true pack $f -side top -fill both -expand true $f.text insert end "\ bibenter is a program for creating and editing BibTeX files.\n\ \n\ Menus\n \ File\n \ Items for opening and saving BibTeX files, as well as quiting\n \ Bibtex\n \ Items for clearing, entering, and searching a loaded BibTeX file\n\ \n\ Status\n \ Indicates when you've entered data\n\ \n\ Bibfile\n \ Name of BibTeX file. Save button will save current data to this file\n\ \n\ Entry Region\n \ Enter\n \ Enter current BibTeX entry, over writing displayed entry if bibname matches\n \ Delete\n \ Delete currently displayed BibTeX entry\n \ Clear\n \ Clear fields \n \ The menu button at the top center allows you to select the type of\n \ BibTeX entry\n\ \n\ Main window\n \ The BibTeX file is displayed here. You may click and edit data here\n \ directly. If you double click with the right mouse button, the BibTeX\n \ entry you select will be highlighted and displayed in the entry fields\n \ above.\n\ " return $f.text } proc CommandEntry {name label width command args } { frame $name label $name.label -text $label -width $width -anchor w eval {entry $name.entry -width 40 -relief sunken} $args pack $name.label -side left pack $name.entry -side right -fill x -expand true bind $name.entry $command return $name.entry } proc SetDefaults {} { global bib foreach e [split $bib(fields) :] { set bib($e) "" } } proc EnterIt {} { global bib main text textlabel modFlag # Check to see if selected Entry is same as new entry set t [split [$text.text tag nextrange Entry 0.0]] set findex 1 if {[llength $t]==2} { set st [$text.text search -back @ [lindex $t 1]] $text.text mark set stEntry $st set type [$text.text get $st "stEntry lineend"] if {[llength $t] == 2} { if {[regexp {@[ ]*([a-zA-Z]+)[ ]*\{[ ]*(.+),} $type dummy type bname] == 1} { if {[string compare $bname $bib(bibname)] == 0} { $text.text delete [lindex $t 0] [lindex $t 1] $text.text mark set insert $st set findex 0 } } } } set first [$text.text index insert] foreach e [split $bib(fields) :] { if { [string compare $e bibname] == 0 } { $text.text insert insert "@$bib(Type)\{$bib(bibname),\n" } elseif {[string length $bib($e)] != 0 } { $text.text insert insert " $e = {$bib($e)},\n" } } $text.text insert insert "\}\n" $text.text tag delete Entry $text.text tag add Entry $first insert $text.text tag configure Entry -relief raised -borderwidth 2 Status "Entered $bib(bibname)" set textlabel "Biblist modified" set modFlag 1 $text.text see insert } proc ButtonLabel {name label width args } { frame $name label $name.label -text $label -width $width -anchor w menubutton $name.entry -text --- -anchor w -menu \ $name.entry.menu -relief raised pack $name.label -side left pack $name.entry -side right -fill x -expand true return $name.entry } proc BuildBibTypeList {menu} { $menu add command -label Book -command "setBibType book" $menu add command -label Article -command "setBibType article" $menu add command -label Booklet -command "setBibType booklet" $menu add command -label Inbook -command "setBibType inbook" $menu add command -label Incollection -command "setBibType incollection" $menu add command -label Inproceedings -command "setBibType inproceedings" $menu add command -label Conference -command "setBibType conference" $menu add command -label Manual -command "setBibType manual" $menu add command -label Mastersthesis -command "setBibType mastersthesis" $menu add command -label Misc -command "setBibType misc" $menu add command -label Phdthesis -command "setBibType phdthesis" $menu add command -label Proceedings -command "setBibType proceedings" $menu add command -label Techreport -command "setBibType techreport" $menu add command -label Unpublished -command "setBibType unpublished" } proc setBibType {type} { global bib enter upvar #0 $type utype SetDefaults $enter.type.mb config -text $type set bib(Type) $type set bib(fields) $utype ConfigureIt $utype } proc ConfigureIt {type} { global enter bibfile set i 1 foreach e [split $type :] { $enter.e$i.label config -text $e $enter.e$i.entry config -textvar bib($e) $enter.e$i.entry config -state normal $enter.e$i.entry config -relief sunken bind $enter.e$i.entry "focus $enter.e[expr $i+1].entry" incr i } bind $enter.e[expr $i-1].entry {EnterIt} for {} {$i < 19} {incr i} { $enter.e$i.label config -text "" $enter.e$i.entry config -textvar bib(none) $enter.e$i.entry config -state disabled $enter.e$i.entry config -relief flat set bib(none) xxx } } proc OpenBibFile {f {append 0}} { global fileselect text bibfile textlabel modFlag if {($modFlag == 1) && ($append==0)} { global ok set p [toplevel .prompt -borderwidth 10] message $p.msg -text "Data not saved. Really load new file?" set b [frame $p.buttons -bd 10] pack $p.msg $p.buttons -side top -fill x button $b.ok -text OK -command {set ok 1} button $b.cancel -text Cancel -command {set ok 0} pack $b.ok -side left pack $b.cancel -side right tkwait variable ok destroy .prompt if {$ok == 0} { return } } if {[string length $f]==0} { fileselect "Open Bib File" {} 0 if {[string length $fileselect(path)]==0} { return; } set bibfile $fileselect(path) } if {[file exists $bibfile] == 0} { set textlabel "File $bibfile does not exist. Treating as new bibfile" if {$append == 0} { $text.text delete 1.0 end } return } if [catch {open "$bibfile" r} fi] { set textlabel "Cannot open $bibfile." return } set textlabel "Loading" update if {$append == 0} { $text.text delete 1.0 end $text.text insert 1.0 [read $fi] set modFlag 0 } else { $text.text insert end [read $fi] set modFlag 1 } close $fi set textlabel "Bibfile $bibfile loaded" } proc SaveBibFile {f} { global fileselect text bibfile textlabel modFlag if {[string length $f]==0} { fileselect "Save to..." {} 0 if {[string length $fileselect(path)]==0} { return; } set bibfile $fileselect(path) } if [catch {open "$bibfile" w} fi] { set textlabel "Cannot write to $bibfile. Biblist not saved." return } set textlabel "Writing" update puts -nonewline $fi [$text.text get 1.0 end] close $fi set textlabel "Bibfile $bibfile written" set modFlag 0 } proc FindIt {f str} { global text textlabel searchcase set plen 0 if {$searchcase == 1} { set si [$text.text search -count plen -- "$str" insert] } else { set si [$text.text search -nocase -count plen -- "$str" insert] } if {[string length $si] == 0} { $f.msg config -text "pattern $str not found" return } $f.msg config -text "" $text.text see $si set sl [split $si "."] set se "[lindex $sl 0].[expr [lindex $sl 1]+$plen]" $text.text tag remove sel 1.0 end $text.text tag add sel $si $se $text.text mark set insert $se } set searchcase 1 proc Search {} { global ok global searchstring global searchcase set ok 1 set f [toplevel .search -borderwidth 10] message $f.msg -width 200 entry $f.entry -textvariable searchstring bind $f.entry {FindIt .search $searchstring} checkbutton $f.cb -text "Case Sensitive" -variable searchcase set b [frame $f.buttons -bd 10] pack $f.msg $f.entry $f.cb $f.buttons -side top -fill x button $b.search -text Search -command {set ok 1} button $b.cancel -text Cancel -command {set ok 0} pack $b.search $b.cancel while {$ok == 1} { tkwait variable ok if {$ok == 1} { FindIt $f $searchstring } } destroy $f } # Dialog chapter proc fileselectResources {} { # path is used to enter the file name option add *Fileselect*path.relief sunken startup option add *Fileselect*path.background white startup option add *Fileselect*path.foreground black startup # Text for the label on pathname entry option add *Fileselect*l.text File: startup # Text for the OK and Cancel buttons option add *Fileselect*ok*text OK startup option add *Fileselect*ok*underline 0 startup option add *Fileselect*cancel.text Cancel startup option add *Fileselect*cancel.underline 0 startup # Size of the listbox option add *Fileselect*list.width 20 startup option add *Fileselect*list.height 10 startup } # fileselect returns the selected pathname, or {} proc fileselect {{why "File Selection"} {default {}} {mustExist 1} } { global fileselect set t [toplevel .fileselect -bd 4 -class Fileselect] fileselectResources message $t.msg -aspect 1000 -text $why pack $t.msg -side top -fill x # Create a read-only entry for the current directory set fileselect(dirEnt) [entry $t.dir -width 15 \ -relief flat -state disabled] pack $t.dir -side top -fill x # Create an entry for the pathname # The value is kept in fileselect(path) frame $t.top label $t.top.l -padx 0 set e [entry $t.top.path \ -textvariable fileselect(path)] pack $t.top -side top -fill x pack $t.top.l -side left pack $t.top.path -side right -fill x -expand true # Create a listbox to hold the directory contents set lb [listbox $t.list \ -yscrollcommand [list $t.scroll set]] scrollbar $t.scroll -command [list $lb yview] # Create the OK and Cancel buttons # The OK button has a rim to indicate it is the default frame $t.buttons -bd 10 frame $t.buttons.ok -bd 2 -relief sunken set ok [button $t.buttons.ok.b \ -command fileselectOK] set can [button $t.buttons.cancel \ -command fileselectCancel] # Pack the list, scrollbar, and button box # in a horizontal stack below the upper widgets pack $t.list -side left -fill both -expand true pack $t.scroll -side left -fill y pack $t.buttons -side left -fill both pack $t.buttons.ok $t.buttons.cancel \ -side top -padx 10 -pady 5 pack $t.buttons.ok.b -padx 4 -pady 4 fileselectBindings $t $e $lb $ok $can # Initialize variables and list the directory if {[string length $default] == 0} { set fileselect(path) {} set dir [pwd] } else { set fileselect(path) [file tail $default] set dir [file dirname $default] } set fileselect(dir) {} set fileselect(done) 0 set fileselect(mustExist) $mustExist # Wait for the listbox to be visible so # we can provide feedback during the listing tkwait visibility .fileselect.list fileselectList $dir tkwait variable fileselect(done) destroy $t return $fileselect(path) } proc fileselectBindings { t e lb ok can } { # t - toplevel # e - name entry # lb - listbox # ok - OK button # can - Cancel button # Elimate the all binding tag because we # do our own focus management foreach w [list $e $lb $ok $can] { bindtags $w [list $t [winfo class $w] $w] } # Dialog-global cancel binding bind $t fileselectCancel # Entry bindings bind $e fileselectOK bind $e fileselectComplete # A single click, or , puts the name in the entry # A double-click, or , selects the name bind $lb "fileselectTake $%W ; focus $e" bind $lb \ "fileselectClick %W %y ; focus $e" bind $lb "fileselectTake %W ; fileselectOK" bind $lb \ "fileselectClick %W %y ; fileselectOK" # Focus management. # or selects the name. bind $e "focus $lb ; $lb select set 0" bind $lb "focus $e" # Button focus. Extract the underlined letter # from the button label to use as the focus key. foreach but [list $ok $can] { set char [string tolower [string index \ [$but cget -text] [$but cget -underline]]] bind $t "focus $but ; break" } bind $ok "focus $can" bind $can "focus $ok" # Set up for type in focus $e } # Changed to restrict to .bib files proc fileselectList { dir {files {}} } { global fileselect # Update the directory display set e $fileselect(dirEnt) $e config -state normal $e delete 0 end $e insert 0 $dir $e config -state disabled # scroll to view the tail end $e xview moveto 1 .fileselect.list delete 0 end set fileselect(dir) $dir if ![file isdirectory $dir] { .fileselect.list insert 0 "Bad Directory" return } .fileselect.list insert 0 Listing... update idletasks .fileselect.list delete 0 if {[string length $files] == 0} { # List the directory and add an # entry for the parent directory set files [glob -nocomplain $fileselect(dir)/*] .fileselect.list insert end ../ } # Sort the directories to the front set dirs {} set others {} foreach f [lsort $files] { if [file isdirectory $f] { lappend dirs [file tail $f]/ } elseif {[string match *.bib $f] == 1} { lappend others [file tail $f] } } foreach f [concat $dirs $others] { .fileselect.list insert end $f } } proc fileselectOK {} { global fileselect # Handle the parent directory specially if {[regsub {^\.\./?} $fileselect(path) {} newpath] != 0} { set fileselect(path) $newpath set fileselect(dir) [file dirname $fileselect(dir)] fileselectOK return } set path [string trimright $fileselect(dir)/$fileselect(path) /] if [file isdirectory $path] { set fileselect(path) {} fileselectList $path return } if [file exists $path] { set fileselect(path) $path set fileselect(done) 1 return } # Neither a file or a directory. # See if glob will find something if [catch {glob $path} files] { # No, perhaps the user typed a new # absolute pathname if [catch {glob $fileselect(path)} path] { # Nothing good if {$fileselect(mustExist)} { # Attempt completion fileselectComplete } elseif [file isdirectory \ [file dirname $fileselect(path)]] { # Allow new name set fileselect(done) 1 } return } else { # OK - try again set fileselect(dir) [file dirname $fileselect(path)] set fileselect(path) [file tail $fileselect(path)] fileselectOK return } } else { # Ok - current directory is ok, # either select the file or list them. if {[llength [split $files]] == 1} { set fileselect(path) $files fileselectOK } else { set fileselect(dir) [file dirname [lindex $files 0]] fileselectList $fileselect(dir) $files } } } proc fileselectCancel {} { global fileselect set fileselect(done) 1 set fileselect(path) {} } proc fileselectClick { lb y } { # Take the item the user clicked on global fileselect set fileselect(path) [$lb get [$lb nearest $y]] } proc fileselectTake { lb } { # Take the currently selected list item global fileselect set fileselect(path) [$lb get [$lb curselection]] } proc fileselectComplete {} { global fileselect # Do file name completion # Nuke the space that triggered this call set fileselect(path) [string trim $fileselect(path) \t\ ] # Figure out what directory we are looking at # dir is the directory # tail is the partial name if {[string match /* $fileselect(path)]} { set dir [file dirname $fileselect(path)] set tail [file tail $fileselect(path)] } elseif [string match ~* $fileselect(path)] { if [catch {file dirname $fileselect(path)} dir] { return ;# Bad user } set tail [file tail $fileselect(path)] } else { set path $fileselect(dir)/$fileselect(path) set dir [file dirname $path] set tail [file tail $path] } # See what files are there set files [glob -nocomplain $dir/$tail*] if {[llength [split $files]] == 1} { # Matched a single file set fileselect(dir) $dir set fileselect(path) [file tail $files] } else { if {[llength [split $files]] > 1} { # Find the longest common prefix set l [expr [string length $tail]-1] set miss 0 # Remember that files has absolute paths set file1 [file tail [lindex $files 0]] while {!$miss} { incr l if {$l == [string length $file1]} { # file1 is a prefix of all others break } set new [string range $file1 0 $l] foreach f $files { if ![string match $new* [file tail $f]] { set miss 1 incr l -1 break } } } set fileselect(path) [string range $file1 0 $l] } fileselectList $dir $files } } proc FindEntry {} { global text bib set st [$text.text search -back @ current] $text.text mark unset stEntry $text.text mark set stEntry $st set type [$text.text get $st "stEntry lineend"] regexp {@[ ]*([a-zA-Z]+)[ ]*\{[ ]*(.+),} $type dummy type bname set type [string tolower $type] setBibType $type set bib(bibname) $bname set i 1 set i2 [expr $i+1] while {[regexp "^\}" [$text.text get "stEntry +$i line" "stEntry +$i2 line"]] \ == 0 } { set eline($i) [string trimright \ [$text.text get "stEntry +$i line" "stEntry +$i2 line"]] set i [expr $i+1] set i2 [expr $i2+1] } if {[regexp {,[ ]*$} $eline([expr $i-1])] == 0} { set eline([expr $i-1]) "$eline([expr $i-1])," } set prev "" for {set j 1} {$j < $i} {incr j} { set line [string trimleft $eline($j)] set line "$prev $line" if {[regexp {[ ]*([a-z]+)[ ]*=[ ]*\{(.*)\},} $line dummy f v]==1} { set bib($f) $v set prev "" } elseif {[regexp {[ ]*([a-z]+)[ ]*=[ ]*\"(.*)\",} $line dummy f v]==1} { set bib($f) $v set prev "" } elseif {[regexp {[ ]*([a-z]+)[ ]*=[ ]*([0-9]+) *,} $line dummy f v]==1} { set bib($f) $v set prev "" } else { set prev "$line\n" } } $text.text tag delete Entry $text.text tag add Entry $st "stEntry +$i2 line" $text.text tag configure Entry -relief raised -borderwidth 2 } proc DeleteIt {} { global text modFlag set t [split [$text.text tag nextrange Entry 0.0]] if {[llength $t] == 2} { $text.text delete [lindex $t 0] [lindex $t 1] set modFlag 1 } } SetDefaults proc SaveAndExit {} { global bibfile SaveBibFile $bibfile exit } proc SaveQuitWindow {} { set f [toplevel .prompt -borderwidth 10] message $f.msg -text "Data not saved. Really exit?" set b [frame $f.buttons -bd 10] pack $f.msg $f.buttons -side top -fill x button $b.ok -text OK -command {exit} button $b.saveExit -text "Save and Exit" -command {SaveAndExit} button $b.cancel -text Cancel -command {destroy .prompt} pack $b.ok -side left pack $b.saveExit -side left pack $b.cancel -side right } proc ExitBibenter {} { global modFlag if {$modFlag == 0} { exit } else { SaveQuitWindow } } proc Modified {} { global modFlag set modFlag 1 } #.............................................................................. set main "" wm title . "BibTeX Entry" set bibfile "" # Make the top bar menus set mbar $main.mbar frame $mbar -relief raised -bd 4 pack $mbar -side top -anchor w -fill x menubutton $mbar.file -text File -menu $mbar.file.menu menu $mbar.file.menu $mbar.file.menu add command -label "Open" -command "OpenBibFile \"\"" $mbar.file.menu add command -label "Append" -command "OpenBibFile \"\" 1" $mbar.file.menu add command -label "Save" -command "SaveBibFile \$bibfile" $mbar.file.menu add command -label "Save as" -command "SaveBibFile \"\"" $mbar.file.menu add command -label "Quit" -command {ExitBibenter} pack $mbar.file -side left menubutton $mbar.bibtex -text Bibtex -menu $mbar.bibtex.menu menu $mbar.bibtex.menu $mbar.bibtex.menu add command -label "Clear" -command SetDefaults $mbar.bibtex.menu add command -label "Enter" -command EnterIt $mbar.bibtex.menu add command -label "Search" -command Search pack $mbar.bibtex -side left button $mbar.help -text Help -command "HelpText 80 20" pack $mbar.help -side right # Status Line frame $main.status -relief raised -bd 4 label $main.status.label -text Status: -anchor w label $main.status.status -textvariable status -width 70 pack $main.status -fill x pack $main.status.label $main.status.status -side left # Filename frame $main.fname -relief raised -bd 4 label $main.fname.label -text "Bibfile: " pack $main.fname.label -side left entry $main.fname.fname -textvariable bibfile -width 50 bind $main.fname.fname "OpenBibFile \$bibfile" pack $main.fname -fill x pack $main.fname.fname -side left button $main.fname.save -text Save -command "SaveBibFile \$bibfile" pack $main.fname.save -side left # Make the data entry window set enter $main.enter frame $enter -relief raised -bd 4 pack $enter -side top -anchor w -fill x frame $enter.e button $enter.e.enter -text Enter -command {EnterIt} button $enter.e.delete -text Delete -command {DeleteIt} button $enter.e.clear -text Clear -command {SetDefaults} frame $enter.type -relief raised -bd 4 menubutton $enter.type.mb -text "Type" -menu $enter.type.mb.menu -width 15 pack $enter.type.mb -side left set m [menu $enter.type.mb.menu] BuildBibTypeList $enter.type.mb.menu CommandEntry $enter.e1 e1 15 {focus $enter.e2.entry} -textvar bib(bibname) for {set i 2} {$i < 18} {incr i} { CommandEntry $enter.e$i e$i 15 {focus $enter.e[expr $i+1].entry} -textvar bib(none) } CommandEntry $enter.e18 e18 15 {EnterIt} -textvar bib(none) pack $enter.e -side left -fill y pack $enter.e.enter -side top pack $enter.e.delete -side top pack $enter.e.clear -side top pack $enter.type -side top pack $enter.type.mb -side left pack $enter.e1 $enter.e2 $enter.e3 $enter.e4 $enter.e5 $enter.e6\ $enter.e7 $enter.e8 $enter.e9 $enter.e10 $enter.e11 $enter.e12\ $enter.e13 $enter.e14 $enter.e15 $enter.e16 $enter.e17 $enter.e18\ -fill x ConfigureIt $bib(fields) $enter.type.mb config -text book # Make the text window set text $main.text frame $text -relief raised -bd 4 pack $text -side bottom -fill both -expand true frame $text.mbar pack $text.mbar -side top -anchor w -fill x label $text.label -textvariable textlabel -width 70 text $text.text -width 80 -height 24 \ -setgrid true -wrap none \ -xscrollcommand [list $text.xscroll set] \ -yscrollcommand [list $text.yscroll set] scrollbar $text.xscroll -orient horizontal -command [list $text.text xview] scrollbar $text.yscroll -orient vertical -command [list $text.text yview] pack $text.label -fill x pack $text.xscroll -side bottom -fill x pack $text.yscroll -side right -fill y pack $text.text -side left -fill both -expand true bind $text.text {FindEntry} bind $text.text {Modified} #pack $text -side top -fill both -expand true