COM on! - a tiny web browser

Richard Suchenwirth 2002-09-18: I've learned and preached that Tcl/Tk can be used to glue software components together, but how strong this is I experienced only last night. Steve Offutt hinted me in the Tcl chatroom at optcl, an extension that allows exploring and using Windows COM modules, and mailed me a little 2 KB script that implements a web Browser (by embedding part of Internet Explorer via optcl), animated GIFs as well as directory browsing supported, and even Acrobat Reader gets embedded in turn if you point it to a PDF document.

WikiDbImage comon.jpg

I played with it at home, adjusted and simplified it somehow, and noticed that the whole browser, capable of navigating the WWW as well as your local file system, can fit in less than 1 KB (if you exempt these explanations, and assuming you're on Windows >= 95 and accept the GPL), while allowing you to style it at your heart's delight, just as we know it from Tcl. (Only printing is not supported, it is now ~ MPJ, but we know that from Tcl too ;-) So here goes my "lean and mean" version: }

wm title . {COM on!}
package require optcl

# local setting on my box at home
set home c:/html/index.htm

option add *Button.pady        0
option add *Button.borderWidth 1
option add *Button.relief      flat
bind Button <Enter> {+ %W config -relief raised}
bind Button <Leave> {+ %W config -relief flat}

set htm [optcl::new -window .htm Shell.Explorer.2]
.htm config -width 600 -height 400

frame  .fr
button .fr.h -text ^   -command {catch {$htm navigate [set url $home]}}
button .fr.o -text ... -command {$htm navigate [set url [tk_getOpenFile]]}
button .fr.b -text <   -command {catch {$htm goBack}}
button .fr.f -text >   -command {catch {$htm goForward}}
button .fr.p -text P   -command {
    catch {$htm ExecWB OLECMDID_PRINT OLECMDEXECOPT_PROMPTUSER}}
entry  .fr.e -textvar url -width 80
bind   .fr.e <Return> {$htm navigate $url}
set url $home
pack {*}[winfo children .fr] -side left -fill y

pack .fr  -fill x
pack .htm -fill both -expand 1
catch {$htm navigate $url}
bind . <Escape> {exec wish $argv0 &; exit} ;# useful in development

SO 2009-09-19: I appreciate Richard mentioning our conversation in the chatroom, and the little script I mailed him. I would be remiss, however, if I failed to point out that my little hack was based on a script provided by Michael Jacobson, on the optcl page. Also, I would love to see other examples of using optcl/tcom appear on the wiki. These are truly powerful tools for those of us who spend most of our programming time in the world of windows.


MPJ 2002-09-19: Note there is a little bug in Tk or optcl that does not allow the text box to grab the focus correctly. See solution for regaining focus from OpTcl hosted ActiveX control , comp.lang.tcl, 2002-03-01. I notice that your little app has this problem too. I mention it on the optcl page with this solution (using my other favorite dll ... ffidl). UPDATE I like the way GPS solved this on his WippleWobble - A Mini Web Browser (for Windows) page using forceFocus proc.

load ffidl05.dll
ffidl::callout dll_SetFocus {int} int [ffidl::symbol user32.dll SetFocus]
proc GrabFocus {args} {dll_SetFocus [winfo id .]}
bind . <Button> +GrabFocus

We can also add the use of event bindings from the ActiveX componet to this interface. Here is a little code from NewzPoint to display the MouseOver urls in a status bar at the bottom.

proc linkchanged {id page} {
    global link htm
    if {$page eq {} || [string equal -nocase done $page]} {
        set link [$htm : LocationURL]
    } else {
        set link $page
    } 
}
optcl::bind $htm StatusTextChange linkchanged
label .sta -relief sunken -borderwidth 2  -anchor w -textvariable link
pack .sta -fill x -expand 1

Another item that is a must-have for a browser is security level (icon). Here is how to implement it with optcl.

optcl::bind $html SetSecureLockIcon security
proc security {id secureid} {
    global securitylevel
    set securitylevel [lindex \
        {Unsecure Mixed Unknown 40Bits 56Bits Fortezza 128Bits} $secureid]
}
# now put securitylevel in a status bar

Finally, a moving icon is usually implemeted to show that the browser is navigating to a link and it stops when the link is displayed. This can be accomplished by binding to the BeforeNavigate2 and NavigateComplete2 events. Also you can always check on the status of a page by calling the Busy method (boolean return value).

set pagestatus [$htm : Busy] ;# 1 is busy, 0 is displayed

Martin Lemburg 2002-09-20:

I "clean" up the code, collected the suggestions and tips - here is the "new" code:

package require optcl;

proc forceFocus {win} {
    catch {focus -force $win} ;# MPJ change
}

proc linkchanged {id page} {
    global link htm;

    if {($page eq {}) || [string equal -nocase done $page]} {
        set link [$htm : LocationURL];
    } else {
        set link $page;
    }
    return
}

