First version of graphical Scan. Some work on query-by-form.
[ir-tcl-moved-to-github.git] / client.tcl
1 #
2 # $Log: client.tcl,v $
3 # Revision 1.19  1995-04-18 16:11:50  adam
4 # First version of graphical Scan. Some work on query-by-form.
5 #
6 # Revision 1.18  1995/04/10  10:50:22  adam
7 # Result-set name defaults to suffix of ir-set name.
8 # Started working on scan. Not finished at this point.
9 #
10 # Revision 1.17  1995/03/31  09:34:57  adam
11 # Search-button disabled when there is no connection.
12 #
13 # Revision 1.16  1995/03/31  08:56:36  adam
14 # New button "Search".
15 #
16 # Revision 1.15  1995/03/28  12:45:22  adam
17 # New ir method failback: called on disconnect/protocol error.
18 # New ir set/get method: protocol: SR / Z3950.
19 # Simple popup and disconnect when failback is invoked.
20 #
21 # Revision 1.14  1995/03/22  16:07:55  adam
22 # Minor changes.
23 #
24 # Revision 1.13  1995/03/21  17:27:26  adam
25 # Short-hand keys in setup.
26 #
27 # Revision 1.12  1995/03/21  13:41:03  adam
28 # Comstack cs_create not used too often. Non-blocking connect.
29 #
30 # Revision 1.11  1995/03/21  10:39:06  adam
31 # Diagnostic error message displayed with tkerror.
32 #
33 # Revision 1.10  1995/03/20  15:24:06  adam
34 # Diagnostic records saved on searchResponse.
35 #
36 # Revision 1.9  1995/03/17  18:26:16  adam
37 # Non-blocking i/o used now. Database names popup as cascade items.
38 #
39 # Revision 1.8  1995/03/17  15:45:00  adam
40 # Improved target/database setup.
41 #
42 # Revision 1.7  1995/03/16  17:54:03  adam
43 # Minor changes really.
44 #
45 # Revision 1.6  1995/03/15  19:10:20  adam
46 # Database setup in protocol-setup (rather target setup).
47 #
48 # Revision 1.5  1995/03/15  13:59:23  adam
49 # Minor changes.
50 #
51 # Revision 1.4  1995/03/14  17:32:29  adam
52 # Presentation of full Marc record in popup window.
53 #
54 # Revision 1.3  1995/03/12  19:31:52  adam
55 # Pattern matching implemented when retrieving MARC records. More
56 # diagnostic functions.
57 #
58 # Revision 1.2  1995/03/10  18:00:15  adam
59 # Actual presentation in line-by-line format. RPN query support.
60 #
61 # Revision 1.1  1995/03/09  16:15:07  adam
62 # First presentRequest attempts. Hot-target list.
63 #
64 #
65 set hotTargets {}
66 set hotInfo {}
67 set busy 0
68
69 set profile(Default) {{} {} {210} {} 16384 8192 tcpip {}}
70 set hostid Default
71 set settingsChanged 0
72 set setNo 0
73
74 set queryTypes {Simple}
75 set queryButtons { { {I 0} {I 1} {I 2} } }
76 set queryInfo { { {Title ti} {Author au} {Subject sh} {Any any} } }
77
78 wm minsize . 300 250
79
80 if {[file readable "~/.tk-c"]} {
81     source "~/.tk-c"
82 }
83
84 set queryButtonsFind [lindex $queryButtons 0]
85 set queryInfoFind [lindex $queryInfo 0]
86
87 proc top-down-window {w} {
88     frame $w.top -relief raised -border 1
89     frame $w.bot -relief raised -border 1
90     
91     pack  $w.top $w.bot -side top -fill both -expand yes
92 }
93
94 proc top-down-ok-cancel {w ok-action g} {
95     frame $w.bot.left -relief sunken -border 1
96     pack $w.bot.left -side left -expand yes -padx 5 -pady 5
97     button $w.bot.left.ok -width 6 -text {Ok} \
98             -command ${ok-action}
99     pack $w.bot.left.ok -expand yes -padx 3 -pady 3
100     button $w.bot.cancel -width 6 -text {Cancel} \
101             -command "destroy $w"
102     pack $w.bot.cancel -side left -expand yes    
103
104     if {$g} {
105         # Grab ...
106         grab $w
107         tkwait window $w
108     }
109 }
110
111 proc top-down-ok-cancelx {w buttonList g} {
112     set i 0
113     set l [llength $buttonList]
114
115     frame $w.bot.$i -relief sunken -border 1
116     pack $w.bot.$i -side left -expand yes -padx 5 -pady 5
117     button $w.bot.$i.ok -text [lindex $buttonList $i] \
118             -command [lindex $buttonList [expr $i+1]]
119     pack $w.bot.$i.ok -expand yes -padx 3 -pady 3 -side left
120
121     incr i 2
122     while {$i < $l} {
123         button $w.bot.$i -text [lindex $buttonList $i] \
124                 -command [lindex $buttonList [expr $i+1]]
125         pack $w.bot.$i -expand yes -padx 3 -pady 3 -side left
126         incr i 2
127     }
128     button $w.bot.cancel -width 6 -text {Cancel} \
129             -command "destroy $w"
130     pack $w.bot.cancel -side left -expand yes    
131     
132     if {$g} {
133         # Grab ...
134         grab $w
135         tkwait window $w
136     }
137 }
138
139 proc show-target {target} {
140     .bot.target configure -text "$target"
141 }
142
143 proc show-busy {v1 v2} {
144     global busy
145     if {$busy != 0} {
146         .bot.status configure -fg $v1
147         after 200 [list show-busy $v2 $v1]
148     }
149 }
150         
151 proc show-status {status b} {
152     global busy
153     global statusbg
154     .bot.status configure -text "$status"
155     .bot.status configure -fg black
156     if {$b != 0} {
157         if {$busy == 0} {
158             set busy $b   
159             show-busy red blue
160         }
161         #        . config -cursor {watch black white}
162     } else {
163         #        . config -cursor {top_left_arrow black white}
164         puts "Normal"
165     }
166     set busy $b
167 }
168
169 proc show-message {msg} {
170     .bot.message configure -text "$msg"
171 }
172
173 proc insertWithTags {w text args} {
174     set start [$w index insert]
175     $w insert insert $text
176     foreach tag [$w tag names $start] {
177         $w tag remove $tag $start insert
178     }
179     foreach i $args {
180         $w tag add $i $start insert
181     }
182 }
183
184 proc show-full-marc {no} {
185     global setNo
186
187     set w .full-marc
188
189     if {[winfo exists $w]} {
190         $w.top.record delete 0.0 end
191         set new 0
192     } else {
193
194         toplevel $w
195
196         wm minsize $w 200 200
197         
198         frame $w.top -relief raised -border 1
199         frame $w.bot -relief raised -border 1
200
201         pack  $w.top -side top -fill both -expand yes
202         pack  $w.bot -fill both
203
204         text $w.top.record -width 60 -height 12 -wrap word \
205                 -yscrollcommand [list $w.top.s set]
206         scrollbar $w.top.s -command [list $w.top.record yview]
207
208         set new 1
209     }
210     incr no
211     
212     set r [z39.$setNo recordMarc $no line * * *]
213
214     $w.top.record tag configure marc-tag -foreground blue
215     $w.top.record tag configure marc-data -foreground black
216     $w.top.record tag configure marc-id -foreground red
217
218     foreach line $r {
219         set tag [lindex $line 0]
220         set indicator [lindex $line 1]
221         set fields [lindex $line 2]
222
223         if {$indicator != ""} {
224             insertWithTags $w.top.record "$tag $indicator" marc-tag
225         } else {
226             insertWithTags $w.top.record "$tag    " marc-tag
227         }
228         foreach field $fields {
229             set id [lindex $field 0]
230             set data [lindex $field 1]
231             if {$id != ""} {
232                 insertWithTags $w.top.record " $id " marc-id
233             }
234             set start [$w.top.record index insert]
235             insertWithTags $w.top.record $data {}
236         }
237         $w.top.record insert end "\n"
238     }
239     if {$new} {
240         bind $w <Return> {destroy .full-marc}
241         
242         pack $w.top.s -side right -fill y
243         pack $w.top.record -expand yes -fill both
244         
245         frame $w.bot.left -relief sunken -border 1
246         pack $w.bot.left -side left -expand yes -padx 5 -pady 5
247         button $w.bot.left.close -width 6 -text {Close} \
248                 -command {destroy .full-marc}
249         pack $w.bot.left.close -expand yes -padx 3 -pady 3
250         button $w.bot.edit -width 6 -text {Edit} \
251                 -command {destroy .full-marc}
252         pack $w.bot.edit -side left -expand yes
253     }
254 }
255
256 proc update-target-hotlist {target} {
257     global hotTargets
258
259     set len [llength $hotTargets]
260     if {$len > 0} {
261         .top.target.m delete 5 [expr 5+[llength $hotTargets]]
262     }
263     set indx [lsearch $hotTargets $target]
264     if {$indx >= 0} {
265         set hotTargets [lreplace $hotTargets $indx $indx]
266     }
267     set hotTargets [linsert $hotTargets 0 $target]
268     set-target-hotlist    
269
270
271 proc set-target-hotlist {} {
272     global hotTargets
273     
274     set i 1
275     foreach target $hotTargets {
276         .top.target.m add command -label "$i $target" -command \
277                 "reopen-target $target {}"
278         incr i
279         if {$i > 8} {
280              break
281         }
282     }
283 }
284
285 proc reopen-target {target base} {
286     close-target
287     open-target $target $base
288     update-target-hotlist $target
289 }
290
291 proc define-target-action {} {
292     global profile
293
294     set target [.target-define.top.target.entry get]
295     if {$target == ""} {
296         return
297     }
298     update-target-hotlist $target
299     foreach n [array names profile] {
300         if {$n == $target} {
301             protocol-setup $n
302             return
303         }
304     }
305     set profile($target) $profile(Default)
306     protocol-setup $target
307     destroy .target-define
308 }
309
310 proc fail-response {target} {
311     close-target
312     tkerror "Target connection closed or protocol error"
313 }
314
315 proc connect-response {target} {
316     puts "connect-response"
317     show-target $target
318     init-request
319 }
320
321 proc open-target {target base} {
322     global profile
323     global hostid
324
325     z39 disconnect
326     z39 comstack [lindex $profile($target) 6]
327     # z39 idAuthentication [lindex $profile($target) 3]
328     z39 maximumRecordSize [lindex $profile($target) 4]
329     z39 preferredMessageSize [lindex $profile($target) 5]
330     puts -nonewline "maximumRecordSize="
331     puts [z39 maximumRecordSize]
332     puts -nonewline "preferredMessageSize="
333     puts [z39 preferredMessageSize]
334     if {$base == ""} {
335         z39 databaseNames [lindex [lindex $profile($target) 7] 0]
336     } else {
337         z39 databaseNames $base
338     }
339     z39 failback [list fail-response $target]
340     z39 callback [list connect-response $target]
341     z39 connect [lindex $profile($target) 1]:[lindex $profile($target) 2]
342     show-status {Connecting} 1
343     set hostid $target
344     .top.target.m disable 0
345     .top.target.m enable 1
346 }
347
348 proc close-target {} {
349     global hostid
350
351     set hostid Default
352     z39 disconnect
353     show-target {None}
354     show-status {Not connected} 0
355     show-message {}
356     .top.target.m disable 1
357     .top.target.m enable 0
358     .top.search configure -state disabled
359     .mid.search configure -state disabled
360     .mid.scan configure -state disabled
361 }
362
363 proc load-set-action {} {
364     global setNo
365
366     incr setNo
367     ir-set z39.$setNo
368
369     set fname [.load-set.top.filename.entry get]
370     destroy .load-set
371     if {$fname != ""} {
372         init-title-lines
373
374         show-status {Loading} 1
375         z39.$setNo loadFile $fname
376
377         set no [z39.$setNo numberOfRecordsReturned]
378         add-title-lines $setNo $no 1
379     }
380     show-status {Ready} 0
381 }
382
383 proc load-set {} {
384     set w .load-set
385
386     set oldFocus [focus]
387     toplevel $w
388
389     place-force $w .
390
391     top-down-window $w
392
393     frame $w.top.filename
394     
395     pack $w.top.filename -side top -anchor e -pady 2
396     
397     entry-fields $w.top {filename} \
398             {{Filename:}} \
399             {load-set-action} {destroy .load-set}
400     
401     top-down-ok-cancel $w {load-set-action} 1
402     focus $oldFocus
403 }
404
405 proc init-request {} {
406     global setNo
407     
408     z39 callback {init-response}
409     z39 init
410     show-status {Initializing} 1
411 }
412
413 proc init-response {} {
414     show-status {Ready} 0
415     .top.search configure -state normal
416     .mid.search configure -state normal
417     .mid.scan configure -state normal
418 }
419
420 proc search-request {} {
421     global setNo
422     global profile
423     global hostid
424
425     set target $hostid
426
427     set query [index-query]
428     if {$query==""} {
429         return
430     }
431     incr setNo
432     ir-set z39.$setNo
433
434
435     if {[lindex $profile($target) 10]} {
436         z39.$setNo setName $setNo
437     } else {
438         z39.$setNo setName Default
439     }
440     if {[lindex $profile($target) 8]} {
441         z39 query rpn
442     }
443     if {[lindex $profile($target) 9]} {
444         z39 query ccl
445     }
446     z39 callback {search-response}
447     z39.$setNo search $query
448     show-status {Search} 1
449 }
450
451 proc scan-request {} {
452     set w .scan-window
453
454     global profile
455     global hostid
456
457     set target $hostid
458
459     ir-scan z39.scan
460
461     z39 callback {scan-response}
462     if {![winfo exists $w]} {
463         toplevel $w
464         
465         wm title $w "Scan"
466         
467         wm minsize $w 200 200
468
469         top-down-window $w
470         
471         listbox $w.top.list -yscrollcommand [list $w.top.scroll set] \
472                 -font fixed -geometry 50x14
473         scrollbar $w.top.scroll -orient vertical -border 1
474         pack $w.top.list -side left -fill both -expand yes
475         pack $w.top.scroll -side right -fill y
476         $w.top.scroll config -command [list $w.top.list yview]
477
478         top-down-ok-cancelx $w [list {Close} [list destroy $w]] 0 
479     }
480     z39.scan scan 0
481     
482     show-status {Scan} 1
483 }
484
485 proc scan-response {} {
486     set w .scan-window
487     set m [z39.scan numberOfEntriesReturned]
488     puts $m
489     for {set i 0} {$i < $m} {incr i} {
490         set term [lindex [z39.scan scanLine $i] 1]
491         set nostr [format "%7d" [lindex [z39.scan scanLine $i] 2]]
492
493         $w.top.list insert end "$nostr $term"
494     }
495     show-status {Ready} 0
496 }
497
498 proc search-response {} {
499     global setNo
500     global setOffset
501     global setMax
502
503     init-title-lines
504     show-status {Ready} 0
505     show-message "[z39.$setNo resultCount] hits"
506     set setMax [z39.$setNo resultCount]
507     puts $setMax
508     if {$setMax == 0} {
509         set status [z39.$setNo responseStatus]
510         if {[lindex $status 0] == "NSD"} {
511             set code [lindex $status 1]
512             set msg [lindex $status 2]
513             set addinfo [lindex $status 3]
514             tkerror "NSD$code: $msg: $addinfo"
515         }
516         return
517     }
518     if {$setMax > 4} {
519         set setMax 4
520     }
521     z39 callback {present-response}
522     set setOffset 1
523     z39.$setNo present $setOffset $setMax
524     show-status {Retrieve} 1
525 }
526
527 proc present-more {number} {
528     global setNo
529     global setOffset
530     global setMax
531
532     puts "present-more"
533     if {$setNo == 0} {
534         return
535     }
536     set max [z39.$setNo resultCount]
537     if {$max <= $setMax} {
538         return
539     }
540     puts "max=$max"
541     puts "setOffset=$setOffset"
542     if {$number == ""} {
543         set setMax $max
544     } else {
545         incr setMax $number
546     }
547     z39 callback {present-response}
548     z39.$setNo present $setOffset [expr $setMax - $setOffset + 1]
549     show-status {Retrieve} 1
550 }
551
552 proc init-title-lines {} {
553     .data.list delete 0 end
554 }
555
556 proc add-title-lines {setno no offset} {
557     for {set i 0} {$i < $no} {incr i} {
558         set o [expr $i + $offset]
559         set title [lindex [z39.$setno recordMarc $o field 245 * a] 0]
560         set year  [lindex [z39.$setno recordMarc $o field 260 * c] 0]
561         set nostr [format "%5d" $o]
562         .data.list insert end "$nostr $title - $year"
563     }
564 }
565
566 proc present-response {} {
567     global setNo
568     global setOffset
569     global setMax
570
571     puts "In present-response"
572     set no [z39.$setNo numberOfRecordsReturned]
573     puts "Returned $no records, setOffset $setOffset"
574     add-title-lines $setNo $no $setOffset
575     set setOffset [expr $setOffset + $no]
576     set status [z39.$setNo responseStatus]
577     if {[lindex $status 0] == "NSD"} {
578         show-status {Ready} 0
579         set code [lindex $status 1]
580         set msg [lindex $status 2]
581         set addinfo [lindex $status 3]
582         tkerror "NSD$code: $msg: $addinfo"
583         return
584     }
585     if {$no > 0 && $setOffset <= $setMax} {
586         z39.$setNo present $setOffset [expr $setMax - $setOffset + 1]
587     } else {
588         show-status {Finished} 0
589     }
590 }
591
592 proc left-cursor {w} {
593     set i [$w index insert]
594     if {$i > 0} {
595         incr i -1
596         $w icursor $i
597     }
598 }
599
600 proc right-cursor {w} {
601     set i [$w index insert]
602     incr i
603     $w icursor $i
604 }
605
606 proc bind-fields {list returnAction escapeAction} {
607     set max [expr [llength $list]-1]
608     for {set i 0} {$i < $max} {incr i} {
609         bind [lindex $list $i] <Return> $returnAction
610         bind [lindex $list $i] <Escape> $escapeAction
611         bind [lindex $list $i] <Tab> [list focus [lindex $list [expr $i+1]]]
612         bind [lindex $list $i] <Left> [list left-cursor [lindex $list $i]]
613         bind [lindex $list $i] <Right> [list right-cursor [lindex $list $i]]
614     }
615     bind [lindex $list $i] <Return> $returnAction
616     bind [lindex $list $i] <Escape> $escapeAction
617     bind [lindex $list $i] <Tab>    [list focus [lindex $list 0]]
618     bind [lindex $list $i] <Left> [list left-cursor [lindex $list $i]]
619     bind [lindex $list $i] <Right> [list right-cursor [lindex $list $i]]
620     focus [lindex $list 0]
621 }
622
623 proc entry-fields {parent list tlist returnAction escapeAction} {
624     set alist {}
625     set i 0
626     foreach field $list {
627         set label ${parent}.${field}.label
628         set entry ${parent}.${field}.entry
629         label $label -text [lindex $tlist $i] -anchor e
630         entry $entry -width 32 -relief sunken
631         pack $label -side left
632         pack $entry -side right
633         lappend alist $entry
634         incr i
635     }
636     bind-fields $alist $returnAction $escapeAction
637 }
638
639 proc define-target-dialog {} {
640     set w .target-define
641
642     toplevel $w
643     place-force $w .
644     top-down-window $w
645     frame $w.top.target
646     pack $w.top.target \
647             -side top -anchor e -pady 2 
648     entry-fields $w.top {target} \
649             {{Target:}} \
650             {define-target-action} {destroy .target-define}
651     top-down-ok-cancel $w {define-target-action} 1
652 }
653
654 proc protocol-setup-action {target} {
655     global profile
656     global csRadioType
657     global settingsChanged
658     global RPNCheck
659     global CCLCheck
660     global ResultSetCheck
661
662     set w .setup-${target}.top
663
664     #set w .protocol-setup.top
665     
666     set b {}
667     set settingsChanged 1
668     set len [$w.databases.list size]
669     for {set i 0} {$i < $len} {incr i} {
670         lappend b [$w.databases.list get $i]
671     }
672     set profile($target) [list [$w.description.entry get] \
673             [$w.host.entry get] \
674             [$w.port.entry get] \
675             [$w.idAuthentication.entry get] \
676             [$w.maximumRecordSize.entry get] \
677             [$w.preferredMessageSize.entry get] \
678             $csRadioType \
679             $b \
680             $RPNCheck \
681             $CCLCheck \
682             $ResultSetCheck ]
683
684     cascade-target-list
685     puts $profile($target)
686     destroy .setup-${target}
687 }
688
689 proc place-force {window parent} {
690     set g [wm geometry $parent]
691
692     set p1 [string first + $g]
693     set p2 [string last + $g]
694
695     set x [expr 40+[string range $g [expr $p1 +1] [expr $p2 -1]]]
696     set y [expr 60+[string range $g [expr $p2 +1] end]]
697     wm geometry $window +${x}+${y}
698 }
699
700 proc add-database-action {target} {
701     set w .setup-${target}
702     
703     ${w}.top.databases.list insert end \
704             [.database-select.top.database.entry get]
705     destroy .database-select
706 }
707
708 proc add-database {target} {
709     set w .database-select
710
711     set oldFocus [focus]
712     toplevel $w
713
714     place-force $w .setup-${target}
715
716     top-down-window $w
717
718     frame $w.top.database
719
720     pack $w.top.database -side top -anchor e -pady 2
721     
722     entry-fields $w.top {database} \
723             {{Database to add:}} \
724             [list add-database-action $target] {destroy .database-select}
725
726     top-down-ok-cancel $w [list add-database-action $target] 1
727     focus $oldFocus
728 }
729
730 proc delete-database {target} {
731     set w .setup-${target}
732     
733     foreach i [lsort -decreasing \
734             [$w.top.databases.list curselection]] {
735         $w.top.databases.list delete $i
736     }
737 }
738
739 proc protocol-setup {target} {
740     set w .setup-$target
741
742     global profile
743     global csRadioType
744     global RPNCheck
745     global CCLCheck
746     global ResultSetCheck
747
748     toplevel $w
749
750     wm title $w "Setup $target"
751     place-force $w .
752
753     top-down-window $w
754     
755     if {$target == ""} {
756         set target Default
757     }
758     puts target
759     puts $profile($target)
760
761     frame $w.top.host
762     frame $w.top.port
763     frame $w.top.description
764     frame $w.top.idAuthentication
765     frame $w.top.maximumRecordSize
766     frame $w.top.preferredMessageSize
767     frame $w.top.cs-type -relief ridge -border 2
768     frame $w.top.query -relief ridge -border 2
769     frame $w.top.databases -relief ridge -border 2
770
771     # Maximum/preferred/idAuth ...
772     pack $w.top.description $w.top.host $w.top.port \
773             $w.top.idAuthentication $w.top.maximumRecordSize \
774             $w.top.preferredMessageSize -side top -anchor e -pady 2
775     
776     entry-fields $w.top {description host port idAuthentication \
777             maximumRecordSize preferredMessageSize} \
778             {{Description:} {Host:} {Port:} {Id Authentification:} \
779             {Maximum Record Size:} {Preferred Message Size:}} \
780             [list protocol-setup-action $target] [list destroy $w]
781     
782     foreach sub {description host port idAuthentication \
783             maximumRecordSize preferredMessageSize} {
784         puts $sub
785         bind $w.top.$sub.entry <Control-a> "add-database $target"
786         bind $w.top.$sub.entry <Control-d> "delete-database $target"
787     }
788     $w.top.description.entry insert 0 [lindex $profile($target) 0]
789     $w.top.host.entry insert 0 [lindex $profile($target) 1]
790     $w.top.port.entry insert 0 [lindex $profile($target) 2]
791     $w.top.idAuthentication.entry insert 0 [lindex $profile($target) 3]
792     $w.top.maximumRecordSize.entry insert 0 [lindex $profile($target) 4]
793     $w.top.preferredMessageSize.entry insert 0 [lindex $profile($target) 5]
794     set csRadioType [lindex $profile($target) 6]
795     set RPNCheck [lindex $profile($target) 8]
796     set CCLCheck [lindex $profile($target) 9]
797     set ResultSetCheck [lindex $profile($target) 10]
798
799     # Databases ....
800     pack $w.top.databases -side left -pady 6 -padx 6 -expand yes -fill x
801
802     label $w.top.databases.label -text "Databases"
803     button $w.top.databases.add -text "Add" \
804             -command "add-database $target"
805     button $w.top.databases.delete -text "Delete" \
806             -command "delete-database $target"
807     listbox $w.top.databases.list -geometry 20x6 \
808             -yscrollcommand "$w.top.databases.scroll set"
809     scrollbar $w.top.databases.scroll -orient vertical -border 1
810     pack $w.top.databases.label -side top -fill x \
811             -padx 2 -pady 2
812     pack $w.top.databases.add $w.top.databases.delete -side top -fill x \
813             -padx 2 -pady 2
814     pack $w.top.databases.list -side left -fill both -expand yes \
815             -padx 2 -pady 2
816     pack $w.top.databases.scroll -side right -fill y \
817             -padx 2 -pady 2
818     $w.top.databases.scroll config -command "$w.top.databases.list yview"
819
820     foreach b [lindex $profile($target) 7] {
821         $w.top.databases.list insert end $b
822     }
823
824     # Transport ...
825     pack $w.top.cs-type -pady 6 -padx 6 -side top
826     
827     label $w.top.cs-type.label -text "Transport" 
828     radiobutton $w.top.cs-type.tcpip -text "TCP/IP" \
829             -command {puts tcp/ip} -variable csRadioType -value tcpip
830     radiobutton $w.top.cs-type.mosi -text "MOSI" \
831             -command {puts mosi} -variable csRadioType -value mosi
832     
833     pack $w.top.cs-type.label $w.top.cs-type.tcpip $w.top.cs-type.mosi \
834             -padx 4 -side top -fill x
835
836     # Query ...
837     pack $w.top.query -pady 6 -padx 6 -side top
838
839     label $w.top.query.label -text "Query support" -anchor e
840     checkbutton $w.top.query.c1 -text "RPN query" -variable RPNCheck
841     checkbutton $w.top.query.c2 -text "CCL query" -variable CCLCheck
842     checkbutton $w.top.query.c3 -text "Result sets" -variable ResultSetCheck
843
844     pack $w.top.query.label -side top 
845     pack $w.top.query.c1 $w.top.query.c2 $w.top.query.c3 \
846             -padx 4 -side top -fill x
847
848     # Ok-cancel
849     top-down-ok-cancel $w [list protocol-setup-action $target] 0
850 }
851
852 proc database-select-action {} {
853     set w .database-select.top
854     set b {}
855     foreach indx [$w.databases.list curselection] {
856         lappend b [$w.databases.list get $indx]
857     }
858     if {$b != ""} {
859         z39 databaseNames $b
860     }
861     destroy .database-select
862 }
863
864 proc database-select {} {
865     set w .database-select
866     global profile
867     global hostid
868
869     toplevel $w
870
871     place-force $w .
872
873     top-down-window $w
874
875     frame $w.top.databases -relief ridge -border 2
876
877     pack $w.top.databases -side left -pady 6 -padx 6 -expand yes -fill x
878
879     label $w.top.databases.label -text "List"
880     listbox $w.top.databases.list -geometry 20x6 \
881             -yscrollcommand "$w.top.databases.scroll set"
882     scrollbar $w.top.databases.scroll -orient vertical -border 1
883     pack $w.top.databases.label -side top -fill x \
884             -padx 2 -pady 2
885     pack $w.top.databases.list -side left -fill both -expand yes \
886             -padx 2 -pady 2
887     pack $w.top.databases.scroll -side right -fill y \
888             -padx 2 -pady 2
889     $w.top.databases.scroll config -command "$w.top.databases.list yview"
890
891     foreach b [lindex $profile($hostid) 7] {
892         $w.top.databases.list insert end $b
893     }
894     top-down-ok-cancel $w {database-select-action} 1
895 }
896
897 proc cascade-target-list {} {
898     global profile
899     
900     foreach sub [winfo children .top.target.m.clist] {
901         puts "deleting $sub"
902         destroy $sub
903     }
904     .top.target.m.clist delete 0 last
905     foreach n [array names profile] {
906         if {$n != "Default"} {
907             set nl [string tolower $n]
908             if {[llength [lindex $profile($n) 7]] > 1} {
909                 .top.target.m.clist add cascade -label $n \
910                         -menu .top.target.m.clist.$nl
911                 menu .top.target.m.clist.$nl
912                 foreach b [lindex $profile($n) 7] {
913                     .top.target.m.clist.$nl add command -label $b \
914                             -command "reopen-target $n $b"
915                 }
916             } else {
917                 .top.target.m.clist add command -label $n \
918                         -command "reopen-target $n {}"
919             }
920         }
921     }
922     .top.target.m.slist delete 0 last
923     foreach n [array names profile] {
924         if {$n != "Default"} {
925             .top.target.m.slist add command -label $n \
926                     -command "protocol-setup $n"
927         }
928     }
929 }
930
931 proc cascade-query-list {} {
932     global queryTypes
933
934     set i 0
935     .top.query.m.slist delete 0 last
936     foreach n $queryTypes {
937         .top.query.m.slist add command -label $n \
938                 -command [list query-setup $i]
939         incr i
940     }
941
942     set i 0
943     .top.query.m.clist delete 0 last
944     foreach n $queryTypes {
945         .top.query.m.clist add command -label $n \
946                 -command [list query-select $i]
947         incr i
948     }
949 }
950
951 proc save-settings {} {
952     global hotTargets 
953     global profile
954     global settingsChanged
955     global queryTypes
956     global queryButtons
957     global queryInfo
958
959     set f [open "~/.tk-c" w]
960     puts $f "# Setup file"
961     puts $f "set hotTargets \{ $hotTargets \}"
962
963     foreach n [array names profile] {
964         puts -nonewline $f "set profile($n) \{"
965         puts -nonewline $f $profile($n)
966         puts $f "\}"
967     }
968     puts -nonewline $f "set queryTypes \{" 
969     puts -nonewline $f $queryTypes
970     puts $f "\}"
971     
972     puts -nonewline $f "set queryButtons \{" 
973     puts -nonewline $f $queryButtons
974     puts $f "\}"
975     
976     puts -nonewline $f "set queryInfo \{"
977     puts -nonewline $f $queryInfo
978     puts $f "\}"
979     
980     close $f
981     set settingsChanged 0
982 }
983
984 proc alert {ask} {
985     set w .alert
986
987     global alertAnswer
988
989     toplevel $w
990     place-force $w .
991     top-down-window $w
992
993     message $w.top.message -text $ask
994
995     pack $w.top.message -side left -pady 6 -padx 20 -expand yes -fill x
996   
997     set alertAnswer 0
998     top-down-ok-cancel $w {alert-action} 1
999     return $alertAnswer
1000 }
1001
1002 proc alert-action {} {
1003     global alertAnswer
1004     set alertAnswer 1
1005     destroy .alert
1006 }
1007
1008 proc exit-action {} {
1009     global settingsChanged
1010
1011     if {$settingsChanged} {
1012         set a [alert "you havent saved your settings. Do you wish to save?"]
1013         if {$a} {
1014             save-settings
1015         }
1016     }
1017     destroy .
1018 }
1019
1020 proc listbuttonaction {w name h user i} {
1021     $w configure -text [lindex $name 0]
1022     $h [lindex $name 1] $user $i
1023 }
1024     
1025 proc listbuttonx {button no names handle user} {
1026     if {[winfo exists $button]} {
1027         $button configure -text [lindex [lindex $names $no] 0]
1028         ${button}.m delete 0 last
1029     } else {
1030         menubutton $button -text [lindex [lindex $names $no] 0] \
1031                 -width 10 -menu ${button}.m -relief raised -border 1
1032         menu ${button}.m
1033     }
1034     set i 0
1035     foreach name $names {
1036         ${button}.m add command -label [lindex $name 0] \
1037                 -command [list listbuttonaction ${button} $name \
1038                 $handle $user $i]
1039         incr i
1040     }
1041 }
1042
1043 proc listbutton {button no names} {
1044     menubutton $button -text [lindex $names $no] -width 10 -menu ${button}.m \
1045             -relief raised -border 1
1046     menu ${button}.m
1047     foreach name $names {
1048         ${button}.m add command -label $name \
1049                 -command [list ${button} configure -text $name]
1050     }
1051 }
1052
1053 proc query-add-index-action {queryNo} {
1054     set w .setup-query-$queryNo
1055
1056     global queryInfoTmp
1057     global queryButtonsTmp
1058
1059     lappend queryInfoTmp [list [.query-add-index.top.index.entry get] {}]
1060
1061     destroy .query-add-index
1062     #destroy $w.top.lines
1063     #frame $w.top.lines -relief ridge -border 2
1064     index-lines $w.top.lines 0 $queryButtonsTmp $queryInfoTmp activate-e-index
1065     #pack $w.top.lines -side left -pady 6 -padx 6 -fill y
1066 }
1067
1068 proc query-add-line {queryNo} {
1069     set w .setup-query-$queryNo
1070
1071     global queryInfoTmp
1072     global queryButtonsTmp
1073
1074     lappend queryButtonsTmp {I 0}
1075
1076     #destroy $w.top.lines
1077     #frame $w.top.lines -relief ridge -border 2
1078     index-lines $w.top.lines 0 $queryButtonsTmp $queryInfoTmp activate-e-index
1079     #pack $w.top.lines -side left -pady 6 -padx 6 -fill y
1080 }
1081
1082 proc query-del-line {queryNo} {
1083     set w .setup-query-$queryNo
1084
1085     global queryInfoTmp
1086     global queryButtonsTmp
1087
1088     set l [llength $queryButtonsTmp]
1089     if {$l <= 0} {
1090         return
1091     }
1092     incr l -1
1093     set queryButtonsTmp [lreplace $queryButtonsTmp $l $l]
1094     index-lines $w.top.lines 0 $queryButtonsTmp $queryInfoTmp activate-e-index
1095 }
1096
1097 proc query-add-index {queryNo} {
1098     set w .query-add-index
1099
1100     toplevel $w
1101     place-force $w .setup-query-$queryNo
1102     top-down-window $w
1103     frame $w.top.index
1104     pack $w.top.index \
1105             -side top -anchor e -pady 2 
1106     entry-fields $w.top {index} \
1107             {{Index Name:}} \
1108             [list query-add-index-action $queryNo] {destroy .query-add-index}
1109     top-down-ok-cancel $w [list query-add-index-action $queryNo] 1
1110 }
1111
1112 proc query-setup-action {queryNo} {
1113     global queryButtons
1114     global queryInfo
1115     global queryButtonsTmp
1116     global queryInfoTmp
1117     global queryButtonsFind
1118     global queryInfoFind
1119
1120     set queryInfo [lreplace $queryInfo $queryNo $queryNo \
1121             $queryInfoTmp]
1122     set queryButtons [lreplace $queryButtons $queryNo $queryNo \
1123             $queryButtonsTmp]
1124     set queryInfoFind $queryInfoTmp
1125     set queryButtonsFind $queryButtonsTmp
1126
1127     puts $queryInfo
1128     puts $queryButtons
1129     destroy .setup-query-$queryNo
1130
1131     index-lines .lines 1 $queryButtonsFind $queryInfoFind activate-index
1132 }
1133
1134 proc activate-e-index {value no i} {
1135     global queryButtonsTmp
1136     
1137     puts $queryButtonsTmp
1138     set queryButtonsTmp [lreplace $queryButtonsTmp $no $no [list I $i]]
1139     puts $queryButtonsTmp
1140     puts "value $value"
1141     puts "no $no"
1142     puts "i $i"
1143 }
1144
1145 proc activate-index {value no i} {
1146     global queryButtonsFind
1147
1148     set queryButtonsFind [lreplace $queryButtonsFind $no $no [list I $i]]
1149
1150     puts "queryButtonsFind $queryButtonsFind"
1151     puts "value $value"
1152     puts "no $no"
1153     puts "i $i"
1154 }
1155
1156 proc query-setup {queryNo} {
1157     set w .setup-query-$queryNo
1158     global queryTypes
1159     set queryTypes {Simple}
1160     global queryButtons
1161     global queryInfo
1162     global queryButtonsTmp
1163     global queryInfoTmp
1164
1165     set queryName [lindex $queryTypes $queryNo]
1166     set queryInfoTmp [lindex $queryInfo $queryNo]
1167     set queryButtonsTmp [lindex $queryButtons $queryNo]
1168
1169     #set queryButtons { {I 0 I 1 I 2} }
1170     #set queryInfo { { {Title ti} {Author au} {Subject sh} } }
1171
1172     toplevel $w
1173
1174     wm title $w "Query setup $queryName"
1175     place-force $w .
1176
1177     top-down-window $w
1178
1179     frame $w.top.lines -relief ridge -border 2
1180     frame $w.top.use -relief ridge -border 2
1181     frame $w.top.relation -relief ridge -border 2
1182     frame $w.top.position -relief ridge -border 2
1183     frame $w.top.structure -relief ridge -border 2
1184     frame $w.top.truncation -relief ridge -border 2
1185     frame $w.top.completeness -relief ridge -border 2
1186
1187     # Index Lines
1188
1189     index-lines $w.top.lines 0 $queryButtonsTmp $queryInfoTmp activate-e-index
1190
1191     pack $w.top.lines -side left -pady 6 -padx 6 -fill y
1192
1193     # Use Attributes
1194     pack $w.top.use -side left -pady 6 -padx 6 -fill y
1195
1196     label $w.top.use.label -text "Use"
1197     listbox $w.top.use.list -geometry 20x10 \
1198             -yscrollcommand "$w.top.use.scroll set"
1199     scrollbar $w.top.use.scroll -orient vertical -border 1
1200     pack $w.top.use.label -side top -fill x \
1201             -padx 2 -pady 2
1202     pack $w.top.use.list -side left -fill both -expand yes \
1203             -padx 2 -pady 2
1204     pack $w.top.use.scroll -side right -fill y \
1205             -padx 2 -pady 2
1206     $w.top.use.scroll config -command "$w.top.use.list yview"
1207
1208     foreach u {{Personal name} {Corporate name}} {
1209         $w.top.use.list insert end $u
1210     }
1211     # Relation Attributes
1212     pack $w.top.relation -pady 6 -padx 6 -side top
1213
1214     label $w.top.relation.label -text "Relation" -width 18
1215     
1216     listbutton $w.top.relation.b 0\
1217             {{None} {Less than} {Greater than or equal} \
1218             {Equal} {Greater than or equal} {Greater than} {Not equal} \
1219             {Phonetic} \
1220             {Stem} {Relevance} {AlwaysMatches}}
1221     
1222     pack $w.top.relation.label $w.top.relation.b -fill x 
1223
1224     # Position Attributes
1225     pack $w.top.position -pady 6 -padx 6 -side top
1226
1227     label $w.top.position.label -text "Position" -width 18
1228
1229     listbutton $w.top.position.b 0 {{None} {First in field} {First in subfield}
1230     {Any position in field}}
1231     
1232     pack $w.top.position.label $w.top.position.b -fill x
1233
1234     # Structure Attributes
1235
1236     pack $w.top.structure -pady 6 -padx 6 -side top
1237     
1238     label $w.top.structure.label -text "Structure" -width 18
1239
1240     listbutton $w.top.structure.b 0 {{None} {Phrase} {Word} {Key} {Year}
1241     {Date (norm)} {Word list} {Date (un-norm)} {Name (norm)} {Date (un-norm)}
1242     {Structure} {urx} {free-form} {doc-text} {local-number} {string}
1243     {numeric string}}
1244
1245     pack $w.top.structure.label $w.top.structure.b -fill x
1246
1247     # Truncation Attributes
1248
1249     pack $w.top.truncation -pady 6 -padx 6 -side top
1250     
1251     label $w.top.truncation.label -text "Truncation" -width 18
1252
1253     listbutton $w.top.truncation.b 0 {{Auto} {Right} {Left} {Left and right} \
1254             {No truncation} {Process #} {Re-1} {Re-2}}
1255     pack $w.top.truncation.label $w.top.truncation.b -fill x
1256
1257     # Completeness Attributes
1258
1259     pack $w.top.completeness -pady 6 -padx 6 -side top
1260     
1261     label $w.top.completeness.label -text "Truncation" -width 18
1262
1263     listbutton $w.top.completeness.b 0 {{None} {Incomplete subfield} \
1264             {Complete subfield} {Complete field}}
1265     pack $w.top.completeness.label $w.top.completeness.b -fill x
1266
1267     # Ok-cancel
1268     top-down-ok-cancelx $w [list \
1269             {Ok} [list query-setup-action $queryNo] \
1270             {Add index} [list query-add-index $queryNo] \
1271             {Add line} [list query-add-line $queryNo] \
1272             {Delete line} [list query-del-line $queryNo]] 0
1273 }
1274
1275 proc index-clear {} {
1276     global queryButtonsFind
1277
1278     set i 0
1279     foreach b $queryButtonsFind {
1280         .lines.$i.e delete 0 end
1281         incr i
1282     }
1283 }
1284     
1285 proc index-query {} {
1286     global queryButtonsFind
1287     global queryInfoFind
1288
1289     set i 0
1290     set qs {}
1291
1292     foreach b $queryButtonsFind {
1293         set term [string trim [.lines.$i.e get]]
1294         if {$term != ""} {
1295             set attr [lindex [lindex $queryInfoFind [lindex $b 1]] 1]
1296             if {$qs != ""} {
1297                 set qs "${qs} and "
1298             }
1299             if {$attr != ""} {
1300                 set qs "${qs}${attr}="
1301             }
1302             set qs "${qs}(${term})"
1303         }
1304         incr i
1305     }
1306     puts "qs=  $qs"
1307     return $qs
1308 }
1309
1310 proc index-lines {w realOp buttonInfo queryInfo handle} {
1311     set i 0
1312     foreach b $buttonInfo {
1313         if {! [winfo exists $w.$i]} {
1314             frame $w.$i -background white -border 1
1315         }
1316         listbuttonx $w.$i.l [lindex $b 1] $queryInfo $handle $i
1317
1318         if {! [winfo exists $w.$i.e]} {
1319             if {$realOp} {
1320                 entry $w.$i.e -width 32 -relief sunken
1321             }
1322             pack $w.$i.l -side left
1323             if {$realOp} {
1324                 pack $w.$i.e -side left -fill x -expand yes
1325             }
1326             pack $w.$i -side top -fill x -padx 2 -pady 2
1327         }
1328         if {$realOp} {
1329             bind $w.$i.e <Left> [list left-cursor $w.$i.e]
1330             bind $w.$i.e <Right> [list right-cursor $w.$i.e]
1331             bind $w.$i.e <Return> search-request
1332         }
1333         incr i
1334     }
1335     set j $i
1336     while {[winfo exists $w.$j]} {
1337         destroy $w.$j
1338         incr j
1339     }
1340     if {! $realOp} {
1341         return
1342     }
1343     set j 0
1344     incr i -1
1345     while {$j < $i} {
1346         set k [expr $j+1]
1347         bind $w.$j.e <Tab> "focus $w.$k.e \n
1348         $w.$k configure -background red \n
1349         $w.$j configure -background white"
1350         set j $k
1351     }
1352     if {$i >= 0} {
1353         bind $w.$i.e <Tab> "focus $w.0.e \n
1354         $w.0 configure -background red \n
1355         $w.$i configure -background white"
1356         focus $w.0.e
1357         $w.0 configure -background red
1358     }
1359 }
1360
1361 proc search-fields {w buttondefs} {
1362     set i 0
1363     foreach buttondef $buttondefs {
1364         frame $w.$i -background white
1365         
1366         listbutton $w.$i.l 0 $buttondef
1367         entry $w.$i.e -width 32 -relief sunken
1368         
1369         pack $w.$i.l -side left
1370         pack $w.$i.e -side left -fill x -expand yes
1371
1372         pack $w.$i -side top -fill x -padx 2 -pady 2
1373
1374         bind $w.$i.e <Left> [list left-cursor $w.$i.e]
1375         bind $w.$i.e <Right> [list right-cursor $w.$i.e]
1376
1377         incr i
1378     }
1379     set j 0
1380     incr i -1
1381     while {$j < $i} {
1382         set k [expr $j+1]
1383         bind $w.$j.e <Tab> "focus $w.$k.e \n
1384         $w.$k configure -background red \n
1385         $w.$j configure -background white"
1386         set j $k
1387     }
1388     bind $w.$i.e <Tab> "focus $w.0.e \n
1389         $w.0 configure -background red \n
1390         $w.$i configure -background white"
1391     focus $w.0.e
1392     $w.0 configure -background red
1393 }
1394
1395 frame .top  -border 1 -relief raised
1396 frame .lines  -border 1 -relief raised
1397 frame .mid  -border 1 -relief raised
1398 frame .data -border 1 -relief raised
1399 frame .bot  -border 1 -relief raised
1400 pack .top .lines .mid -side top -fill x
1401 pack .data -side top -fill both -expand yes
1402 pack .bot -fill x
1403
1404 menubutton .top.file -text "File" -underline 0 -menu .top.file.m
1405 menu .top.file.m
1406 .top.file.m add command -label "Save settings" -command {save-settings}
1407 .top.file.m add command -label "Load Set" -command {load-set}
1408 .top.file.m add separator
1409 .top.file.m add command -label "Exit" -command {exit-action}
1410
1411 menubutton .top.target -text "Target" -underline 0 -menu .top.target.m
1412 menu .top.target.m
1413 .top.target.m add cascade -label "Connect" -menu .top.target.m.clist
1414 .top.target.m add command -label "Disconnect" -command {close-target}
1415 #.top.target.m add command -label "Initialize" -command {init-request}
1416 .top.target.m add cascade -label "Setup" -menu .top.target.m.slist
1417 .top.target.m add command -label "Setup new" -command {define-target-dialog}
1418 .top.target.m add separator
1419 set-target-hotlist
1420
1421 .top.target.m disable 1
1422
1423 menu .top.target.m.clist
1424 menu .top.target.m.slist
1425 cascade-target-list
1426
1427 menubutton .top.search -text "Search" -underline 0 -menu .top.search.m
1428 menu .top.search.m
1429 .top.search.m add command -label "Database" -command {database-select}
1430 .top.search.m add cascade -label "Query type" -menu .top.search.m.querytype
1431 menu .top.search.m.querytype
1432 .top.search.m.querytype add radiobutton -label "RPN"
1433 .top.search.m.querytype add radiobutton -label "CCL"
1434 .top.search.m add cascade -label "Present" -menu .top.search.m.present
1435 menu .top.search.m.present
1436 .top.search.m.present add command -label "More" -command [list present-more 10]
1437 .top.search.m.present add command -label "All" -command [list present-more {}]
1438 .top.search configure -state disabled
1439
1440 menubutton .top.query -text "Query" -underline 0 -menu .top.query.m
1441 menu .top.query.m
1442 .top.query.m add cascade -label "Choose" -menu .top.query.m.clist
1443 .top.query.m add command -label "Define" -command {new-query-dialog}
1444 .top.query.m add cascade -label "Edit" -menu .top.query.m.slist
1445 menu .top.query.m.clist
1446 menu .top.query.m.slist
1447 cascade-query-list
1448
1449 menubutton .top.help -text "Help" -menu .top.help.m
1450 menu .top.help.m
1451
1452 .top.help.m add command -label "Help on help" -command {puts "Help on help"}
1453 .top.help.m add command -label "About" -command {puts "About"}
1454
1455 pack .top.file .top.target .top.query .top.search -side left
1456 pack .top.help -side right
1457
1458 index-lines .lines 1 $queryButtonsFind [lindex $queryInfo 0] activate-index
1459
1460 button .mid.search -width 6 -text {Search} -command search-request \
1461         -state disabled
1462 button .mid.scan -width 6 -text {Scan} -command scan-request \
1463         -state disabled
1464 button .mid.clear -width 6 -text {Clear} -command index-clear
1465 pack .mid.search .mid.scan .mid.clear -side left -padx 5 -pady 3
1466
1467 listbox .data.list -yscrollcommand {.data.scroll set} -font fixed
1468 scrollbar .data.scroll -orient vertical -border 1
1469 pack .data.list -side left -fill both -expand yes
1470 pack .data.scroll -side right -fill y
1471 .data.scroll config -command {.data.list yview}
1472
1473 message .bot.target -text "None" -aspect 1000 -relief sunken -border 1
1474 label .bot.status -text "Not connected" -width 12 -relief \
1475         sunken -anchor w -border 1
1476 label .bot.set -textvariable setNo -width 5 -relief \
1477         sunken -anchor w -border 1
1478 label .bot.message -text "" -width 14 -relief \
1479         sunken -anchor w -border 1
1480 pack .bot.target .bot.status .bot.set .bot.message -anchor nw \
1481         -side left -padx 2 -pady 2
1482
1483 bind .data.list <Double-Button-1> {set indx [.data.list nearest %y]
1484 show-full-marc $indx}
1485
1486 ir z39