Responseand others, opens up the possibility to write what we will call filters, which alter specific aspects of a fudget's input/output behaviour. Filters have type
F a b -> F a b, which indicates that they do not tamper with the high-level messages, they only analyse and modify the low-level messages.
A number of problems can be solved by using filters--for example, swapping the meaning of the left and the right mouse buttons, or swapping the black and white colors in GUI fudgets.
In the following sections, we will see two examples of filters from the Fudget library which alter the behaviour of complex fudgets:
loopThroughRightF, and is called
Just asloopThroughLowF :: SP (Either TRequest TResponse) (Either TRequest TResponse) -> F i o -> F i o
loopThroughRightFis often used by application programmers to encapsulate and modify the behaviour of existing fudgets,
loopThroughLowFis used in filters located in
shellF, and can thus modify certain aspects of all fudgets in an application. The controlling stream processor, which is the first argument to
loopThroughLowF, receives as input the stream of all tagged requests that are output from the encapsulated fudgets, and also all tagged responses directed to the same fudgets. It can analyse and manipulate these messages in any manner before outputting them, after which they will continue their way to the I/O system (in the case of the requests), or the fudgets (in the case of the responses). The simplest conceivable filter is
which simply passes through all requests and responses undisturbed, and thus acts as the identity filter.loopThroughLowF idSP
It is the role of the cache filter to support this resource
sharing between fudgets. It is part of
means that all fudgets in the program benefit from the resource
The effect of the cache filter is most notable on slow connections
with high round trip delays, such as dialup connections. To
demonstrate this, we have run
Cla, one of the demonstration
programs from the Fudget distribution, over a dial-up connection
using PPP and secure shell (
ssh, compression rate 9). The
modem speed was 14400 bits
per second, and the round trip delay 250 milliseconds on average. To
eliminate errors due to different compression rates, the program
was started repeatedly, until the startup time converged. Without
the cache filter, the minimum startup time for
clocked to 133 seconds. When enabling the cache, the startup time
decreased to 9.6 seconds, a speedup factor of over 13. (As a
comparison, we also ran
Cla on this slow connection
without compression: the startup times were 274 seconds
with no cache, and 31 seconds with cache. Compression is a good thing!)
The heap usage is also better when the cache is enabled, the peak decreases from 990 to 470 kilobytes.
These figures should not come as a surprise since the GUI in
Cla consists of one display, and 28 push buttons which can share
the same resources.
Using the cache filter means that there is an overhead in the program. Except for drawing commands, the filter will analyse each request that is output. As a result, the calculator startup time is about 5% longer when the X server runs on the same computer as the calculator. In this case, the connection is fast and has negligible round trip delay.
CreateGC d tgc al, where d is the drawable in which the GC will be used, tgc is a template GC, and al is a list of attributes that the new GC should have. The request is turned into a call to the Xlib function
XCreateGC, which returns a reference to a new GC. This is sent back as the response
GCCreated gcto the requesting fudget, which brings it to use. When the GC is not needed anymore, the fudget can explicitly deallocate it by outputting the X command
The idea of using a cache is of course that if a second fudget wants to create a GC with the same template and attributes, we could reuse the first GC, if it is not yet deallocated. So a GC cache maintains table from template and attributes to graphics contexts and reference counts.
It turns out that most resource (de)allocation follows the same
pattern as our scenario, if we abstract from the specific request
and response constructors. This abstraction is captured in the
RequestType, which expresses whether a request is an
allocation, a deallocation, or something else:
The argument to thedata RequestType a r = Allocate a | Free r | Other
Allocateconstructor carries allocation data that the cache filter uses as search key in the resource table. Similarly, the
Freeconstructor carries the resource that should be freed. In the case of graphics contexts, the allocation data are pairs of template GCs and attribute lists, and the resources are graphics contexts.
gcRequestType determines the type of request
for graphics contexts:
The general cache filtergcRequestType :: Request -> RequestType (GCId,GCAttributeList) GCId gcRequestType r = case r of CreateGC d tgc al -> Allocate (tgc,al) FreeGC gc -> Free gc _ -> Other
cacheFilteris parameterised over the function that determines the request type:
The internal statecacheFilter :: (Eq a,Eq r) => (Request -> RequestType a r) -> F i o -> F i o cacheFilter rtf = loopThroughLowF (cache ) where cache table = ...
tableis a list of type
[(a, (r, Int))], where the elements are allocation data with associated resources and reference counts.
The definition of a cache for graphics contexts is now simple:
The Fudget library defines request type functions likegcCacheFilter :: F i o -> F i o gcCacheFilter = cacheFilter gcRequestType
gcRequestTypefor a number of resources, and the corresponding cache filters, using the general
cacheFilter. All these filters are combined into
This cache filter is wrapped around all fudget programs inallcacheFilter :: F a b -> F a b allcacheFilter = fontCacheFilter . fontStructCacheFilter . gcCacheFilter . colorCacheFilter . bitmapFileCacheFilter . fontCursorCacheFilter
fudlogue. One should fear that
allcacheFilterwould impose a considerable overhead, since all commands must be inspected in turn by each of the six filters. In practice, the overhead is not a big problem.
stringF) signals its interest in focus by configuring its event mask to include
LeaveWindowMask. This means that the fudget can receive keyboard input, and also events when the pointer enters or leaves the fudget (crossing events). The crossing events are used to give visual feedback about which fudget has the focus.
A potential problem with point-to-type controlled focus, is that the user must move a hand back and forth a lot between the keyboard and the pointing device (assuming that the pointer cannot be controlled from the keyboard), if she wants to fill in data in a form that consists of several input fields. It is also easy to touch the pointing device accidentally so that the pointer jumps a little, which could result in a focus change.
These problems vanish when using a click-to-type focus model. With click-to-type, the tight coupling between the pointer position and focus is removed. Instead, the user clicks in an input field to indicate that it should have the focus. The focus stays there until the user clicks in another input field. In addition, if the keyboard can be used for circulating focus between the input fields in a form, it can be filled in without using the pointing device.
A limited variant of this improved input model has been added to the Fudget library as a filter in the shell fudgets, leaving the various GUI fudgets unmodified. The limitation is that the model is only click-to-type as long as the pointer is inside the shell fudget. When the pointer leaves the shell fudget, focus goes to whatever application window is under it, unless the window manager uses click-to-type.
ffMaskis a subset:
A simplified implementation of a focus filter is shown in Figure 57.ffMask = [KeyPressMask, EnterWindowMask, LeaveWindowMask]
focusFilter :: F a b -> F a b focusFilter f = loopThroughLowF (focusSP ) (simpleGroupF [KeyPressMask] f) focusSP :: [Path] -> SP (Either TRequest TResponse) (Either TRequest TResponse) focusSP fpaths = getSP (either request response) where request (p,r) = case getEventMask r of Just mask | ffMask `issubset` mask -> putSP (Left (p,setEventMask (mask' r)) $ focusSP (p:fpaths) where mask' = [ButtonPressMask] `union` mask _ -> putSP (Left (p,r)) $ focusSP fpaths response (p,r) = if keyPressed r then (putSP (Right (head fpaths, r)) $ focusSP fpaths) else if leftButtonPressed 1 r && p `elem` fpaths then putSP (Right (p,r)) $ focusSP (aft++bef) else putSP (Right (p,r)) where (bef,aft) = break (==path) fpaths -- Auxiliary functions: simpleGroupF :: [EventMask] -> F a b -> F a b getEventMask :: Request -> Maybe [EventMask] setEventMask :: [EventMask] -> Request -> Request keyPressed :: Request -> Bool leftButtonPressed :: Request -> Bool
Figure 57. A focus filter.
The focus filters reside immediately inside
the shell fudgets. To get keyboard events, no matter the position
of the pointer (as long as it is inside the shell window), a
group fudget is created around the inner fudgets with a suitable
event mask. This is done with
simpleGroupF, which acts as
groupF without a kernel.
The filtering is done in
focusSP, whose argument
fpaths accumulates a list of paths to the focus fudgets. This is
done by looking for window configuration commands with matching
event masks. The event masks of the focus fudgets is modified to
mask', so that the windows of focus fudgets
will generate mouse button events.
The head of
fpaths is considered to own the focus, and
incoming key events are redirected to it. If the user clicks in
one of the focus fudgets,
fpaths is reorganised so that
the path of the clicked fudget comes first.
As noted, Figure 57 shows a simplified focus filter. The filter in the Fudget library is more developed; it also handles crossing events, and focus changes using the keyboard. More complex issues, like dynamic creation and destruction of fudgets, are also handled. Still, it ignores some complications, introduced by the migrating fudgets in Chapter 25.
It should also be noted that the X window model supports special focus change events which should rather be used when controlling focus. This fits better with window managers that implement click-to-type.
XChangeGC) must be avoided in the GUI fudgets.
The implementation of filters often involves that a piece of state
must be associated with each GUI fudget. This means that the state
of some GUI fudgets are spread out in the library, in some
sense. One piece resides in the fudget itself as local state, then
there is non-local state in the focus filter, and in the
fudlogue tables, which are used to route asynchronous events. If
fudget state is distributed like this, there is always a danger
that it becomes inconsistent, for example when fudgets move or