Scid  4.7.0
htext.tcl
Go to the documentation of this file.
1 ###################
2 # htext.tcl: Online help/hypertext display module for Scid
3 #
4 # The htext module implements html-like display in a text widget.
5 # It is used in Scid for the help and crosstable windows, and for
6 # the game information area.
7 
8 namespace eval ::htext {}
9 
10 set helpWin(Stack) {}
11 set helpWin(yStack) {}
12 set helpWin(Indent) 0
13 
14 # help_PushStack and help_PopStack:
15 # Implements the stack of help windows for the "Back" button.
16 #
17 proc help_PushStack {name {heading ""}} {
18  global helpWin
19  lappend helpWin(Stack) $name
20  if {[llength $helpWin(Stack)] > 10} {
21  set helpWin(Stack) [lrange $helpWin(Stack) 1 end]
22  }
23  if {[winfo exists .helpWin]} {
24  set helpWin(yStack) [linsert $helpWin(yStack) 0 \
25  [lindex [.helpWin.text yview] 0]]
26  if {[llength $helpWin(yStack)] > 10} {
27  set helpWin(yStack) [lrange $helpWin(yStack) 0 9]
28  }
29  }
30 }
31 
32 set ::htext::headingColor "\#990000"
33 array set ::htext:updates {}
34 
35 proc help_PopStack {} {
36  global helpWin helpText
37  set len [llength $helpWin(Stack)]
38  if {$len < 1} { return}
39  incr len -2
40  set name [lindex $helpWin(Stack) $len]
41  set helpWin(Stack) [lrange $helpWin(Stack) 0 $len]
42 
43  set ylen [llength $helpWin(yStack)]
44  set yview 0.0
45  if {$ylen >= 1} {
46  set yview [lindex $helpWin(yStack) 0]
47  set helpWin(yStack) [lrange $helpWin(yStack) 1 end]
48  }
49  updateHelpWindow $name
50  .helpWin.text yview moveto $yview
51 }
52 
53 # Given a window name (usually the focused widget)
54 # opens the help window trying to select a pertinent page
55 proc helpWindowPertinent {win} {
56  set availTitles [array names ::helpTitle]
57  regexp {[.]\w*} $win topWin
58 
59  # Look for a toplevel page (i.e. ".treeWin1" -> "tree")
60  if { [regexp {[.](\w*?)Win\d*$} $topWin -> topTitle] } {
61  set title [lsearch -inline -nocase $availTitles $topTitle]
62  if {$title != ""} {
63  return [helpWindow $title]
64  }
65  }
66 
67  # Default
68  return [helpWindow "Contents"]
69 }
70 
71 proc helpWindow {name {heading ""}} {
72  help_PushStack $name
73  updateHelpWindow $name $heading
74 }
75 
76 proc updateHelpWindow {name {heading ""}} {
77  global helpWin helpText helpTitle windowsOS language
78  set w .helpWin
79 
80  set slist [split $name " "]
81  if {[llength $slist] > 1} {
82  set name [lindex $slist 0]
83  set heading [lindex $slist 1]
84  }
85 
86  if {[info exists helpText($language,$name)] && [info exists helpTitle($language,$name)]} {
87  set title $helpTitle($language,$name)
88  set helptext $helpText($language,$name)
89  } elseif {[info exists helpText($name)] && [info exists helpTitle($name)]} {
90  set title $helpTitle($name)
91  set helptext $helpText($name)
92  } else {
93  return
94  }
95 
96  if {![winfo exists $w]} {
97  toplevel $w
98  # wm geometry $w -10+0
100  setWinSize $w
101 
102  wm minsize $w 20 5
103  text $w.text -setgrid yes -wrap word -width $::winWidth($w) -height $::winHeight($w) -relief sunken -border 0 -yscroll "$w.scroll set"
104  ttk::scrollbar $w.scroll -command "$w.text yview"
105 
106  ttk::frame $w.b -relief raised -border 2
107  pack $w.b -side bottom -fill x
108  ttk::button $w.b.contents -textvar ::tr(Contents) -command { helpWindow Contents }
109  ttk::button $w.b.index -textvar ::tr(Index) -command { helpWindow Index }
110  ttk::button $w.b.back -textvar ::tr(Back) -command { help_PopStack }
111  ttk::button $w.b.close -textvar ::tr(Close) -command {
112  set ::helpWin(Stack) {}
113  set ::helpWin(yStack) {}
114  destroy .helpWin
115  }
116 
117  pack $w.b.contents $w.b.index $w.b.back -side left -padx 1 -pady 2
118  pack $w.b.close -side right -padx 5 -pady 2
119  pack $w.scroll -side right -fill y -padx 2 -pady 2
120  pack $w.text -fill both -expand 1 -padx 1
121 
122  $w.text configure -font font_Regular -foreground black -background white
123  ::htext::init $w.text
124  bind $w <Configure> "recordWinSize $w"
125  }
126 
127  $w.text configure -cursor top_left_arrow
128  $w.text configure -state normal
129  $w.text delete 0.0 end
130 
131  $w.b.index configure -state normal
132  if {$name == "Index"} { $w.b.index configure -state disabled}
133  $w.b.contents configure -state normal
134  if {$name == "Contents"} { $w.b.contents configure -state disabled}
135  $w.b.back configure -state disabled
136  if {[llength $helpWin(Stack)] >= 2} {
137  $w.b.back configure -state normal
138  }
139 
140  wm title $w "Scid Help: $title"
141  wm iconname $w "Scid help"
142 
143  $w.text delete 0.0 end
144  bind $w <Up> "$w.text yview scroll -1 units"
145  bind $w <Down> "$w.text yview scroll 1 units"
146  bind $w <Prior> "$w.text yview scroll -1 pages"
147  bind $w <Next> "$w.text yview scroll 1 pages"
148  bind $w <Key-Home> "$w.text yview moveto 0"
149  bind $w <Key-End> "$w.text yview moveto 0.99"
150  bind $w <Escape> "$w.b.close invoke"
151  bind $w <Key-b> "$w.b.back invoke"
152  bind $w <Left> "$w.b.back invoke"
153  bind $w <Key-i> "$w.b.index invoke"
154 
155  ::htext::display $w.text $helptext $heading 0
156  focus $w
157 }
158 
159 proc ::htext::updateRate {w rate} {
160  set ::htext::updates($w) $rate
161 }
162 
163 proc ::htext::init {w} {
164  set cyan "\#007000"
165  set maroon "\#990000"
166  set green "darkgreen"
167 
168  set ::htext::updates($w) 100
169  $w tag configure black -foreground black
170  $w tag configure white -foreground white
171  $w tag configure red -foreground red
172  $w tag configure blue -foreground blue
173  $w tag configure darkblue -foreground darkBlue
174  $w tag configure green -foreground $green
175  $w tag configure cyan -foreground $cyan
176  $w tag configure yellow -foreground yellow
177  $w tag configure maroon -foreground $maroon
178  $w tag configure gray -foreground gray20
179 
180  $w tag configure bgBlack -background black
181  $w tag configure bgWhite -background white
182  $w tag configure bgRed -background red
183  $w tag configure bgBlue -background blue
184  $w tag configure bgLightBlue -background lightBlue
185  $w tag configure bgGreen -background $green
186  $w tag configure bgCyan -background $cyan
187  $w tag configure bgYellow -background yellow
188 
189  $w tag configure tab -lmargin2 50
190  $w tag configure li -lmargin2 50
191  $w tag configure center -justify center
192 
193  if {[$w cget -font] == "font_Small"} {
194  $w tag configure b -font font_SmallBold
195  $w tag configure i -font font_SmallItalic
196  } else {
197  $w tag configure b -font font_Bold
198  $w tag configure i -font font_Italic
199  }
200  $w tag configure bi -font font_BoldItalic
201  $w tag configure tt -font font_Fixed
202  $w tag configure u -underline 1
203  $w tag configure h1 -font font_H1 -foreground $::htext::headingColor \
204  -justify center
205  $w tag configure h2 -font font_H2 -foreground $::htext::headingColor
206  $w tag configure h3 -font font_H3 -foreground $::htext::headingColor
207  $w tag configure h4 -font font_H4 -foreground $::htext::headingColor
208  $w tag configure h5 -font font_H5 -foreground $::htext::headingColor
209  $w tag configure footer -font font_Small -justify center
210 
211  $w tag configure term -font font_BoldItalic -foreground $::htext::headingColor
212  $w tag configure menu -font font_Bold -foreground $cyan
213 
214  # PGN-window-specific tags:
215  $w tag configure tag -foreground $::pgnColor(Header)
216  if { $::pgn::boldMainLine } {
217  $w tag configure nag -foreground $::pgnColor(Nag) -font font_Regular
218  $w tag configure var -foreground $::pgnColor(Var) -font font_Regular
219  } else {
220  $w tag configure nag -foreground $::pgnColor(Nag)
221  $w tag configure var -foreground $::pgnColor(Var)
222  ### TODO
223  ### $w tag configure var -foreground $::pgnColor(Var) -font font_Figurine_Var
224 
225  }
226  $w tag configure ip1 -lmargin1 25 -lmargin2 25
227  $w tag configure ip2 -lmargin1 50 -lmargin2 50
228  $w tag configure ip3 -lmargin1 75 -lmargin2 75
229  $w tag configure ip4 -lmargin1 100 -lmargin2 100
230 }
231 
232 proc ::htext::isStartTag {tagName} {
233  return [expr {![strIsPrefix "/" $tagName]}]
234 }
235 
236 proc ::htext::isEndTag {tagName} {
237  return [strIsPrefix "/" $tagName]
238 }
239 
240 proc ::htext::isLinkTag {tagName} {
241  return [strIsPrefix "a " $tagName]
242 }
243 
244 proc ::htext::extractLinkName {tagName} {
245  if {[::htext::isLinkTag $tagName]} {
246  return [lindex [split [string range $tagName 2 end] " "] 0]
247  }
248  return ""
249 }
250 
251 proc ::htext::extractSectionName {tagName} {
252  if {[::htext::isLinkTag $tagName]} {
253  return [lindex [split [string range $tagName 2 end] " "] 1]
254  }
255  return ""
256 }
257 
258 set ::htext::interrupt 0
259 
260 proc ::htext::display {w helptext {section ""} {fixed 1}} {
261  global helpWin
262  # set start [clock clicks -milli]
263  set helpWin(Indent) 0
264  set ::htext::interrupt 0
265  $w mark set insert 0.0
266  $w configure -state normal
267  set linkName ""
268 
269  set count 0
270  set str $helptext
271  if {$fixed} {
272  regsub -all "\n\n" $str "<p>" str
273  regsub -all "\n" $str " " str
274  } else {
275  regsub -all "\[ \n\]+" $str " " str
276  regsub -all ">\[ \n\]+" $str "> " str
277  regsub -all "\[ \n\]+<" $str " <" str
278  }
279  set tagType ""
280  set seePoint ""
281 
282  if {! [info exists ::htext::updates($w)]} {
283  set ::htext::updates($w) 100
284  }
285 
286  # Loop through the text finding the next formatting tag:
287 
288  while {1} {
289  set startPos [string first "<" $str]
290  if {$startPos < 0} { break}
291  set endPos [string first ">" $str]
292  if {$endPos < 1} { break}
293 
294  set tagName [string range $str [expr {$startPos + 1}] [expr {$endPos - 1}]]
295 
296  # Check if it is a starting tag (no "/" at the start):
297 
298  if {![strIsPrefix "/" $tagName]} {
299 
300  # Check if it is a link tag:
301  if {[strIsPrefix "a " $tagName]} {
302  set linkName [::htext::extractLinkName $tagName]
303  set sectionName [::htext::extractSectionName $tagName]
304  set linkTag "link ${linkName} ${sectionName}"
305  set tagName "a"
306  $w tag configure "$linkTag" -foreground blue -underline 1
307  $w tag bind "$linkTag" <ButtonRelease-1> \
308  "helpWindow $linkName $sectionName"
309  $w tag bind $linkTag <Any-Enter> \
310  "$w tag configure \"$linkTag\" -background yellow
311  $w configure -cursor hand2"
312  $w tag bind $linkTag <Any-Leave> \
313  "$w tag configure \"$linkTag\" -background {}
314  $w configure -cursor {}"
315  } elseif {[strIsPrefix "url " $tagName]} {
316  # Check if it is a URL tag:
317  set urlName [string range $tagName 4 end]
318  set urlTag "url $urlName"
319  set tagName "url"
320  $w tag configure "$urlTag" -foreground red -underline 1
321  $w tag bind "$urlTag" <ButtonRelease-1> "openURL {$urlName}"
322  $w tag bind $urlTag <Any-Enter> \
323  "$w tag configure \"$urlTag\" -background yellow
324  $w configure -cursor hand2"
325  $w tag bind $urlTag <Any-Leave> \
326  "$w tag configure \"$urlTag\" -background {}
327  $w configure -cursor {}"
328  } elseif {[strIsPrefix "run " $tagName]} {
329  # Check if it is a Tcl command tag:
330  set runName [string range $tagName 4 end]
331  set runTag "run $runName"
332  set tagName "run"
333  $w tag bind "$runTag" <ButtonRelease-1> "catch {$runName}"
334  $w tag bind $runTag <Any-Enter> \
335  "$w tag configure \"$runTag\" -foreground yellow
336  $w tag configure \"$runTag\" -background darkBlue
337  $w configure -cursor hand2"
338  $w tag bind $runTag <Any-Leave> \
339  "$w tag configure \"$runTag\" -foreground {}
340  $w tag configure \"$runTag\" -background {}
341  $w configure -cursor {}"
342  } elseif {[strIsPrefix "go " $tagName]} {
343  # Check if it is a goto tag:
344  set goName [string range $tagName 3 end]
345  set goTag "go $goName"
346  set tagName "go"
347  $w tag bind "$goTag" <ButtonRelease-1> \
348  "catch {$w see \[lindex \[$w tag nextrange $goName 1.0\] 0\]}"
349  $w tag bind $goTag <Any-Enter> \
350  "$w tag configure \"$goTag\" -foreground yellow
351  $w tag configure \"$goTag\" -background maroon
352  $w configure -cursor hand2"
353  $w tag bind $goTag <Any-Leave> \
354  "$w tag configure \"$goTag\" -foreground {}
355  $w tag configure \"$goTag\" -background {}
356  $w configure -cursor {}"
357  } elseif {[strIsPrefix "pi " $tagName]} {
358  # Check if it is a player info tag:
359  set playerTag $tagName
360  set playerName [string range $playerTag 3 end]
361  set tagName "pi"
362  $w tag configure "$playerTag" -foreground darkBlue
363  $w tag bind "$playerTag" <ButtonRelease-1> "::pinfo::playerInfo \"$playerName\""
364  $w tag bind $playerTag <Any-Enter> \
365  "$w tag configure \"$playerTag\" -foreground yellow
366  $w tag configure \"$playerTag\" -background darkBlue
367  $w configure -cursor hand2"
368  $w tag bind $playerTag <Any-Leave> \
369  "$w tag configure \"$playerTag\" -foreground darkBlue
370  $w tag configure \"$playerTag\" -background {}
371  $w configure -cursor {}"
372  } elseif {[strIsPrefix "g_" $tagName]} {
373  # Check if it is a game-load tag:
374  set gameTag $tagName
375  set tagName "g"
376  set gnum [string range $gameTag 2 end]
377  set glCommand "::game::LoadMenu $w [sc_base current] $gnum %X %Y"
378  $w tag bind $gameTag <ButtonPress-1> $glCommand
379  $w tag bind $gameTag <ButtonPress-$::MB3> \
380  "::gbrowser::new [sc_base current] $gnum"
381  $w tag bind $gameTag <Any-Enter> \
382  "$w tag configure $gameTag -foreground yellow
383  $w tag configure $gameTag -background darkBlue
384  $w configure -cursor hand2"
385  $w tag bind $gameTag <Any-Leave> \
386  "$w tag configure $gameTag -foreground {}
387  $w tag configure $gameTag -background {}
388  $w configure -cursor {}"
389  } elseif {[strIsPrefix "m_" $tagName]} {
390  # Check if it is a move tag:
391  set moveTag $tagName
392  set tagName "m"
393  ### TODO
394  ### Does not work for variations as the var-Tag appears before
395  ### the <m_ tags, therefore this overwrites font sizes
396  ### $w tag configure $moveTag -font font_Figurine_ML
397  $w tag bind $moveTag <ButtonRelease-1> "sc_move pgn [string range $moveTag 2 end]; updateBoard"
398  # Bind middle button to popup a PGN board:
399  $w tag bind $moveTag <ButtonPress-$::MB2> "::pgn::ShowBoard .pgnWin.text $moveTag %X %Y"
400  $w tag bind $moveTag <ButtonRelease-$::MB2> "::pgn::HideBoard"
401  # invoking contextual menu in PGN window
402  $w tag bind $moveTag <ButtonPress-$::MB3> "sc_move pgn [string range $moveTag 2 end]; updateBoard"
403  $w tag bind $moveTag <Any-Enter> "$w tag configure $moveTag -underline 1
404  $w configure -cursor hand2"
405  $w tag bind $moveTag <Any-Leave> "$w tag configure $moveTag -underline 0
406  $w configure -cursor {}"
407  } elseif {[strIsPrefix "c_" $tagName]} {
408  # Check if it is a comment tag:
409  set commentTag $tagName
410  set tagName "c"
411  if { $::pgn::boldMainLine } {
412  $w tag configure $commentTag -foreground $::pgnColor(Comment) -font font_Regular
413  } else {
414  $w tag configure $commentTag -foreground $::pgnColor(Comment)
415  }
416  $w tag bind $commentTag <ButtonRelease-1> "sc_move pgn [string range $commentTag 2 end]; updateBoard; ::makeCommentWin"
417  $w tag bind $commentTag <Any-Enter> "$w tag configure $commentTag -underline 1
418  $w configure -cursor hand2"
419  $w tag bind $commentTag <Any-Leave> "$w tag configure $commentTag -underline 0
420  $w configure -cursor {}"
421  }
422 
423  if {$tagName == "h1"} {$w insert end "\n"}
424 
425  }
426 
427  # Now insert the text up to the formatting tag:
428  $w insert end [string range $str 0 [expr {$startPos - 1}]]
429 
430  # Check if it is a name tag matching the section we want:
431  if {$section != "" && [strIsPrefix "name " $tagName]} {
432  set sect [string range $tagName 5 end]
433  if {$section == $sect} { set seePoint [$w index insert]}
434  }
435 
436  if {[string index $tagName 0] == "/"} {
437  # Get rid of initial "/" character:
438  set tagName [string range $tagName 1 end]
439  switch -- $tagName {
440  h1 - h2 - h3 - h4 - h5 {$w insert end "\n"}
441  }
442  if {$tagName == "p"} {$w insert end "\n"}
443  #if {$tagName == "h1"} {$w insert end "\n"}
444  if {$tagName == "menu"} {$w insert end "\]"}
445  if {$tagName == "ul"} {
446  incr helpWin(Indent) -4
447  $w insert end "\n"
448  }
449  if {[info exists startIndex($tagName)]} {
450  switch -- $tagName {
451  a {$w tag add $linkTag $startIndex($tagName) [$w index insert]}
452  g {$w tag add $gameTag $startIndex($tagName) [$w index insert]}
453  c {$w tag add $commentTag $startIndex($tagName) [$w index insert]}
454  m {$w tag add $moveTag $startIndex($tagName) [$w index insert]}
455  pi {$w tag add $playerTag $startIndex($tagName) [$w index insert]}
456  url {$w tag add $urlTag $startIndex($tagName) [$w index insert]}
457  run {$w tag add $runTag $startIndex($tagName) [$w index insert]}
458  go {$w tag add $goTag $startIndex($tagName) [$w index insert]}
459  default {$w tag add $tagName $startIndex($tagName) [$w index insert]}
460  }
461  unset startIndex($tagName)
462  }
463  } else {
464  switch -- $tagName {
465  ul {incr helpWin(Indent) 4}
466  li {
467  $w insert end "\n"
468  for {set space 0} {$space < $helpWin(Indent)} {incr space} {
469  $w insert end " "
470  }
471  }
472  p {$w insert end "\n"}
473  br {$w insert end "\n"}
474  q {$w insert end "\""}
475  lt {$w insert end "<"}
476  gt {$w insert end ">"}
477  h2 - h3 - h4 - h5 {$w insert end "\n"}
478  }
479  #Set the start index for this type of tag:
480  set startIndex($tagName) [$w index insert]
481  if {$tagName == "menu"} {$w insert end "\["}
482  }
483 
484  # Check if it is an image or button tag:
485  if {[strIsPrefix "img " $tagName]} {
486  set imgName [string range $tagName 4 end]
487  set winName $w.$imgName
488  while {[winfo exists $winName]} { append winName a}
489  ttk::label $winName -image $imgName -relief flat -borderwidth 0 -background white
490  $w window create end -window $winName
491  }
492  if {[strIsPrefix "button " $tagName]} {
493  set idx [ string first "-command" $tagName]
494  set cmd ""
495  if {$idx == -1} {
496  set imgName [string range $tagName 7 end]
497  } else {
498  set imgName [string trim [string range $tagName 7 [expr $idx -1]]]
499  set cmd [ string range $tagName [expr $idx +9] end]
500  }
501  set winName $w.$imgName
502  while {[winfo exists $winName]} { append winName a}
503  ttk::button $winName -image $imgName -command $cmd
504  $w window create end -window $winName
505  }
506  if {[strIsPrefix "window " $tagName]} {
507  set winName [string range $tagName 7 end]
508  $w window create end -window $winName
509  }
510 
511  # Now eliminate the processed text from the string:
512  set str [string range $str [expr {$endPos + 1}] end]
513  incr count
514  if {$count == $::htext::updates($w)} { update idletasks; set count 1}
515  if {$::htext::interrupt} {
516  $w configure -state disabled
517  return
518  }
519  }
520 
521  # Now add any remaining text:
522  if {! $::htext::interrupt} { $w insert end $str}
523 
524  if {$seePoint != ""} { $w yview $seePoint}
525  $w configure -state disabled
526  # set elapsed [expr {[clock clicks -milli] - $start}]
527 }
528 
529 
530 # openURL:
531 # Sends a command to the user's web browser to view a webpage given
532 # its URL.
533 #
534 proc openURL {url} {
535  global windowsOS
536  busyCursor .
537  if {$windowsOS} {
538  # On Windows, use the "start" command:
539  regsub -all " " $url "%20" url
540  if {[string match $::tcl_platform(os) "Windows NT"]} {
541  catch {exec $::env(COMSPEC) /c start $url &}
542  } else {
543  catch {exec start $url &}
544  }
545  } elseif {$::macOS} {
546  # On Mac OS X use the "open" command:
547  catch {exec open $url &}
548  } else {
549  # First, check if xdg-open works:
550  if {! [catch {exec xdg-open $url &}] } {
551  #lauch default browser seems ok, nothing more to do
552  } elseif {[file executable [auto_execok firefox]]} {
553  # Mozilla seems to be available:
554  # First, try -remote mode:
555  if {[catch {exec /bin/sh -c "$::auto_execs(firefox) -remote 'openURL($url)'"}]} {
556  # Now try a new Mozilla process:
557  catch {exec /bin/sh -c "$::auto_execs(firefox) '$url'" &}
558  }
559  } elseif {[file executable [auto_execok iceweasel]]} {
560  # First, try -remote mode:
561  if {[catch {exec /bin/sh -c "$::auto_execs(iceweasel) -remote 'openURL($url)'"}]} {
562  # Now try a new Mozilla process:
563  catch {exec /bin/sh -c "$::auto_execs(iceweasel) '$url'" &}
564  }
565  } elseif {[file executable [auto_execok mozilla]]} {
566  # First, try -remote mode:
567  if {[catch {exec /bin/sh -c "$::auto_execs(mozilla) -remote 'openURL($url)'"}]} {
568  # Now try a new Mozilla process:
569  catch {exec /bin/sh -c "$::auto_execs(mozilla) '$url'" &}
570  }
571  } elseif {[file executable [auto_execok www-browser]]} {
572  # Now try a new Mozilla process:
573  catch {exec /bin/sh -c "$::auto_execs(www-browser) '$url'" &}
574  } elseif {[file executable [auto_execok netscape]]} {
575  # OK, no Mozilla (poor user) so try Netscape (yuck):
576  # First, try -remote mode to avoid starting a new netscape process:
577  if {[catch {exec /bin/sh -c "$::auto_execs(netscape) -raise -remote 'openURL($url)'"}]} {
578  # Now just try starting a new netscape process:
579  catch {exec /bin/sh -c "$::auto_execs(netscape) '$url'" &}
580  }
581  } else {
582  foreach executable {iexplorer opera lynx w3m links epiphan galeon
583  konqueror mosaic amaya browsex elinks} {
584  set executable [auto_execok $executable]
585  if [string length $executable] {
586  # Is there any need to give options to these browsers? how?
587  set command [list $executable $url &]
588  catch {exec /bin/sh -c "$executable '$url'" &}
589  break
590  }
591  }
592  }
593  }
594  unbusyCursor .
595 }