I had someone ask me about plugging in WithStyle to BottomFeeder. Previously, I had been using Twoflower, a more basic html display component. There are some differences in how you use WS; I decided that it was worth a post.
With Twoflower, I had a widget - i.e., the component had been wired into the GUI builder. That made interfacing with it simple - I just grabbed the widgte, slapped it on a canvas, and went with it. It even exposed an API at the widget level that allowed me to capture the 'browse' event (useful in Bf since users can either browse in Bf or spawn an external browser). WithStyle wasn't done that way; there's a low level View, and the example browser and it's superclasses. Now, it's easy enough to re-use a full window interface - there's the subcanvas widget in the toolkit for that. What I decided to do is the following:
- I subclassed WsThinClientWindow. This happens to be the superclass of a couple of WS examples, so I decided to start there
- I added a bunch of protocol that was specific to the Bf/WS integration
- I had to register for (and handle) a few trigger events
Let's start from the beginning. Here's the definition of my class:
Smalltalk.RSS defineClass: #BrowserPane
instanceVariableNames: 'textIncrement textDecrement currentSize currentStyleSheet '
The new instance variables have to do with resizing text and managing the current stylesheet - that's new in this release, since Twoflower didn't support CSS. I also had to redefine the UI, since I wanted a plain browser pane - no menus, toolbars, etc. That required knowing a little bit about the innards of WS - anyone who wants to work with this stuff is going to have to take a look at class WsRenderWidget. Here's the way the painter definition tool looks for it:
The next thing to do was to provide an API useful for managing the use of the html pane by BottomFeeder. I implemented these methods to handle placing text into the pane:
htmlString: string uri: aURI
self browserWidget userStylesheet: self currentStyleSheet.
ifTrue: [self browserWidget documentSource: string]
ifFalse: [self browserWidget documentSource: string uri: aURI asURI].
Those methods manage the use of the current style sheet and a re-adjusting of the text size as appropriate (i.e., based on user settings). The two other things that had to be dealt with are handling trigger events. WS triggers events on things like:
- Mouse is over an href
- An href has been clicked (i.e., a browse event)
I had to catch both - the first so that I could display the url being floated over, the second so that I could manage browsing (as currently set by the user - internal or external). So I registered for the following events:
self htmlModel browserWidget browserView when: #navigateTo: send: #openURL: to: self.
self htmlModel browserWidget browserView class when: #scrolledToEnd send: #goToNextNew to: self.
self htmlModel browserWidget browserView when: #mouseMovedTo: send: #mouseMovedTo: to: self.
The middle one is something I added; Bf will go to the next new item when you page past the end of an item you are looking at. The other two end up being handled in the main UI - in the navigate one, I look at settings, and either browse internally or pop up a browser. In the mouse moved event, I grab the href that the mouse is over and display it in the status bar.
There are a bunch of other convenience methods (copying text, etc) - but that's the major stuff right there. As it happens, WS is pretty easy to work with - and it's fairly easy to customize the browsing as per your needs. One thing that's interesting is the way I handle browse requests (since there are so many different kinds - here's my code for that:
| fLink |
(self isPagingUrl: link)
ifTrue: [^self handleNewspaperPaging: link].
(self isCommentUrl: link)
ifTrue: [^self handleCommentUrl: link].
(self looksLikeAMailTag: link)
ifTrue: [^self spawnMailFor: link].
fLink := link isString
ifTrue: [self scrubLink: link]
ifFalse: [self linkFromURLObject: link].
self settings alwaysBrowseInline
ifTrue: [Cursor wait showWhile: [self loadBrowserViewOn: fLink]]
ifFalse: [Cursor wait showWhile: [BrowserLaunchService current openBrowserOn: fLink]]
Now, some of that is very specific to BottomFeeder. Paging links are pseudo-links I use for newspaper views that are too large to put on one page. Comment links are similar, something that is also added (as appropriate) for newspaper view. The Mail tag handling is there to handle mailTo hrefs - on Windows, I pop the "default" mail tool, on other platforms I send up a small mail tool. The #isString check has been there since a time when I had some API issue in my own code; it does no harm, so I've just left it there. Finally, there's the "browse internally, or browse externally" check. The interesting aspect is the fact that you can easily intercept the browse event and do whatever you need to do.
I hope that's a useful primer on using WS - if you are interested, visit the WS home page and register for their developer program - you'll then be able to download their daily builds.