Request
, Response
and 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
loopThroughLowF
:Just asloopThroughLowF :: SP (Either TRequest TResponse) (Either TRequest TResponse) -> F i o -> F i o
loopThroughRightF
is often used by application
programmers to encapsulate and modify the behaviour of existing
fudgets, loopThroughLowF
is used in filters located in
fudlogue
and 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 fudlogue
, which
means that all fudgets in the program benefit from the resource
sharing.
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 Cla
was
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 gc
to 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 FreeGC
gc
.
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
type 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
Allocate
constructor carries allocation
data that the cache filter uses as search key in the resource
table. Similarly, the Free
constructor 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.
The function 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
cacheFilter
is 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 = ...
table
is 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
gcRequestType
for a number of resources, and the corresponding
cache filters, using the general cacheFilter
. All these
filters are combined into allCacheFilter
: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 allcacheFilter
would 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 KeyPressMask
,
EnterWindowMask
, and 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.
ffMask
is 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 -> BoolFigure 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
a 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
die.