#----------------------------------------------------------------------- # TITLE: # markupviewer.tcl # # AUTHOR: # inspired in Will Duquette # # DESCRIPTION: # A viewer for browsing Notebook pages. Simplified version of # pageviewer.tcl # # LICENSE: # #----------------------------------------------------------------------- package require snit package require renderpane package require autoscroll 1.1 snit::widget markupviewer { # A command to use for outputting messages. The message will be # appended. option -messagecommand {} #------------------------------------------------------------------- # Other options # A command that's called when the displayed page changes. The # command gets one argument, the page name. option -pagecommand {} # A command that's called when we try to display a page that # doesn't exist. option -unknowncommand {} # The notebook database option -db -default {} -readonly 1 # control if the title is rendered on the renderpane option -showtitle -default 1 #------------------------------------------------------------------- # Components variable db # The renderpane is the component that actually displays the # rendered page. variable rp #-------------------------------------------------------------------- # Other variables # The history list is a list of {PageName PagePos} pairs. variable history {} # The histPos is the index of the currently displayed page in the # history list. It's initially -1 because there's nothing in the # list. variable histPos -1 # A list of recently displayed pages. Each page appears only once, # regardless of how often (and where) it appears in the history list. # There will be ten pages listed, maximum; no more than one of them # are guaranteed to still exist. variable recentPages {} #-------------------------------------------------------------------- # Constructor constructor {args} { # As yet we have no option delegation, so go ahead and get the # args $self configurelist $args if { $options(-db) eq "" } { error "Invalid database option: '$options(-db)'" } set db $options(-db) # Create the renderpane. install rp using renderpane $win.rp \ -yscrollcommand "$win.scroll set" \ -querycommand [mymethod pageexists] \ -titlecommand [mymethod TitleHandler] \ -linkcommand [mymethod LinkHandler] \ -buttoncommand [mymethod ButtonHandler] \ -macroerrorcommand [mymethod MacroErrorHandler] \ -messagecommand $options(-messagecommand) scrollbar $win.scroll \ -command "$win.rp yview" # grid them in. # josp: use grid and autoscroll if { 0 } { pack $win.scroll -side right -fill y -expand false pack $rp -side top -fill both -expand true } grid $rp -row 0 -column 0 -sticky "snew" grid $win.scroll -row 0 -column 1 -sticky "ns" grid rowconfigure $win 0 -weight 1 grid columnconfigure $win 0 -weight 1 ::autoscroll::autoscroll $win.scroll #--------------------------------------------------------------- # User Interactions # Add the pageviewer window itself to the renderpane's bindtags, # so that keys bound to the pageviewer will take effect when the # renderpane has focus set tags [bindtags $rp] lappend tags $win bindtags $rp $tags # Go to the previous page when is pressed. bind $rp [list $self backpage] # User Menu. # josp: no user menu considered if { 0 } { menu $rp.user -tearoff no -postcommand [mymethod PostUserMenu] $rp.user add command -label "User Menu" $rp.user add separator $rp.user add command -label "Dummy" bind $rp [list tk_popup $rp.user %X %Y 2] bind $rp <3> [list tk_popup $rp.user %X %Y 2] # Recent Pages Menu. menu $rp.recent -tearoff no -postcommand [mymethod PostRecent] $rp.recent add command -label "Recent Pages" $rp.recent add separator $rp.recent add command -label "Dummy" bind $rp [list tk_popup $rp.recent %X %Y 2] } #--------------------------------------------------------------- # Prepare to receive preferences events; unregister on # destroy. prefs register $selfns [mymethod UsePreferences] } destructor { catch {prefs unregister $selfns} } # josp: delegate methods in order to use it as text delegate method tag to rp delegate method index to rp delegate method search to rp delegate method compare to rp delegate method mark to rp delegate method see to rp delegate method edit to rp delegate method get to rp delegate option -autoseparators to rp delegate option -font to rp delegate method UsePreferences to rp # josp: delegate UsePreferences on renderpane if { 0 } { method UsePreferences {} { # Update the display when the preferences change. $self showpage } } #------------------------------------------------------------------- # Public Methods delegate method searchfor to rp delegate method pagetime to db delegate method pagename to db as name method pageexists { name } { if { [ $db exists $name ] } { return 1 } else { return [ regexp "(http|https)://" $name ] } } # Returns the expanded text of the page, which must exist. method pageexpand {name} { ::trycatch::try { set result [$db expand [$db get $name]] } return $result } # Returns the name of the currently displayed page, or "" if none. # Note that during a deletion or page renaming the named page # is not guaranteed to exist. method current {} { return [lindex $history $histPos 0] } # Tells the pageviewer to take the keyboard focus. # If -friendly, we take the focus only if our toplevel already # has it (e.g., we take the focus back from the search box). method focus {{option {}}} { set focusWin [focus] if {$focusWin eq ""} { return } if {$option ne "-friendly" || [winfo toplevel $win] eq [winfo toplevel $focusWin]} { focus $rp } } # Shows the named page. The -unknowncommand is called if the # page doesn't exist. method showpage {{name ""}} { # FIRST, remember the position for the page we're showing now. if {$histPos >= 0} { lset history $histPos 1 [$self GetPos] } # NEXT, default to the current page. if {$name eq ""} { set name [$self current] if {$name eq ""} { error "no current page." } } # NEXT, if the page doesn't exist, call the -unknowncommand. if {![$self pageexists $name]} { if {$options(-unknowncommand) ne {}} { set command $options(-unknowncommand) lappend command $name uplevel \#0 $command } else { return -code error -errorcode USER "No such page: '$name'" } return } # NEXT, save the new page in the history list. When we show # a new page, any pages forward of our position go away. set history [lrange $history 0 $histPos] lappend history [list [$self pagename $name] 0] incr histPos # NEXT, display it. $self DisplayPage } # Steps back one page, if possible method backpage {} { # FIRST, remember the position for the page we're showing now. if {$histPos >= 0} { lset history $histPos 1 [$self GetPos] } # NEXT, Look back through the history list for a page that exists, # and is also different than the current page. set current [lindex $history $histPos 0] set pos [expr {$histPos - 1}] while {$pos >= 0} { set candidate [lindex $history $pos 0] if {[$self pageexists $candidate]} { if {![string equal -nocase $candidate $current]} { break } } incr pos -1 } if {$pos < 0} { bell return } set histPos $pos $self DisplayPage } # Steps forward one page, if possible method forwardpage {} { # FIRST, remember the position for the page we're showing now. if {$histPos >= 0} { lset history $histPos 1 [$self GetPos] } # NEXT, Look forward through the history list for a page that exists, # and is also different than the current page. set current [lindex $history $histPos 0] set pos [expr {$histPos + 1}] set len [llength $history] while {$pos < $len} { set candidate [lindex $history $pos 0] if {[$self pageexists $candidate]} { if {![string equal -nocase $candidate $current]} { break } } incr pos } if {$pos >= $len} { bell return } set histPos $pos $self DisplayPage } # Shows the page from the beginning of the Recent Pages list, if it exists. method cyclerecent {} { foreach id $recentPages { if {[$self pageexists $id]} { $self showpage $id break } } } #------------------------------------------------------------------- # Private Methods # Display the current page method DisplayPage { } { # FIRST, Get the current page specs set name [$self pagename [lindex $history $histPos 0]] set ypos [lindex $history $histPos 1] # NEXT, add the name to the list of recent pages $self SaveRecentPage $name # NEXT, Expand the requested page and display it. set pagetext [$self pageexpand $name] if { $options(-showtitle) } { $rp render $name $pagetext } else { $rp render "" $pagetext } # josp: don't known what is it for if { 0 } { $rp searchfor [$self searchtext] } # NEXT, give the renderpane the focus so that we don't # need to click in it. #$self focus -friendly $self setpos $ypos # NEXT, notify those concerned. if {$options(-pagecommand) ne {}} { set command $options(-pagecommand) lappend command $name uplevel \#0 $command } } # Save the specified page to the recent pages list, removing # any previous entry with the same name. Names are stored # in lower case. No more than ten names are stored. method SaveRecentPage {name} { set name [string tolower $name] set ndx [lsearch -exact $recentPages $name] if {$ndx != -1} { set recentPages [lreplace $recentPages $ndx $ndx] } lappend recentPages $name # Cut down back to ten, if need be if {[llength $recentPages] == 11} { set recentPages [lreplace $recentPages 0 0] } } # Get the y-position of the text at the top of the window. method GetPos {} { lindex [$rp yview] 0 } # Set the y-position of the text at the top of the window. method setpos {ypos} { $rp yview moveto $ypos } # Called when the user clicks on the title text. method TitleHandler {title} { $self showsearch $title } # Called when the user clicks on a link; shows the linked page. method LinkHandler {linkName} { if { [ regexp "(http|https)://" $linkName ] } { LaunchBrowser $linkName } else { $self showpage $linkName } } # Called when the user clicks on a magic button; calls the button's script method ButtonHandler {script} { $self evaluser $script } # Finds the macro error text and displays the error message. method MacroErrorHandler {macro} { $self evaluser $macro # TBD: If macros are disabled, then clicking on this won't # display an error. We need to handle this somehow. } # procs proc LaunchBrowser { url } { global tcl_platform # It *is* generally a mistake to switch on $tcl_platform(os), particularly # in comparison to $tcl_platform(platform). For now, let's just regard it # as a stylistic variation subject to debate. switch $tcl_platform(os) { Darwin { set command [list open $url] } HP-UX - Linux - SunOS { foreach executable {firefox mozilla netscape iexplorer opera lynx w3m links epiphany galeon konqueror mosaic amaya browsex elinks} { set executable [auto_execok $executable] if [string length $executable] { # Do you want to mess with -remote? How about other browsers? # Frank Darby says the '&' leads on his Ubuntu to attempts to # open 'www.&.com', and so on. set command [list $executable $url &] set command [list $executable $url] break } } } {Windows 95} - {Windows NT} { set command "[auto_execok start] {} [list $url]" } } if [info exists command] { # Replace {*}$command by eval "$command" if you want < tcl 8.5 compatibility ([RA]) # Added the '&' to launch the browser as background process. [Duoas] if [catch {eval exec $command &} err] { tk_messageBox -icon error -message "error '$err' with '$command'" } } else { tk_messageBox -icon error -message \ "Please tell CL that ($tcl_platform(os), $tcl_platform(platform)) is not yet ready for browsing." } } } package provide markupviewer 1.0