proc security {id secureid} {
    global securitylevel

    set securitylevel [lindex {
        Unsecure Mixed
        Unknown 40Bits
        56Bits Fortezza
        128Bits
    } $secureid]
    return
}

proc startLoading args {
    global stopLoading loadingTime
    set loadingTime 0.00
    set stopLoading 0
    after 250 incrLoadingTime
    return
}

proc incrLoadingTime {} {
    global stopLoading loadingTime
    set loadingTime [format {%.2lf} [expr {$loadingTime + 0.25}]]
    if {!$stopLoading} {
        after 250 incrLoadingTime
    }
    return
}

proc stopLoading {args} {
    global stopLoading
    set stopLoading 1
    return
}

set home http://www.google.com

wm title . {COM on!}

option add *Button.pady        0
option add *Button.borderWidth 1
option add *Button.relief      flat

bind Button <Enter> {+ %W config -relief raised}
bind Button <Leave> {+ %W config -relief flat}

frame  .fr
button .fr.h -text ^   -command {catch {$htm navigate [set url $home]}}
button .fr.o -text ... -command {$htm navigate [set url [tk_getOpenFile]]}
button .fr.b -text <   -command {catch {$htm goBack}}
button .fr.f -text >   -command {catch {$htm goForward}}
button .fr.p -text P   -command {
    catch {$htm ExecWB "OLECMDID_PRINT" "OLECMDEXECOPT_PROMPTUSER"}}
entry  .fr.e -textvar url -width 80
label  .fr.l -textvar loadingTime -width 7 -relief sunken -borderwidth 2
pack {*}[winfo children .fr] -side left -fill none -expand 0
pack .fr.e -side left  -fill x    -expand 1
pack .fr.l -side right -fill none -expand 0

set htm [optcl::new -window .htm Shell.Explorer.2];
.htm config -width 600 -height 400;

frame .sta
label .sta.info -relief sunken -borderwidth 2  -anchor w -textvariable link
label .sta.secu -relief sunken -borderwidth 2  -anchor w \
    -textvariable securitylevel -width 10
pack .sta.info -side left  -fill x    -expand 1
pack .sta.secu -side right -fill none -expand 0

set url $home

pack .fr  -side top    -fill x    -expand 0
pack .htm -side top    -fill both -expand 1
pack .sta -side bottom -fill x    -expand 0

bind .fr.e <Return> {$htm navigate $url}

# useful in development
bind . <Escape> {exec wish $argv0 &; exit}

# MPJ change
bind all <Enter> {forceFocus %W}
bind all <ButtonPress-1> {forceFocus %W}

optcl::bind $htm StatusTextChange  linkchanged
catch {optcl::bind $htm SetSecureLockIcon security}
optcl::bind $htm BeforeNavigate2   startLoading
optcl::bind $htm NavigateComplete2 stopLoading

catch {$htm navigate $url};

Michael Jacobson: I updated the above so it does not need ffidl anymore. Give credit to GPS for this trick. Also if someone wants to try this code out in a Starkit format then just download this file http://mywebpages.comcast.net/jakeforce/COMon!.kit (92kb) and run with with TclKit. (Note: that it includes the optcl extension so all you need are the Tclkit executable and the COMon! Starkit)

VL 2003-06-11: Trying to run the starkit on my w2k machine I get an error. I'll try to hunt it down when I have some time but if anyone know the problem help is appreciated?

event not found: SetSecureLockIcon

MPJ: There are different versions of the IE COM object depending on which version of windows you are using. Some do not have the SetSecureLockIcon event. Therefore I just put a catch around this line are repacked the starkit. Please download and try it again. VL: works like a charm!

VL 2003-06-11: Now if I could only add the local html-file I want to view as a commandline argument this would be perfect before I get my table-viewer stuff finished?

MPJ: Per you request I updated my starkit to take the 1st argument as the new home page. So you can do this...

tclkit COMon!.kit www.yahoo.com

And yahoo will be the new home page and start to load. Please download and try it again.

VL: Thanks!

Here is a Starkit version where I changed the homepage to be served locally using the code from the Comic Server page ... http://mywebpages.comcast.net/jakeforce/COMon!ics.kit (96kb). If you want to see the code just unwrap it "tclkit sdx.kit unwrap COMon!ics.kit".


Michael Jacobson 2002-12-13: I just added a print button to the examples after noticing RS' remarks that it was not supported (it is but you need to use the OLE interface to the WebBrowser component). Below is how you can also do a CUT, COPY and PASTE button too.

button .fr.pa -text PA -command {
    catch {$htm ExecWB OLECMDID_PASTE OLECMDEXECOPT_DODEFAULT}}
button .fr.co -text CO -command {
    catch {$htm ExecWB OLECMDID_COPY OLECMDEXECOPT_DODEFAULT}}
button .fr.cu -text CU -command {
    catch {$htm ExecWB OLECMDID_CUT OLECMDEXECOPT_DODEFAULT}}

In fact, the cut and paste are pretty interesting to have available all the time (MSIE allows it only for text fields).