source: trunk/gui/scripts/htmlviewer.tcl @ 785

Last change on this file since 785 was 785, checked in by mmc, 17 years ago

Added support for a <note> on the output side of an <image> object.
This was needed for app-nsopticsjr. We should experiement a little
more with this, design it properly, and apply the same idea to all
output items.

File size: 14.4 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: htmlviewer - easy way to display HTML text
3#
4#  This is a thin layer on top of the Tkhtml widget.  It makes it
5#  easier to load HTML text and render it with proper behaviors.
6# ======================================================================
7#  AUTHOR:  Michael McLennan, Purdue University
8#  Copyright (c) 2004-2007  Purdue Research Foundation
9#
10#  See the file "license.terms" for information on usage and
11#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12# ======================================================================
13package require Itk
14package require Tkhtml
15package require Img
16
17option add *HTMLviewer.width 3i widgetDefault
18option add *HTMLviewer.height 0 widgetDefault
19option add *HTMLviewer.maxLines 4 widgetDefault
20
21itcl::class Rappture::HTMLviewer {
22    inherit itk::Widget
23
24    itk_option define -foreground foreground Foreground black
25    itk_option define -background background Background white
26    itk_option define -linknormalcolor linkNormalColor LinkColor blue
27    itk_option define -linkactivecolor linkActiveColor LinkColor black
28    itk_option define -linkactivebackground linkActiveBackground LinkActiveBackground #cccccc
29    itk_option define -xscrollcommand xScrollCommand XScrollCommand ""
30    itk_option define -yscrollcommand yScrollCommand YScrollCommand ""
31    itk_option define -width width Width 0
32    itk_option define -height height Height 0
33    itk_option define -maxlines maxLines MaxLines 0
34
35    constructor {args} { # defined below }
36
37    public method load {htmlText args}
38    public method add {htmlText args}
39    public method followLink {url}
40
41    # this widget works with scrollbars
42    public method xview {args} { eval $itk_component(html) xview $args }
43    public method yview {args} { eval $itk_component(html) yview $args }
44
45    protected method _config {args}
46    private variable _dispatcher "" ;# dispatcher for !events
47
48    protected method _fixHeight {args}
49    private variable _linesize ""   ;# height of default font size
50
51    protected method _mouse {option x y}
52    private variable _hover ""   ;# list of nodes that mouse is hovering over
53
54    protected method _getImage {file}
55    private common _icons        ;# maps file name => image handle
56    private variable _dirlist "" ;# list of directories where HTML came from
57}
58
59itk::usual HTMLviewer {
60    keep -cursor
61    keep -foreground -background
62}
63
64# ----------------------------------------------------------------------
65# CONSTRUCTOR
66# ----------------------------------------------------------------------
67itcl::body Rappture::HTMLviewer::constructor {args} {
68    Rappture::dispatcher _dispatcher
69    $_dispatcher register !config
70    $_dispatcher dispatch $this !config [itcl::code $this _config]
71    $_dispatcher register !fixHeight
72    $_dispatcher dispatch $this !fixHeight [itcl::code $this _fixHeight]
73
74    itk_component add html {
75        html $itk_interior.html -imagecmd [itcl::code $this _getImage]
76    } {
77        # no real options to work with for this widget
78    }
79    pack $itk_component(html) -expand yes -fill both
80
81    bind $itk_component(html) <Motion> \
82        [itcl::code $this _mouse over %x %y]
83    bind $itk_component(html) <ButtonPress-1> \
84        [itcl::code $this _mouse over %x %y]
85    bind $itk_component(html) <ButtonRelease-1> \
86        [itcl::code $this _mouse release %x %y]
87
88    # measure the default font height
89    $itk_component(html) reset
90    $itk_component(html) parse -final "Testing"
91    set node [$itk_component(html) node]
92    foreach {x0 y0 x1 y1} [$itk_component(html) bbox $node] break
93    set _linesize [expr {$y1-$y0}]
94    $itk_component(html) reset
95
96    eval itk_initialize $args
97}
98
99# ----------------------------------------------------------------------
100# USAGE: load <htmlText> ?-in <fileName>?
101#
102# Clients use this to clear the contents and load a new string of
103# <htmlText>.  If the text is empty, this has the effect of clearing
104# the widget.
105# ----------------------------------------------------------------------
106itcl::body Rappture::HTMLviewer::load {htmlText args} {
107    Rappture::getopts args params {
108        value -in ""
109    }
110    if {[llength $args] > 0} {
111        error "wrong # args: should be \"load text ?-in name?\""
112    }
113
114    $itk_component(html) reset
115    set _hover ""
116    set _dirlist ""
117
118    $itk_component(html) parse $htmlText
119
120    if {"" != $params(-in) && [file exists $params(-in)]} {
121        if {[file isdirectory $params(-in)]} {
122            lappend _dirlist $params(-in)
123        } else {
124            lappend _dirlist [file dirname $params(-in)]
125        }
126    }
127    $_dispatcher event -now !config
128}
129
130# ----------------------------------------------------------------------
131# USAGE: add <htmlText> ?-in <fileName>?
132#
133# Clients use this to add the <htmlText> to the bottom of the contents
134# being displayed in the widget.
135# ----------------------------------------------------------------------
136itcl::body Rappture::HTMLviewer::add {htmlText args} {
137    Rappture::getopts args params {
138        value -in ""
139    }
140    if {[llength $args] > 0} {
141        error "wrong # args: should be \"add text ?-in name?\""
142    }
143
144    $itk_component(html) parse $htmlText
145
146    if {"" != $params(-in) && [file exists $params(-in)]} {
147        if {[file isdirectory $params(-in)]} {
148            lappend _dirlist $params(-in)
149        } else {
150            lappend _dirlist [file dirname $params(-in)]
151        }
152    }
153    $_dispatcher event -now !config
154}
155
156# ----------------------------------------------------------------------
157# USAGE: followLink <url>
158#
159# Invoked automatically whenever the user clicks on a link within
160# an HTML page.  Tries to follow the <url> by invoking "exportfile"
161# to pop up further information.  If the <url> starts with http://
162# or https://, then it is used directly.  Otherwise, it is treated
163# as a relative file path and resolved with respect to the -in
164# options passed into load/add.
165# ----------------------------------------------------------------------
166itcl::body Rappture::HTMLviewer::followLink {url} {
167    if {[regexp -nocase {^https?://} $url]} {
168        foreach prog {clientaction /apps/xvnc/bin/clientaction ""} {
169            if {"" != [auto_execok $prog]} {
170                break
171            }
172        }
173        if {"" != $prog} {
174            exec $prog url $url &
175        } else {
176            bell
177        }
178        return
179    }
180
181    # must be a file -- use exportfile
182    set url [string trimleft $url /]
183    set path ""
184    foreach dir $_dirlist {
185        if {[file readable [file join $dir $url]]} {
186            set path [file join $dir $url]
187            break
188        }
189    }
190
191    foreach prog {exportfile /apps/bin/exportfile ""} {
192        if {"" != [auto_execok $prog]} {
193            break
194        }
195    }
196    if {"" != $path && "" != $prog} {
197        exec $prog --format html $path &
198    } else {
199        bell
200    }
201}
202
203# ----------------------------------------------------------------------
204# USAGE: _mouse over <x> <y>
205# USAGE: _mouse release <x> <y>
206#
207# Invoked automatically as the mouse pointer moves around or otherwise
208# interacts with this widget.  When the mouse is over a link, the link
209# becomes annotated with the "hover" style, so it can be highlighted.
210# Clicking and releasing on the same link invokes the action associated
211# with the link.
212# ----------------------------------------------------------------------
213itcl::body Rappture::HTMLviewer::_mouse {option x y} {
214    switch -- $option {
215        over {
216            # get a list of nodes with tags we care about
217            set nlist ""
218            foreach node [$itk_component(html) node $x $y] {
219                while {"" != $node} {
220                    if {[$node tag] == "a"} {
221                        lappend nlist $node
222                        break
223                    }
224                    set node [$node parent]
225                }
226            }
227
228            # over something new? then tag it with "hover"
229            if {$nlist != $_hover} {
230                foreach node $_hover {
231                    catch {$node dynamic clear hover}
232                }
233                set _hover $nlist
234                foreach node $_hover {
235                    catch {$node dynamic set hover}
236                }
237            }
238        }
239        release {
240            set prev $_hover
241            _mouse over $x $y
242
243            # mouse release on same node as mouse click? then follow link
244            if {$prev == $_hover} {
245                foreach node $_hover {
246                    if {[$node tag] == "a"} {
247                        followLink [$node attr -default {} href]
248                    }
249                }
250            }
251        }
252        default {
253            error "bad option \"$option\": should be over or release"
254        }
255    }
256}
257
258# ----------------------------------------------------------------------
259# USAGE: _config <arg> <arg>...
260#
261# Invoked automatically whenever the style-related configuration
262# options change for this widget.  Changes the main style sheet to
263# configure the widget.
264# ----------------------------------------------------------------------
265itcl::body Rappture::HTMLviewer::_config {args} {
266    component html style -id "author" "
267      body {
268        background: $itk_option(-background);
269        color: $itk_option(-foreground);
270        font: 10px helvetica,arial;
271      }
272      a {
273        color: $itk_option(-linknormalcolor);
274        text-decoration: underline;
275      }
276      a:hover {
277        color: $itk_option(-linkactivecolor);
278        background: $itk_option(-linkactivebackground);
279      }
280    "
281}
282
283# ----------------------------------------------------------------------
284# USAGE: _fixHeight <arg> <arg>...
285#
286# Invoked automatically whenever the height-related configuration
287# options change for this widget.  If -height is set to 0, then this
288# routine figures out a good height for the widget based on -maxlines.
289# ----------------------------------------------------------------------
290itcl::body Rappture::HTMLviewer::_fixHeight {args} {
291    set ht [winfo pixels $itk_component(html) $itk_option(-height)]
292    if {$ht <= 0} {
293        # figure out a good size automatically
294        set realht [winfo pixels $itk_component(html) 1i]
295        set node [$itk_component(html) node]
296        if {"" != $node} {
297            set bbox [$itk_component(html) bbox $node]
298            set realht [expr {[lindex $bbox 3]-[lindex $bbox 1]}]
299        }
300        if {$itk_option(-maxlines) > 0} {
301            set ht [expr {$itk_option(-maxlines)*$_linesize}]
302            if {$realht < $ht} {
303                set ht $realht
304            }
305        } else {
306            set ht $realht
307        }
308    }
309    $itk_component(html) configure -height $ht
310}
311
312# ----------------------------------------------------------------------
313# USAGE: _getImage <fileName>
314#
315# Used internally to convert a <fileName> to its corresponding image
316# handle.  If the <fileName> is relative, then it is loaded with
317# respect to the paths given by the -in option for the load/add
318# methods.  Returns an image handle for the image within the file,
319# or the broken image icon if anything goes wrong.
320# ----------------------------------------------------------------------
321itcl::body Rappture::HTMLviewer::_getImage {fileName} {
322    if {[info exists _icons($fileName)]} {
323        return $_icons($fileName)
324    }
325
326    set searchlist $fileName
327    if {[file pathtype $fileName] != "absolute"} {
328        foreach dir $_dirlist {
329            lappend searchlist [file join $dir $fileName]
330        }
331    }
332
333    foreach name $searchlist {
334        if {[catch {image create photo -file $name} imh] == 0} {
335            set _icons($fileName) $imh
336            return $imh
337        }
338    }
339    return [Rappture::icon exclaim]
340}
341
342# ----------------------------------------------------------------------
343# OPTION: -background
344# ----------------------------------------------------------------------
345itcl::configbody Rappture::HTMLviewer::background {
346    $_dispatcher event -idle !config
347}
348
349# ----------------------------------------------------------------------
350# OPTION: -foreground
351# ----------------------------------------------------------------------
352itcl::configbody Rappture::HTMLviewer::foreground {
353    $_dispatcher event -idle !config
354}
355
356# ----------------------------------------------------------------------
357# OPTION: -linknormalcolor
358# ----------------------------------------------------------------------
359itcl::configbody Rappture::HTMLviewer::linknormalcolor {
360    $_dispatcher event -idle !config
361}
362
363# ----------------------------------------------------------------------
364# OPTION: -linkactivecolor
365# ----------------------------------------------------------------------
366itcl::configbody Rappture::HTMLviewer::linkactivecolor {
367    $_dispatcher event -idle !config
368}
369
370# ----------------------------------------------------------------------
371# OPTION: -linkactivebackground
372# ----------------------------------------------------------------------
373itcl::configbody Rappture::HTMLviewer::linkactivebackground {
374    $_dispatcher event -idle !config
375}
376
377# ----------------------------------------------------------------------
378# OPTION: -xscrollcommand
379# ----------------------------------------------------------------------
380itcl::configbody Rappture::HTMLviewer::xscrollcommand {
381    $itk_component(html) configure -xscrollcommand $itk_option(-xscrollcommand)
382}
383
384# ----------------------------------------------------------------------
385# OPTION: -yscrollcommand
386# ----------------------------------------------------------------------
387itcl::configbody Rappture::HTMLviewer::yscrollcommand {
388    $itk_component(html) configure -yscrollcommand $itk_option(-yscrollcommand)
389}
390
391# ----------------------------------------------------------------------
392# OPTION: -width
393# ----------------------------------------------------------------------
394itcl::configbody Rappture::HTMLviewer::width {
395    $itk_component(html) configure -width $itk_option(-width)
396}
397
398# ----------------------------------------------------------------------
399# OPTION: -maxlines
400# Sets the maximum number of lines for widgets with "-height 0"
401# ----------------------------------------------------------------------
402itcl::configbody Rappture::HTMLviewer::maxlines {
403    $_dispatcher event -idle !fixHeight
404}
405
406# ----------------------------------------------------------------------
407# OPTION: -height
408# Set to absolute size ("3i") or 0 for default size based on -maxlines.
409# ----------------------------------------------------------------------
410itcl::configbody Rappture::HTMLviewer::height {
411    $_dispatcher event -idle !fixHeight
412}
Note: See TracBrowser for help on using the repository browser.