source: trunk/indexers/mg/src/scripts/xmg.sh@ 3745

Last change on this file since 3745 was 3745, checked in by mdewsnip, 21 years ago

Addition of MG package for search and retrieval

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 43.0 KB
Line 
1#!/usr/local/bin/wish -f
2# Edit the line above to point to your version of wish
3#
4###############################################################################
5#
6# xmg -- X interface to the mg information retrieval system
7# Copyright (C) 1994 Bruce McKenzie, Ian Witten and Craig Nevill-Manning
8#
9# This program is free software; you can redistribute it and/or modify it under
10# the terms of the GNU General Public License as published by the Free Software
11# Foundation; either version 2 of the License, or (at your option) any later
12# version.
13#
14# This program is distributed in the hope that it will be useful, but WITHOUT
15# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17# details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22#
23# @(#)xmg.sh 1.4 25 Mar 1994
24#
25###############################################################################
26#
27# "xmg" is an X interface to the mg information retrieval system.
28#
29# Type a query in the "Query" box.
30# The first line or so of each matching document is listed.
31# Click on any line to show the complete document.
32# Shift-click to show the document in a new window.
33# Press Next and Previous to cycle through the list of documents (Shift works
34# here too).
35# If you change the query, existing windows continue to refer to the original
36# query.
37#
38# You can select either Boolean or Ranked queries.
39# Boolean queries: use "&" for AND, "|" for OR, "!" for NOT, and parentheses
40# for grouping
41# Ranked queries: - documents are listed in order of relevance; a weight is
42# shown alongside each
43# - weights are normalized to 100 for the most relevant
44# document
45# - choose Approximate Ranking for greater retrieval speed.
46#
47# In document windows (other than the main window):
48# Typing 'n' = next document
49# Typing 'N' = next document in new window
50# Typing 'p' = previous document
51# Typing 'P' = previous document in new window
52# Typing 'q' = close window
53#
54###############################################################################
55
56# Date: 13 March 1994
57# Authors: Bruce McKenzie, Ian Witten and Craig Nevill-Manning
58
59###############################################################################
60# Customizing xmg
61###############################################################################
62
63# To override the default font options, use RESOURCE_MANAGER properties like
64# the following in your .Xdefaults file
65#
66# xmg.textfont: fixed
67# xmg.buttonfont: fixed
68#
69# To override the default query type of ranked, use
70#
71# xmg.querytype: boolean
72#
73# To override the default collection name (the first one listed by /bin/ls from
74# your MGDATA environment variable), use
75#
76# xmg.collection: mail93
77
78###############################################################################
79# Global variables
80###############################################################################
81
82# genuine constants:
83#
84# env the environment variables
85# Terminator terminator for mgquery
86# MaxWindowHeight the maximum height permitted for a document window
87# ListBoxLength the number of items in the document list
88# HeadLength the length document head displayed in the list box
89# ImageString the string at the start of a document associated
90# with an image
91#
92# things that rarely get changed:
93#
94# querytype the current query type (boolean, ranked, approx-ranked)
95# booleanquery says whether the query is boolean or ranked
96# approxquery says whether ranking is to be approximate or not
97# collection the name of the document collection being used
98# fileId file identifer for a pipe to the mgquery process
99# errorOccurred flag to say that there has been an I/O error on the
100# current collection
101#
102# things that reflect items in the main panel
103#
104# query the current query
105# hits number of hits for the current query
106# display shell command to display the image associated with
107# the last document retrieved
108# scrolledto document prefixes have been obtained for all documents
109# up to this position in the document list
110#
111# static variable needed in just one procedure
112#
113# top_rank weight associated with the first document retrieved
114# windowname unique id for each document window
115
116###############################################################################
117# High-level procedures
118###############################################################################
119
120proc SaveDocument {w} {
121 set file [FSBox "Save document to file:"]
122
123 if {$file != ""} {
124 if [file exists $file] {
125 toplevel .r
126 wm title .r "\n"
127 .r configure -borderwidth 2 -relief raised
128 focus .r
129 label .r.message -text "The file already exists"
130 button .r.replace -text "Replace" -width 10 -height 2 \
131 -command "destroy .r; WriteDocument $file \"w\" $w"
132 button .r.append -text "Append" -width 10 -height 2 \
133 -command "destroy .r; WriteDocument $file \"a\" $w"
134 button .r.cancel -text "Cancel" -command {destroy .r} -width 10 -height 2
135 pack .r.message -side top -pady 10 -padx 20
136 pack .r.replace .r.append .r.cancel -side left -pady 10 -padx 20
137
138 } else {
139 WriteDocument $file "w" $w
140 }
141 }
142}
143
144proc Dialog {text command} {
145 if {![winfo exists .r] } { toplevel .r }
146 wm title .r "\n"
147 .r configure -borderwidth 2 -relief raised
148 focus .r
149 label .r.message -text $text
150 button .r.ok -text "OK" -width 10 -height 2 \
151 -command "destroy .r $command"
152 pack .r.message -side top -pady 10 -padx 20
153 pack .r.ok -side bottom -pady 10 -padx 20
154}
155
156proc WriteDocument {file mode w} {
157 if {[catch {set f [open $file $mode]}] == 0} {
158 if [winfo exists $w] {
159 puts $f [$w get 0.0 end]
160 } else {
161 Dialog "Unable to save text" ""
162 }
163 flush $f
164 close $f
165 } else {
166 Dialog "Unable to open file for writing" "; SaveDocument $w"
167 }
168}
169
170# Send a query to mg and enter the response in the document list
171#
172proc sendQuery {window} {
173 global query hits querytype errorOccurred scrolledto
174
175 .m.t.q.retrieved.label configure -foreground Gray
176 .m.t.q.retrieved.hits configure -foreground Gray
177
178 sendto ".set query \"$querytype\""
179 sendto ".set mode \"docnums\""
180 foreach f "sdocnums sdocs srank" { $window.$f delete 0 end }
181 disableButton .m.d.buttons.previous
182 disableButton .m.d.buttons.next
183 if {[string index $query 0] == "." || [string length $query] > 255} {
184 set doclist ""
185 } else { set doclist [sendandget $query] }
186
187 if {$errorOccurred} {
188 enterText .m.d.text [mgqueryFailure]
189 .m.t.q.retrieved.label configure -foreground Black
190 .m.t.q.retrieved.hits configure -foreground Black
191 update
192 return
193 }
194 sendto ".set query \"DocNums\""
195 sendto ".set mode \"text\""
196 .m.t.q.retrieved.label configure -foreground Black
197 .m.t.q.retrieved.hits configure -foreground Black
198 set hits [llength $doclist]
199 set i 0
200 global top_rank
201
202 scan [lindex $doclist 0] "%d %f %d" d rank size
203 set top_rank $rank
204 if {$top_rank == 0} {set top_rank 1}
205
206 while {$i < $hits} {
207 scan [lindex $doclist $i] "%d %f %d" d rank size
208 if {$rank == "Inf"} {set rank 1}
209 if {$querytype == "boolean"} {
210 $window.srank insert end ""
211 } else {
212 $window.srank insert end \
213 [format "%3.0f" [expr $rank / $top_rank * 99 + 1]]
214 }
215 $window.sdocnums insert end [format " %d" $d]
216 incr i
217 }
218
219 set scrolledto 0
220 scroll $window 0
221 bind $window.sdocs <ButtonRelease-1> \
222 "showDocument $window.sdocs %y .m.d \"$doclist\" \"$query\""
223 bind $window.sdocs <Shift-ButtonRelease-1> \
224 "showDocument $window.sdocs %y New \"$doclist\" \"$query\""
225 enterText .m.d.text ""
226}
227
228# Scrolling routine for the document list
229#
230proc scroll {window n} {
231 global hits scrolledto ListBoxLength Terminator fileId ImageString
232
233 $window.sdocnums yview $n
234 $window.sdocs yview $n
235 $window.srank yview $n
236 set last $n
237 if {$last <= 0} {set last 0}
238 incr last $ListBoxLength
239 if {$last > $hits} {set last $hits}
240
241# .m config -cursor {watch}
242# update
243 sendto ".set mode \"heads\""
244 sendto ".set terminator \"\""
245 while {$scrolledto < $last} {
246 if {[scan [$window.sdocnums get $scrolledto] "%d" d] == 1} {
247 sendto $d
248 gets $fileId line
249 set i [string first " " $line]
250 if {$i != -1} {set line [string range $line [expr $i + 1] end]}
251 if {[string compare $ImageString [string range $line 0 9]] == 0} {
252 set line [getImageDocumentHead $d]
253 }
254 $window.sdocs insert $scrolledto $line
255 }
256 incr scrolledto
257 }
258 sendto ".set terminator \"$Terminator\\n\""
259 sendto ".set mode text"
260# .m config -cursor {}
261}
262
263proc getImageDocumentHead {d} {
264 global HeadLength ImageString fileId
265
266 sendto ".set heads_length 1000"
267 sendto $d
268 gets $fileId line
269
270 set line [string range $line 11 end]
271
272 set s [string first $ImageString $line]
273 if {$s != -1} {
274 incr s 11
275 set line [string range $line $s [expr $s + $HeadLength]]
276 }
277
278 sendto ".set heads_length $HeadLength"
279
280 return "* $line"
281}
282
283# Show document from the document list
284#
285proc showDocument {window y newWindow doclist query} {
286 showThisDocument [$window nearest $y] $doclist $newWindow $query
287}
288
289proc adjacentDocument {index doclist window q button} {
290 global ListBoxLength
291
292 showThisDocument $index $doclist $window $q
293
294 if {$window == ".m.d"} {
295 set first [.m.t.q.r.sdocs nearest 0]
296 if {$index < $first || $index >= $first + $ListBoxLength} {
297 scroll .m.t.q.r [expr $index - $ListBoxLength / 2]
298 }
299 .m.t.q.r.sdocs select from $index
300 }
301}
302
303
304# Show document with this index in the document list
305#
306proc showThisDocument {index doclist window q} {
307 global collection MaxWindowHeight display windowname
308
309 set document ""
310 scan [lindex $doclist $index] "%d %f" document rank
311 if {$document == ""} { return }
312 set textlist [sendandget $document]
313
314 if {[string length $display] != 0} { exec csh -c $display >& /dev/null & }
315
316# throw away the first line
317 set text [join [lrange $textlist 1 end] "\n"]
318
319# if necessary, create a new window for this document
320 if {$window == "New"} {
321 .m.t.q.r.sdocs select clear
322 set window .m.document$windowname
323 incr windowname
324 set existing ""
325 set raised 0
326 foreach w [lrange [winfo children .m] 2 end] {
327 if {[lindex [$w.docnum configure -text] 4] == $index && \
328 [lindex [$w.query configure -text] 4] == $q} {
329 if {$raised} {
330 closeDocumentWindow $w
331 } else {
332 raise $w
333 getFocus $w
334 set window $w
335 set raised 1
336 }
337 }
338 }
339
340 if {!$raised} {
341 toplevel $window
342 $window configure -borderwidth 2 -relief raised
343 wm protocol $window WM_TAKE_FOCUS "getFocus $window"
344 getFocus $window
345 makeDocumentPanel $window
346 bind $window q "$window.buttons.close invoke"
347 label $window.docnum -text $index
348 label $window.query -text $q
349 }
350 }
351
352# configure the window for this document
353 set rows [expr [llength $textlist] - 1]
354 if {$window != ".m.d"} {
355 wm title $window "$collection \"$q\""
356 wm iconname $window "$document"
357 $window.text configure -height $rows
358 if {$rows > $MaxWindowHeight} {
359 $window.text configure -height $MaxWindowHeight
360 }
361 }
362
363# show the document text in the window
364 enterText $window.text $text
365
366# configure the window's Previous and Next buttons
367 if {$index == 0} {
368 disableButton $window.buttons.previous
369 } else {
370 set newindex [expr $index-1]
371 bindBoth $window previous $newindex $doclist $q
372 bind $window p [bind $window.buttons.previous <1>]
373 bind $window P [bind $window.buttons.previous <Shift-1>]
374 }
375 if {$index == [expr [llength $doclist]-1]} {
376 disableButton $window.buttons.next
377 } else {
378 set newindex [expr $index+1]
379 bindBoth $window next $newindex $doclist $q
380 bind $window n [bind $window.buttons.next <1>]
381 bind $window N [bind $window.buttons.next <Shift-1>]
382 }
383
384 if {$window != ".m.d"} { $window.docnum configure -text $index }
385
386 if {[string length $display] != 0} {
387 set on "{
388 if \[winfo exists $window\] \{
389 $window.buttons.image configure \
390 -foreground Black
391 update\}
392 }"
393 set off "{
394 if \[winfo exists $window\] \{
395 $window.buttons.image configure \
396 -foreground [lindex [.m.d configure -background] 4]
397 update\}
398 }"
399 set t 0
400 while {$t < 5} {
401 after [expr $t * 1000] eval $on
402 after [expr $t * 1000 + 500] eval $off
403 incr t
404 }
405 }
406}
407
408# Bind both Button-1 and Shift-Button-1 commands
409#
410proc bindBoth {window button index doclist query} {
411 $window.buttons.$button configure -state normal
412 bind $window.buttons.$button <Button-1> \
413 "adjacentDocument $index \"$doclist\" $window \"$query\" $button"
414 bind $window.buttons.$button <Shift-Button-1> \
415 "adjacentDocument $index \"$doclist\" New \"$query\" $button"
416}
417
418# Change the type of query
419#
420proc changeQuerytype {name element op} {
421 global querytype booleanquery approxquery
422
423 if {$booleanquery} {
424 .m.t.b.type.approx configure -state disabled
425 .m.t.q.retrieved.weight configure \
426 -foreground [lindex [.m configure -background] 4]
427 set querytype boolean
428 } else {
429 .m.t.b.type.approx configure -state normal
430 .m.t.q.retrieved.weight configure -foreground Black
431 if {$approxquery} {
432 set querytype "approx-ranked"
433 } else {
434 set querytype "ranked"
435 }
436 }
437}
438
439proc enterText {window text} {
440 $window configure -state normal
441 $window delete 0.0 end
442 $window insert 0.0 $text
443 $window configure -state disabled
444}
445
446###############################################################################
447# IO and initialization
448###############################################################################
449
450# Send a message to mg and return the reply
451#
452proc sendandget {message} {
453 global fileId Terminator display errorOccurred ImageString
454 set display ""
455
456 .m config -cursor {watch}
457 update idletasks
458 set reply ""
459 sendto "$message\n"
460 if {$message != "" && !$errorOccurred} {
461 gets $fileId line
462 while {[string compare $Terminator $line] != 0} {
463 lappend reply $line;
464 gets $fileId line
465 }
466
467 if {[string compare $ImageString [lindex $reply 1]] == 0 } {
468 if {[string compare "MGDATA" [string range [lindex $reply 3] 0 5]] == 0} {
469 set display [string range [lindex $reply 3] 7 end];
470 }
471 set reply [lrange $reply 4 end]
472 }
473 }
474
475 .m config -cursor {}
476 return $reply
477}
478
479# Send a message to mg
480#
481proc sendto {message} {
482 global fileId errorOccurred
483
484 puts $fileId $message
485 set errorOccurred [catch {flush $fileId}]
486}
487
488# Reinitialise mgquery to work with a new document collection
489#
490proc reInitMG {c} {
491 global fileId
492 catch {close $fileId}
493 initMG $c
494}
495
496# Initialize mgquery to work with a specified text collection
497#
498proc initMG {c} {
499 global query hits fileId collection Terminator env errorOccurred
500 global HeadLength
501
502 set collection $c
503 set query ""
504 set hits 0
505 set errorOccurred 0
506
507 foreach f "sdocs srank" { .m.t.q.r.$f delete 0 end }
508 enterText .m.d.text ""
509 .m.t.b.collect.m configure -text $collection
510 foreach w [winfo children .m] {
511 if {[string first ".m.document" $w] == 0} { closeDocumentWindow $w }
512 }
513
514 disableButton .m.d.buttons.previous
515 disableButton .m.d.buttons.next
516
517 if {$c != ""} {
518 set f $env(MGDATA)/$c
519
520 if {[file exists $f] && [file readable $f]} {
521 set fileId [open "| mgquery $collection" RDWR]
522 sendto ".set expert \"true\""
523 sendto ".set terminator \"$Terminator\\n\""
524 sendto ".set pager \"cat\""
525 sendto ".set heads_length $HeadLength"
526 return
527 }
528 }
529 bind .m.t.q.e.entry <Return> {null}
530 enterText .m.d.text [initializationFailure]
531 update
532}
533
534# Routine to obtain values from RESOURCE_MANAGER
535#
536proc query_resource {option class default} {
537 set val [option get . $option $class]
538 if {[string length $val] == 0} { return $default }
539 return $val
540}
541
542
543proc disableButton {b} {
544 bind $b <Button-1> {null}
545 bind $b <Shift-Button-1> {null}
546 $b configure -state disabled
547}
548
549proc null {} {}
550
551###############################################################################
552# Setting up windows
553###############################################################################
554
555proc removeFromWindowList {window} {
556 global stacking
557
558 set i 0
559 while {$i < [llength $stacking]} {
560 if {$window == [lindex $stacking $i]} {
561 set stacking [lreplace $stacking $i $i]
562 }
563 incr i
564 }
565}
566
567proc getFocus {window} {
568 global stacking
569
570 focus $window
571 removeFromWindowList $window
572 set stacking [linsert $stacking 0 $window]
573}
574
575proc closeDocumentWindow {w} {
576 global stacking
577 removeFromWindowList $w
578 set t [lindex $stacking 0]
579
580 if {$t != ""} {
581 focus $t
582 raise $t
583 }
584 destroy $w
585}
586
587# Make a "document" panel, including Previous, Next, and Close buttons
588#
589proc makeDocumentPanel {window} {
590 frame $window.buttons
591 button $window.buttons.previous -text "Previous" -width 10 -state disabled
592 button $window.buttons.next -text "Next" -width 10 -state disabled
593 button $window.buttons.close -text "Close" -width 10 \
594 -command "closeDocumentWindow $window"
595 button $window.buttons.save -text "Save" -width 10 \
596 -command "SaveDocument $window.text"
597
598 label $window.buttons.image -text "Fetching image..." \
599 -foreground [lindex [$window configure -background] 4]
600 pack $window.buttons.previous $window.buttons.next -side left
601 pack $window.buttons.image -side left -padx 10
602 pack $window.buttons.close $window.buttons.save -side right
603 pack $window.buttons -side top -fill x
604
605 text $window.text -borderwidth 2 -relief raised \
606 -setgrid true -width 80 -state disabled
607
608 $window.text configure -yscrollcommand "$window.sy set"
609 scrollbar $window.sy -relief flat -command "$window.text yview"
610 pack $window.sy -side right -fill y
611 pack $window.text -side top -expand yes -fill both
612}
613
614# Make the "buttons" panel, with the query type and collection name
615#
616proc mkButtons {window} {
617 global collection booleanquery approxquery env
618 frame $window.type
619 label $window.type.label -text "Query type"
620
621 radiobutton $window.type.boolean -text "Boolean" -variable booleanquery \
622 -value 1 -relief flat
623
624 radiobutton $window.type.ranked -text "Ranked" -variable booleanquery \
625 -value 0 -relief flat
626
627 checkbutton $window.type.approx -text "Approximate" -variable approxquery \
628 -relief flat
629
630 trace variable booleanquery w changeQuerytype
631 trace variable approxquery w changeQuerytype
632
633 pack $window.type.label -side top -anchor w
634 pack $window.type.boolean -side top -anchor w -padx 12
635 pack $window.type.ranked -side top -anchor w -padx 12
636 pack $window.type.approx -side top -anchor w -padx 36
637
638 pack $window.type -side top -anchor w -pady 12
639
640 frame $window.collect
641 label $window.collect.label -text "Collection: "
642 pack $window.collect.label -side left -anchor w
643
644 getViewer
645
646 set dirs [getMGDATA]
647 set collection ""
648 if {$dirs != ""} { set collection [lindex $dirs 0] }
649
650 menubutton $window.collect.m -menu $window.collect.m.menu \
651 -relief sunken
652 menu $window.collect.m.menu
653 foreach c $dirs {
654 if {[file readable $env(MGDATA)/$c] &&
655 [file isdirectory $env(MGDATA)/$c]} {
656 $window.collect.m.menu add command -label $c \
657 -command "reInitMG $c"
658 }
659 }
660 pack $window.collect.m -side left
661 pack $window.collect -side bottom -anchor w
662
663
664}
665
666# Check that the MGDATA environment variable is set, and is non-null
667#
668proc getMGDATA {} {
669 global env
670
671 set MGDATA 0
672 foreach i [array names env] {
673 if {$i == "MGDATA"} {
674 set dirs [exec /bin/ls $env(MGDATA)]
675 if {[info exists dirs]} { return $dirs }
676 }
677 }
678
679 return ""
680}
681
682proc getViewer {} {
683 global env
684
685 set viewer 0
686 foreach i [array names env] {
687 if {$i == "MGIMAGEVIEWER"} {set viewer $env(MGIMAGEVIEWER)}
688 }
689
690 if {$viewer == 0} {set env(MGIMAGEVIEWER) "xloadimage -quiet -fit stdin"}
691}
692
693# Make the "query" panel, with the query and list of retrieved documents
694#
695proc mkQuery {window} {
696 global hits ListBoxLength
697
698# at the top of the "query" panel is the "entry" frame
699 frame $window.e
700 label $window.e.label -text "Query"
701 entry $window.e.entry -relief sunk -width 47 -textvariable query
702 pack $window.e.label -side left
703 pack $window.e.entry -side right -expand yes -fill x
704 focus default $window.e.entry
705 focus $window.e.entry
706
707 bind $window.e.entry <Control-Key> {null}
708 bind $window.e.entry <Return> "sendQuery $window.r"
709 bind $window.e.entry <Escape> { set query "" }
710 bind $window.e.entry <2> {
711 %W insert insert [selection get STRING]
712 tk_entrySeeCaret %W
713 }
714
715# next is the "retrieved" frame
716 frame $window.retrieved
717 label $window.retrieved.weight -text "Weight"
718 label $window.retrieved.label -text " Documents retrieved: "
719 label $window.retrieved.hits -width 4 -relief sunk -textvariable hits
720 set documentsRetrieved 0
721 pack $window.retrieved.weight $window.retrieved.label \
722 $window.retrieved.hits -side left
723
724# next is the "results" frame (note that the docnums listbox doesn't appear on
725# the screen)
726 frame $window.r
727 scrollbar $window.r.sscroll -relief sunken -command "scroll $window.r"
728# bind $window.r.sscroll <ButtonRelease-1> "updateSdocs $window.r.sdocs $window.r.sdocnums"
729 listbox $window.r.sdocnums
730 listbox $window.r.sdocs -relief sunken \
731 -setgrid 1 -geometry 50x$ListBoxLength
732 listbox $window.r.srank -yscroll "$window.r.sscroll set" \
733 -setgrid 1 -geometry 4x$ListBoxLength
734
735 bind $window.r.srank <Any-1> {null}
736 bind $window.r.srank <Any-2> {null}
737 bind $window.r.srank <Any-3> {null}
738
739 bind $window.r.srank <Any-B1-Motion> {null}
740 bind $window.r.srank <Any-B2-Motion> {null}
741 bind $window.r.srank <Any-B3-Motion> {null}
742
743 bind $window.r.sdocs <Any-2> {null}
744 bind $window.r.sdocs <Any-B2-Motion> {null}
745
746 pack $window.r.sscroll -side right -fill y
747 pack $window.r.srank -side left
748 pack $window.r.sdocs -side left -fill x -expand yes
749
750# pack them up
751 pack $window.e -side top -anchor e -pady 10 -fill x -expand yes
752 pack $window.retrieved -anchor w
753 pack $window.r -side bottom -anchor e -pady 10 -fill x -expand yes
754}
755
756# Make a "help" window
757#
758proc mkHelp {} {
759 if [winfo exists .help] { destroy .help }
760 toplevel .help
761 wm title .help "X interface to the mg information retrieval system"
762 wm iconname .help "About xmg"
763 frame .help.buttons
764 button .help.buttons.close -text Close -command "destroy .help" -width 10
765 button .help.buttons.help -text Help -width 10
766 button .help.buttons.conditions -text Conditions -width 10
767 button .help.buttons.warranty -text Warranty -width 10
768 button .help.buttons.bugs -text Bugs -width 10
769
770 frame .help.t
771 text .help.t.text -relief raised -bd 2 -setgrid true -height 24 -width 95
772 .help.t.text configure -yscrollcommand ".help.t.sy set"
773 scrollbar .help.t.sy -relief flat -command ".help.t.text yview"
774
775 pack .help.buttons.close .help.buttons.help -side right -fill x
776 pack .help.buttons.conditions .help.buttons.warranty \
777 .help.buttons.bugs -side left
778 pack .help.buttons -side bottom -fill x
779
780 pack .help.t.text -side left -expand yes -fill both
781 pack .help.t.sy -side right -fill y
782 pack .help.t -side top -fill both -expand yes
783 enterText .help.t.text [HelpText]
784 grayButton help
785}
786
787proc grayButton {button} {
788 bind .help.buttons.help <1> "enterText .help.t.text \[HelpText\]
789 grayButton help"
790 .help.buttons.help configure -state normal
791
792 bind .help.buttons.bugs <1> "enterText .help.t.text \[Bugs\]
793 grayButton bugs"
794 .help.buttons.bugs configure -state normal
795
796 bind .help.buttons.warranty <1> "enterText .help.t.text \[Warranty\]
797 grayButton warranty"
798 .help.buttons.warranty configure -state normal
799
800 bind .help.buttons.conditions <1> "enterText .help.t.text \[Conditions\]
801 grayButton conditions"
802 .help.buttons.conditions configure -state normal
803 disableButton .help.buttons.$button
804}
805
806# The text for errors in the MG environment variable
807#
808
809
810proc bookDetails {} {
811 return { \
812
813 This X interface was written by Bruce McKenzie, Ian Witten and Craig Nevill-Manning.
814 For information on mg, see
815 Witten, I.H., Moffat, A. and Bell, T.C. (1994) "Managing gigabytes: compressing
816 and indexing documents and images." Van Nostrand Reinhold, New York.
817 ISBN 0-442-01863-0, US $54.95; call 1 (800) 544-0550 to order.
818}
819}
820
821proc initializationFailure {} {
822 return [format "%s\n%s" {\
823In order to run XMG you need to have already created and indexed some document collections,
824 and placed them in a directory whose name is given by the environment variable MGDATA.
825 Your MGDATA variable is unset or does not specify a directory with files in; therefore XMG
826 cannot proceed.
827} [bookDetails]]
828}
829
830proc mgqueryFailure {} {
831 return [format "%s\n%s" {\
832In order to run XMG you need to have already created and indexed some document collections,
833 and placed them in a directory whose name is given by the environment variable MGDATA.
834 There is a problem with the collection that you have chosen. Please re-index the collection before
835 you use it again.
836} [bookDetails]]
837
838}
839
840# The text for the help message
841#
842proc HelpText {} {
843 return [format "%s\n%s" {\
844This is an X interface to the mg information retrieval system.
845
846 Type a query in the "Query" box.
847 The first line or so of each matching document is listed.
848 Click on any line to show the complete document.
849 Shift-click to show the document in its own window
850 Press Next and Previous to cycle through the list of documents (Shift works here too).
851 In document windows (other than the main window), typing n or p shows the next and previous
852 documents; N or P creates a new window for them. q closes the most recently created window.
853 If you change the query, existing windows continue to refer to the original query.
854
855 You can select either Boolean or Ranked queries.
856 Boolean queries: use & for AND, | for OR, ! for NOT, and parentheses for grouping
857 Ranked queries: documents are listed in order of relevance; a weight is shown alongside each
858 weights are normalized to 100 for the most relevant document
859 choose Approximate Ranking for greater retrieval speed.
860 } [bookDetails]]
861}
862
863proc reformat {text} {
864 regsub -all "\n *\n *" $text "BLEQUEWFI" text
865 regsub -all "\[ \t\n\r\]+" $text " " text
866 regsub -all "BLEQUEWFI" $text "\n\n" text
867 return $text
868}
869
870proc Conditions {} {
871 return [reformat [join [sendandget ".conditions\nTHISWILLNEVERMATCHANYTHING\n"] "\n"]]
872}
873
874proc Warranty {} {
875 return [reformat [join [sendandget ".warranty\nTHISWILLNEVERMATCHANYTHING\n"] "\n"]]
876}
877
878proc Bugs {} {
879 return [reformat {
880Known shortcomings of xmg include:
881
882 Slow picture viewing. Pictures are decompressed in their entirety before a window is created
883 for the picture. This often results in a long delay with no feedback. Moreover, when
884 an image's document is displayed repeatedly, multiple identical images are shown.
885
886 Sequential viewing. There is no way of going to the next document in the collection to see the
887 context of documents that have been retrieved. This would be trivial to implement but
888 conflicts with the interpretation of the "previous" and "next" buttons.
889 }]
890}
891
892###############################################################################
893# The main program
894###############################################################################
895
896scan [info tclversion] "%d.%d" major minor
897
898if {$major < 7} {
899 puts "xmg requires version 7.0 of the tcl interpreter. You are using version $major.$minor"
900 exit
901}
902
903# Default font settings
904set defaulttextfont -adobe-times-medium-r-normal--14-140-75-75-p-74-iso8859-1
905set defaultbuttonfont -adobe-times-bold-r-normal--14-140-75-75-p-77-iso8859-1
906set textfont [query_resource textfont Textfont $defaulttextfont]
907set buttonfont [query_resource buttonfont Buttonfont $defaultbuttonfont]
908
909set MaxWindowHeight 10
910set HeadLength 100
911set scrolledto 0
912set ListBoxLength 12
913set windowname 0
914set Terminator "RECORD_END"
915set fileId ""
916set query ""
917set ImageString "::::::::::"
918set stacking ".m.t"
919
920tk_listboxSingleSelect Listbox
921option add *Text.wrap word
922
923option add *Text.font $textfont
924option add *Listbox.font $textfont
925option add *Entry.font $textfont
926
927option add *Button.font $buttonfont
928option add *Menubutton.font $buttonfont
929option add *Radiobutton.font $buttonfont
930option add *Checkbutton.font $buttonfont
931option add *Label.font $buttonfont
932option add *Menu.font $buttonfont
933
934wm title . "Managing Gigabytes"
935wm iconname . "xmg"
936frame .m -borderwidth 5 -relief raised
937
938# top part of frame
939frame .m.t
940
941# on the left side of the top part is the "buttons" panel
942frame .m.t.b
943mkButtons .m.t.b
944
945# on the right side is the "query" panel
946frame .m.t.q
947mkQuery .m.t.q
948
949set q [query_resource querytype Querytype approx-ranked]
950set booleanquery [expr \"$q\" == \"boolean\"]
951set approxquery [expr \"$q\" == \"approx-ranked\"]
952# the above lines will trigger changeQuerytype
953
954pack .m.t.b -side left -anchor nw
955pack .m.t.q -side right -anchor ne -fill both -expand yes
956
957# at the bottom is the "document" panel
958frame .m.d
959makeDocumentPanel .m.d
960
961.m.d.buttons.close configure -text "Exit" \
962 -command "destroy ."
963button .m.d.buttons.help -text "Help" -width 10 \
964 -command "mkHelp"
965
966pack .m.d.buttons.help -side right
967pack .m.t -side top -fill x -expand yes
968pack .m.d -side bottom -fill both -expand yes
969
970pack .m -fill both -expand yes
971wm geometry . +300+85
972
973set collection [query_resource collection Collection $collection]
974if {$argc>0} { set collection $argv }
975
976enterText .m.d.text [HelpText]
977
978initMG $collection
979
980###############################################################################
981###############################################################################
982# The following file selection box procedure was written by Andrew Donkin
983# of the University of Waikato.
984###############################################################################
985
986proc FSBox { {fsBoxMessage "Select file:"}} {
987# xf ignore me 5
988 global fsBox
989
990 set fsBox(name) ""
991 set fsBox(internalPath) [pwd]
992 set fsBox(pattern) "*"
993 set fsBox(all) 0
994
995 # build widget structure
996
997 # start build of toplevel
998 if {"[info commands XFDestroy]" != ""} {
999 catch {XFDestroy .fsBox}
1000 } {
1001 catch {destroy .fsBox}
1002 }
1003 toplevel .fsBox -borderwidth 0
1004 wm geometry .fsBox 350x300
1005 wm title .fsBox {File select box}
1006 wm maxsize .fsBox 1000 1000
1007 wm minsize .fsBox 100 100
1008 # end build of toplevel
1009
1010 label .fsBox.message1 -anchor c -text "$fsBoxMessage"
1011 frame .fsBox.frame1 -borderwidth 0 -relief raised
1012
1013 button .fsBox.frame1.ok -text "OK" -command "
1014 global fsBox
1015 set fsBox(name) \[.fsBox.file.file get\]
1016 set fsBox(path) \[.fsBox.path.path get\]
1017
1018 set fsBox(internalPath) \[.fsBox.path.path get\]
1019
1020 if {\"\[info commands XFDestroy\]\" != \"\"} {
1021 catch {XFDestroy .fsBox}
1022 } {
1023 catch {destroy .fsBox}
1024 }"
1025
1026 button .fsBox.frame1.rescan -text "Rescan" -command {
1027 global fsBox
1028 FSBoxFSShow [.fsBox.path.path get] [.fsBox.pattern.pattern get] $fsBox(all)
1029 }
1030
1031 button .fsBox.frame1.cancel -text "Cancel" -command "
1032 global fsBox
1033 set fsBox(name) {}
1034 set fsBox(path) {}
1035
1036 if {\"\[info commands XFDestroy\]\" != \"\"} {
1037 catch {XFDestroy .fsBox}
1038 } {
1039 catch {destroy .fsBox}
1040 }"
1041
1042 frame .fsBox.path -borderwidth 0 -relief raised
1043 frame .fsBox.path.paths
1044 label .fsBox.path.paths.paths -borderwidth 0 -text "Pathname:"
1045
1046 entry .fsBox.path.path -relief sunken
1047 .fsBox.path.path insert 0 $fsBox(internalPath)
1048 frame .fsBox.pattern -borderwidth 0 -relief raised
1049 frame .fsBox.pattern.patterns
1050
1051 label .fsBox.pattern.patterns.patterns -borderwidth 0 -text "Selection pattern:"
1052 entry .fsBox.pattern.pattern -relief sunken
1053 .fsBox.pattern.pattern insert 0 $fsBox(pattern)
1054
1055 frame .fsBox.files -borderwidth 0 -relief raised
1056 scrollbar .fsBox.files.vscroll -relief raised -command ".fsBox.files.files yview"
1057 scrollbar .fsBox.files.hscroll -orient horiz -relief raised -command ".fsBox.files.files xview"
1058 listbox .fsBox.files.files -exportselection false -relief raised -xscrollcommand \
1059 ".fsBox.files.hscroll set" -yscrollcommand ".fsBox.files.vscroll set"
1060 frame .fsBox.file -borderwidth 0 -relief raised
1061 label .fsBox.file.labelfile -text "Filename:"
1062 entry .fsBox.file.file -relief sunken
1063 focus .fsBox.file.file
1064
1065 .fsBox.file.file delete 0 end
1066 .fsBox.file.file insert 0 $fsBox(name)
1067
1068 checkbutton .fsBox.pattern.all -offvalue 0 -onvalue 1 -text "Show all files" -variable fsBox(all) -command {
1069 global fsBox
1070 FSBoxFSShow [.fsBox.path.path get] [.fsBox.pattern.pattern get] $fsBox(all)
1071 }
1072
1073 FSBoxFSShow $fsBox(internalPath) $fsBox(pattern) $fsBox(all)
1074
1075 # bindings
1076 bind .fsBox.files.files <Double-Button-1> "FSBoxFSFileSelectDouble %W %y"
1077 bind .fsBox.files.files <ButtonPress-1> "FSBoxFSFileSelect %W %y"
1078 bind .fsBox.files.files <Button1-Motion> "FSBoxFSFileSelect %W %y"
1079 bind .fsBox.files.files <Shift-Button1-Motion> "FSBoxFSFileSelect %W %y"
1080 bind .fsBox.files.files <Shift-ButtonPress-1> "FSBoxFSFileSelect %W %y"
1081
1082 bind .fsBox.path.path <Return> {
1083 global fsBox
1084 FSBoxFSShow [.fsBox.path.path get] [.fsBox.pattern.pattern get] $fsBox(all)
1085 .fsBox.file.file icursor end
1086 focus .fsBox.file.file}
1087 catch "bind .fsBox.path.path <Up> {}"
1088 bind .fsBox.path.path <Down> {
1089 .fsBox.file.file icursor end
1090 focus .fsBox.file.file}
1091
1092 bind .fsBox.file.file <Return> "
1093 global fsBox
1094 set fsBox(name) \[.fsBox.file.file get\]
1095 set fsBox(path) \[.fsBox.path.path get\]
1096 set fsBox(internalPath) \[.fsBox.path.path get\]
1097
1098 if {\"\[info commands XFDestroy\]\" != \"\"} {
1099 catch {XFDestroy .fsBox}
1100 } {
1101 catch {destroy .fsBox}
1102 }"
1103 bind .fsBox.file.file <Up> {.fsBox.path.path icursor end
1104 focus .fsBox.path.path}
1105 bind .fsBox.file.file <Down> {.fsBox.pattern.pattern icursor end
1106 focus .fsBox.pattern.pattern}
1107
1108 bind .fsBox.pattern.pattern <Return> {
1109 global fsBox
1110 FSBoxFSShow [.fsBox.path.path get] [.fsBox.pattern.pattern get] $fsBox(all)}
1111 bind .fsBox.pattern.pattern <Up> {
1112 .fsBox.file.file icursor end
1113 focus .fsBox.file.file}
1114 catch "bind .fsBox.pattern.pattern <Down> {}"
1115
1116 # packing
1117 pack .fsBox.files.vscroll -side left -fill y
1118 pack .fsBox.files.hscroll -side bottom -fill x
1119 pack .fsBox.files.files -side left -fill both -expand yes
1120
1121 pack .fsBox.file.labelfile -side left
1122 pack .fsBox.file.file -side left -fill both -expand yes
1123
1124 pack .fsBox.frame1.ok -side left -fill both -expand yes
1125 pack .fsBox.frame1.rescan -side left -fill both -expand yes
1126 pack .fsBox.frame1.cancel -side left -fill both -expand yes
1127
1128 pack .fsBox.path.paths.paths -side left
1129
1130 pack .fsBox.pattern.patterns.patterns -side left
1131
1132 pack .fsBox.path.paths -side left
1133 pack .fsBox.path.path -side left -fill both -expand yes
1134
1135 pack .fsBox.pattern.patterns -side left
1136 pack .fsBox.pattern.all -side right -fill both
1137 pack .fsBox.pattern.pattern -side left -fill both -expand yes
1138
1139 pack .fsBox.message1 -side top -fill both
1140 pack .fsBox.frame1 -side bottom -fill both
1141 pack .fsBox.pattern -side bottom -fill both
1142 pack .fsBox.file -side bottom -fill both
1143 pack .fsBox.path -side bottom -fill both
1144 pack .fsBox.files -side left -fill both -expand yes
1145
1146 update idletask
1147 grab .fsBox
1148 tkwait window .fsBox
1149
1150 if {"[string trim $fsBox(path)]" != "" ||
1151 "[string trim $fsBox(name)]" != ""} {
1152 if {"[string trimleft [string trim $fsBox(name)] /]" == ""} {
1153 return [string trimright [string trim $fsBox(path)] /]
1154 } {
1155 return [string trimright [string trim $fsBox(path)] /]/[string trimleft [string trim $fsBox(name)] /]
1156 }
1157 }
1158}
1159
1160proc FSBoxBindSelectOne { fsBoxW fsBoxY} {
1161# xf ignore me 6
1162
1163 set fsBoxNearest [$fsBoxW nearest $fsBoxY]
1164 if {$fsBoxNearest >= 0} {
1165 $fsBoxW select from $fsBoxNearest
1166 $fsBoxW select to $fsBoxNearest
1167 }
1168}
1169
1170proc FSBoxFSFileSelect { fsBoxW fsBoxY} {
1171# xf ignore me 6
1172 global fsBox
1173
1174 FSBoxBindSelectOne $fsBoxW $fsBoxY
1175 set fsBoxNearest [$fsBoxW nearest $fsBoxY]
1176 if {$fsBoxNearest >= 0} {
1177 set fsBoxTmpEntry [$fsBoxW get $fsBoxNearest]
1178 if {"[string index $fsBoxTmpEntry [expr [string length $fsBoxTmpEntry]-1]]" == "/" ||
1179 "[string index $fsBoxTmpEntry [expr [string length $fsBoxTmpEntry]-1]]" == "@"} {
1180 set fsBoxFileName [string range $fsBoxTmpEntry 0 [expr [string length $fsBoxTmpEntry]-2]]
1181 if {![IsADir [string trimright $fsBox(internalPath)/$fsBoxFileName @]] &&
1182 ![IsASymlink [string trimright $fsBox(internalPath)/$fsBoxFileName @]]} {
1183 set fsBoxFileName $fsBoxTmpEntry
1184 }
1185 } {
1186 if {"[string index $fsBoxTmpEntry [expr [string length $fsBoxTmpEntry]-1]]" == "*"} {
1187 set fsBoxFileName [string range $fsBoxTmpEntry 0 [expr [string length $fsBoxTmpEntry]-2]]
1188 if {![file executable $fsBox(internalPath)/$fsBoxFileName]} {
1189 set fsBoxFileName $fsBoxTmpEntry
1190 }
1191 } {
1192 set fsBoxFileName $fsBoxTmpEntry
1193 }
1194 }
1195 if {![IsADir [string trimright $fsBox(internalPath)/$fsBoxFileName @]]} {
1196 set fsBox(name) $fsBoxFileName
1197 .fsBox.file.file delete 0 end
1198 .fsBox.file.file insert 0 $fsBox(name)
1199 }
1200 }
1201}
1202
1203proc FSBoxFSFileSelectDouble { fsBoxW fsBoxY} {
1204# xf ignore me 6
1205 global fsBox
1206
1207 FSBoxBindSelectOne $fsBoxW $fsBoxY
1208 set fsBoxNearest [$fsBoxW nearest $fsBoxY]
1209 if {$fsBoxNearest >= 0} {
1210 set fsBoxTmpEntry [$fsBoxW get $fsBoxNearest]
1211 if {"$fsBoxTmpEntry" == "../"} {
1212 set fsBoxTmpEntry [string trimright [string trim $fsBox(internalPath)] "@/"]
1213 if {"$fsBoxTmpEntry" == ""} {
1214 return
1215 }
1216 FSBoxFSShow [file dirname $fsBoxTmpEntry] [.fsBox.pattern.pattern get] $fsBox(all)
1217 .fsBox.path.path delete 0 end
1218 .fsBox.path.path insert 0 $fsBox(internalPath)
1219 } {
1220 if {"[string index $fsBoxTmpEntry [expr [string length $fsBoxTmpEntry]-1]]" == "/" ||
1221 "[string index $fsBoxTmpEntry [expr [string length $fsBoxTmpEntry]-1]]" == "@"} {
1222 set fsBoxFileName [string range $fsBoxTmpEntry 0 [expr [string length $fsBoxTmpEntry]-2]]
1223 if {![IsADir [string trimright $fsBox(internalPath)/$fsBoxFileName @]] &&
1224 ![IsASymlink [string trimright $fsBox(internalPath)/$fsBoxFileName @]]} {
1225 set fsBoxFileName $fsBoxTmpEntry
1226 }
1227 } {
1228 if {"[string index $fsBoxTmpEntry [expr [string length $fsBoxTmpEntry]-1]]" == "*"} {
1229 set fsBoxFileName [string range $fsBoxTmpEntry 0 [expr [string length $fsBoxTmpEntry]-2]]
1230 if {![file executable $fsBox(internalPath)/$fsBoxFileName]} {
1231 set fsBoxFileName $fsBoxTmpEntry
1232 }
1233 } {
1234 set fsBoxFileName $fsBoxTmpEntry
1235 }
1236 }
1237 if {[IsADir [string trimright $fsBox(internalPath)/$fsBoxFileName @]]} {
1238 set fsBox(internalPath) "[string trimright $fsBox(internalPath) {/@}]/$fsBoxFileName"
1239 FSBoxFSShow $fsBox(internalPath) [.fsBox.pattern.pattern get] $fsBox(all)
1240 .fsBox.path.path delete 0 end
1241 .fsBox.path.path insert 0 $fsBox(internalPath)
1242 } {
1243 set fsBox(name) $fsBoxFileName
1244 set fsBox(path) $fsBox(internalPath)
1245 if {"[info commands XFDestroy]" != ""} {
1246 catch {XFDestroy .fsBox}
1247 } {
1248 catch {destroy .fsBox}
1249 }
1250 }
1251 }
1252 }
1253}
1254
1255proc FSBoxFSShow { fsBoxPath fsBoxPattern fsBoxAll} {
1256# xf ignore me 6
1257 global fsBox
1258
1259 set fsBox(pattern) $fsBoxPattern
1260
1261 if {[file exists $fsBoxPath] && [file readable $fsBoxPath] &&
1262 [IsADir $fsBoxPath]} {
1263 set fsBox(internalPath) $fsBoxPath
1264 } {
1265 if {[file exists $fsBoxPath] && [file readable $fsBoxPath] &&
1266 [IsAFile $fsBoxPath]} {
1267 set fsBox(internalPath) [file dirname $fsBoxPath]
1268 .fsBox.file.file delete 0 end
1269 .fsBox.file.file insert 0 [file tail $fsBoxPath]
1270 set fsBoxPath $fsBox(internalPath)
1271 } {
1272 while {"$fsBoxPath" != "" && "$fsBoxPath" != "/" &&
1273 ![file isdirectory $fsBoxPath]} {
1274 set fsBox(internalPath) [file dirname $fsBoxPath]
1275 set fsBoxPath $fsBox(internalPath)
1276 }
1277 }
1278 }
1279 if {"$fsBoxPath" == ""} {
1280 set fsBoxPath "/"
1281 set fsBox(internalPath) "/"
1282 }
1283 .fsBox.path.path delete 0 end
1284 .fsBox.path.path insert 0 $fsBox(internalPath)
1285
1286 if {[.fsBox.files.files size] > 0} {
1287 .fsBox.files.files delete 0 end
1288 }
1289 if {$fsBoxAll} {
1290 if {[catch "exec ls -F -a $fsBoxPath" fsBoxResult]} {
1291 puts stderr "$fsBoxResult"
1292 }
1293 } {
1294 if {[catch "exec ls -F $fsBoxPath" fsBoxResult]} {
1295 puts stderr "$fsBoxResult"
1296 }
1297 }
1298 set fsBoxElementList [lsort $fsBoxResult]
1299
1300 foreach fsBoxCounter [winfo children .fsBox.pattern.patterns.patterns] {
1301 if {[string length [info commands XFDestroy]] > 0} {
1302 catch {XFDestroy $fsBoxCounter}
1303 } {
1304 catch {destroy $fsBoxCounter}
1305 }
1306 }
1307 menu .fsBox.pattern.patterns.patterns.menu
1308
1309 if {"$fsBoxPath" != "/"} {
1310 .fsBox.files.files insert end "../"
1311 }
1312 foreach fsBoxCounter $fsBoxElementList {
1313 if {[string match $fsBoxPattern $fsBoxCounter] ||
1314 [IsADir [string trimright $fsBoxPath/$fsBoxCounter "/@"]]} {
1315 if {"$fsBoxCounter" != "../" &&
1316 "$fsBoxCounter" != "./"} {
1317 .fsBox.files.files insert end $fsBoxCounter
1318 }
1319 }
1320 }
1321}
1322
1323proc IsADir { pathName} {
1324# xf ignore me 5
1325 if {[file isdirectory $pathName]} {
1326 return 1
1327 } {
1328 catch "file type $pathName" fileType
1329 if {"$fileType" == "link"} {
1330 if {[catch "file readlink $pathName" linkName]} {
1331 return 0
1332 }
1333 catch "file type $linkName" fileType
1334 while {"$fileType" == "link"} {
1335 if {[catch "file readlink $linkName" linkName]} {
1336 return 0
1337 }
1338 catch "file type $linkName" fileType
1339 }
1340 return [file isdirectory $linkName]
1341 }
1342 }
1343 return 0
1344}
1345
1346proc IsAFile { fileName} {
1347# xf ignore me 5
1348 if {[file isfile $fileName]} {
1349 return 1
1350 } {
1351 catch "file type $fileName" fileType
1352 if {"$fileType" == "link"} {
1353 if {[catch "file readlink $fileName" linkName]} {
1354 return 0
1355 }
1356 catch "file type $linkName" fileType
1357 while {"$fileType" == "link"} {
1358 if {[catch "file readlink $linkName" linkName]} {
1359 return 0
1360 }
1361 catch "file type $linkName" fileType
1362 }
1363 return [file isfile $linkName]
1364 }
1365 }
1366 return 0
1367}
1368
1369proc IsASymlink { fileName} {
1370# xf ignore me 5
1371 catch "file type $fileName" fileType
1372 if {"$fileType" == "link"} {
1373 return 1
1374 }
1375 return 0
1376}
Note: See TracBrowser for help on using the repository browser.