## widget.pkg
#
# See bottom-of-file comments for
# extended widget-internals docs.
# Compiled by:
#
src/lib/x-kit/widget/xkit-widget.sublib### "Steve Jobs said two years ago
### that X is brain-damaged and
### it will be gone in two years.
### He was half right."
###
### -- Dennis Ritchie
stipulate
include package threadkit; # threadkit is from
src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg #
package xc = xclient; # xclient is from
src/lib/x-kit/xclient/xclient.pkg #
package g2d= geometry2d; # geometry2d is from
src/lib/std/2d/geometry2d.pkg #
package rw = root_window_old; # root_window_old is from
src/lib/x-kit/widget/old/basic/root-window-old.pkg package wa = widget_attribute_old; # widget_attribute_old is from
src/lib/x-kit/widget/old/lib/widget-attribute-old.pkgherein
package widget
: (weak) Widget # Widget is from
src/lib/x-kit/widget/old/basic/widget.api {
include package widget_base; # widget_base is from
src/lib/x-kit/widget/old/basic/widget-base.pkg include package root_window_old; # root_window_old is from
src/lib/x-kit/widget/old/basic/root-window-old.pkg include package widget_attributes; # widget_attributes is from
src/lib/x-kit/widget/old/basic/widget-attributes.pkg #
# These three are all specified by api Widget.
Arg = (wa::Name, wa::Value);
Realize_Widget
=
{ kidplug: xc::Kidplug,
window: xc::Window,
window_size: g2d::Size
}
->
Void;
# Soon we'll want to change all the Size values
# in this file to Box values, so as to track the
# full size+position info relative to parent for
# each window.
exception ALREADY_REALIZED;
Widget
=
WIDGET
{
root_window: Root_Window,
id: Int,
args: Void -> Window_Args,
#
size_preference_thunk_of: Void -> Widget_Size_Preference,
#
seen_first_redraw: Oneshot_Maildrop( Void ),
realized: Oneshot_Maildrop( Void ),
realize_widget: Realize_Widget,
#
# Support for window_of.
# This ise NULL until realization
# and non-NULL thereafter:
#
window: Ref( Null_Or(xc::Window) )
};
fun make_widget { root_window as { next_widget_id, ... }: Root_Window, args, size_preference_thunk_of, realize_widget }
=
WIDGET
{
root_window,
args,
#
realized => make_oneshot_maildrop (),
seen_first_redraw => make_oneshot_maildrop (),
id => next_widget_id (),
#
size_preference_thunk_of,
realize_widget,
#
window => REF NULL
};
fun root_window_of (WIDGET { root_window, ... } ) = root_window;
fun args_of (WIDGET { args, ... } ) = args ();
fun args_fn (WIDGET { args, ... } ) = args;
fun window_of (WIDGET { window => REF (THE window), ... } ) => window;
window_of (WIDGET { window => REF NULL, ... } ) => raise exception DIE "widget::window_of called before realization";
end;
# fun size_of (WIDGET { size => REF (THE size), ... } ) => size;
# size_of (WIDGET { size => REF NULL, ... } ) => raise exception DIE "widget::size_of called before realization";
# end;
fun size_preference_of (WIDGET { size_preference_thunk_of, ... } ) = size_preference_thunk_of ();
fun size_preference_thunk_of (WIDGET { size_preference_thunk_of, ... } ) = size_preference_thunk_of;
fun seen_first_redraw_oneshot_of (WIDGET { seen_first_redraw, ... } ) = seen_first_redraw;
fun realize_widget
#
(WIDGET { realize_widget, realized, window => window_ref, seen_first_redraw, ... } )
#
(arg as { kidplug, window, window_size } )
=
{ put_in_oneshot (realized, ())
except
_ = raise exception ALREADY_REALIZED;
# Cache window as support for window_of:
#
window_ref := THE window;
# Post seen_first_redraw oneshot as support for
#
# seen_first_redraw_oneshot_of
#
xc::note_''seen_first_expose''_oneshot window seen_first_redraw;
realize_widget arg;
};
fun same_widget
( WIDGET { id, root_window, ... },
WIDGET { id=>id', root_window=>root_window', ... }
)
=
id == id'
and
same_root (root_window, root_window');
fun preferred_size (WIDGET { size_preference_thunk_of, ... } )
=
{ my { col_preference, row_preference }
=
size_preference_thunk_of ();
{ wide => preferred_length col_preference,
high => preferred_length row_preference
};
};
fun okay_size (widget, size)
=
is_within_size_limits (size_preference_of widget, size);
# Given a widget, return a replacement widget
# in which the 'keyboard', 'mouse' or 'other'
# eventstream is replaced by an externally
# filtered version of the original one.
#
# To be effective, this needs to be done pre-realization.
#
# We return a pair:
#
# o The replacement widget.
#
# o A mailop yielding a (read-mailop, write-slot) pair
# to be used to perform the desired event filtering.
#
#
# Arguments:
#
# which_eventstream extracts from a kidplug either from_mouse', from_keyboard' or from_other'.
#
# replace_eventstream is one of replace_mouse, replace_keyboard, replace_other from
src/lib/x-kit/xclient/src/window/widget-cable-old.pkg # -- it does functional update of a kidplug, replacing one component.
#
# widget to filter.
#
fun filter_widget
(which_eventstream, replace_eventstream)
(WIDGET { root_window, realize_widget, size_preference_thunk_of, args, ... } )
=
{ realize_slot = make_mailslot ();
fun realize_widget' { window, kidplug, window_size }
=
{ event = which_eventstream kidplug;
eslot = make_mailslot ();
kidplug' = replace_eventstream (kidplug, take_from_mailslot' eslot);
put_in_mailslot (realize_slot, (event, eslot));
realize_widget { window, window_size, kidplug=>kidplug'};
};
( make_widget
{
root_window,
args,
size_preference_thunk_of,
realize_widget => realize_widget'
},
take_from_mailslot' realize_slot
);
};
filter_mouse = filter_widget (\\ (xc::KIDPLUG { from_mouse', ... } ) = from_mouse', xc::replace_mouse );
filter_keyboard = filter_widget (\\ (xc::KIDPLUG { from_keyboard', ... } ) = from_keyboard', xc::replace_keyboard);
filter_other = filter_widget (\\ (xc::KIDPLUG { from_other', ... } ) = from_other', xc::replace_other );
fun ignore_widget
(which_eventstream, replace_eventstream)
(WIDGET { root_window, realize_widget, size_preference_thunk_of, args, ... } )
=
{ fun realize_widget' { window, window_size, kidplug }
=
{ fun discard_all_input mailop ()
=
for (;;) {
#
block_until_mailop_fires mailop;
};
# Replace original kidplug eventstream with
# a null stream -- anyone who attempts to read
# it will block forever:
#
kidplug'
=
replace_eventstream
( kidplug,
get_from_oneshot' (make_oneshot_maildrop ())
);
make_thread "widget" (discard_all_input (which_eventstream kidplug));
realize_widget { window, window_size, kidplug=>kidplug'};
};
make_widget
{ root_window,
args,
size_preference_thunk_of,
realize_widget => realize_widget'
};
};
ignore_mouse = ignore_widget (\\ (xc::KIDPLUG { from_mouse', ... } ) = from_mouse', xc::replace_mouse );
ignore_keyboard = ignore_widget (\\ (xc::KIDPLUG { from_keyboard', ... } ) = from_keyboard', xc::replace_keyboard);
Relief == three_d::Relief;
Attribute_Spec = (wa::Name, wa::Type, wa::Value);
fun get_''gui_startup_complete''_oneshot_of
#
(WIDGET { root_window, ... })
=
xc::get_''gui_startup_complete''_oneshot_of_xsession (rw::xsession_of root_window);
}; # package widget
end;
# Prior reading:
#
# Before reading this section you will want to have
# read the bottom-of-file comments in:
#
#
src/lib/x-kit/widget/old/basic/widget.api#
#
# Widget Internals
# ================
#
# (The following comments are adapted from Chapter 5 of
# http://mythryl.org/pub/exene/1993-widgets.ps
# -- Gansner+Reppy's 1993 eXene widget manual.)
#
# Widget creation is usually simple:
#
# o Compute some parameters;
# o Allocate resources such as fonts, colors or pixmaps;
# o Possibly spawn a thread encapsulating mutual widget state.
#
# No X window is created at this point.
# The widget value returned by make_widget wraps
#
# o The relevant Root_Window;
# o a size_preference function;
# o a realize function.
#
#
#
# Realize functions
# -----------------
#
# In x-kit each parent widget controls the resources of its kid(s):
#
# o It allocates the child's X window;
# o It positions that window on the screen;
# o It sizes that window;
# o Ultimately, it deletes that window.
#
# When a child needs any of these actions performed
# it sends a request to its parent.
#
# When a parent invokes the realize function of a child
# it passes it three parameters:
#
# o The Kidplug end of the Widget_Cable from
# which the child receives mouse and keyboard
# input and through which it sends parent requests.
#
# o The child's window. (The canvas on which the child draws itself.)
#
# o The size of that window. (Supplied for convenience.)
#
# The parent will size the child's window taking into
# account its expressed size preference.
#
#
#
# The child widget's realize function must:
#
# o Configure itself per its assigned window size.
#
# o Spawn the threads it needs to draw itself and
# handle keyboard and mouse input appropriately.
#
# o For layout widgets, the realize function must
#
# * Lay out its children;
#
# * Allocate their X windows;
#
# * Arrange handling of its end
# of their Widget_Cable traffic.
#
# * Call their realize functions.
#
# * Map their windows.
# (A leaf widget should not un/map itself.)
#
#
# Handling Keyboard and Mouse Events
# ----------------------------------
#
# Keyboard input arrives via the Kidplug
#
# from_keyboard'
#
# mailop in the form of Keyboard_Mail messages.
# These specify whether the event was a keypress
# or key release, the X keysym of that key, and
# the state of the modifier keys, in particular
# the shift and control keys.
#
# For details see:
#
#
src/lib/x-kit/xclient/src/window/widget-cable-old.pkg#
# For translation of keysyms into ascii characters see:
#
#
src/lib/x-kit/xclient/src/window/keysym-to-ascii.api#
# Mouse input arrives via the Kidplug
#
# from_mouse'
#
# mailop in the form of Mouse_Mail messages.
# These signal mouse button press and release events,
# mouse motions and window enter/leave events.
#
# IMPORTANT: X-kit guarantees that when a widget receives
# a mouse buttonpress that it will receive all mouse
# events until all mouse buttons have been released.
# This corresponds to an "active grab" in X jargon.
#
# The first mouse button event a widget receives is always a
#
# MOUSE_FIRST_DOWN
#
# message, and the last is always
#
# MOUSE_LAST_UP
#
#
#
# Handling Non-keyboard, Non-mouse Events
# ---------------------------------------
#
# Messages not correspondingly directly to user input
# arrive via the Kidplug
#
# from_other'
#
# mailop in the form of Other_Mail messages:
#
# ETC_REDRAW:
# Signals an X Expose event, listing one or more
# rectangular parts of the widget window which need
# to be redrawn. It is always safe to redraw the
# entire window, but redrawing just the indicated
# parts may be quicker.
#
# ETC_RESIZE:
# Notifies widget that its window has been moved
# or resized, and gives the current size and location.
# Any required resizing has already been done by parent.
# Layout widgets will need to recompute their layout
# upon receiving this message and perhaps use
# move_window, resize_window and/or move_and_resize_window
# to reposition them: See
#
#
src/lib/x-kit/xclient/src/window/window-old.api#
# Layout widgets need not explicitly send ETC_RESIZE
# messages their children; such messages will be
# generated as needed by the above calls.
#
# It is not necessary (or good) to redraw upon
# receiving a ETC_RESIZE; separate ETC_REDRAW
# messages will be sent automatically as appropriate.
#
# ETC_OWN_DEATH:
# Our window has been destroyed. We must stop
# drawing on our window, but continue to read
# any mouse and keyboard events which might be
# still en route. (One approach is to attach
# simple null loops to the from_keyboard' and
# from_mouse' mailops.)
#
#
# ETC_CHILD_BIRTH:
# ETC_CHILD_DEATH:
# Used by layout widgets to coordinate mail routing.
#
#
#
#
# Asking Mommy For Help
# ---------------------
#
# A widget requests parental favors via the Kidplug.to_mom function:
#
# REQ_RESIZE:
# Asks parent to resize us. Parent will call
# our size_preference function to get our
# (presumably changed) size preference and
# (maybe) reposition/resize us accordingly.
#
# The parent is not obligated to respond to
# this request; the child should not assume
# anything has changed unless/until it receive
# an ETC_RESIZE message.
#
# REQ_DESTRUCTION:
# Child wishes its window destroyed. Parent
# will attempt this via window::destroy_window:
#
#
src/lib/x-kit/xclient/src/window/window-old.api#
# Child will get an ETC_OWN_DEATH message on completion.
#
#
# Layout and Wrapper Widgets
# --------------------------
#
# Layout widgets arrange multiple visible children.
#
# Wrapper widgets usually have just one visible child,
# performing some service such as sizing it or drawing
# a border around it.
#
# Layout and wrapper widgets must handle the Kidplug
# end of their own Widget_Cable just like any leaf
# widget, and must in addition handle the Momplug
# ends of the cable(s) leading to their kid(s).
#
# Tasks include:
#
# o Geometric layout of kids within own window.
# o Creating windows and cables for kids.
# o Calling child realize function(s).
# o Mapping child windows.
#
# By convention x-kit windows do not explicitly
# use the X server border or background properties,
# instead using wrapper widgets to implement this
# functionality. This is intended to yield cleaner
# and more consistent code better insulated from
# X idiosyncracies; for example widgets need
# not take border presence or thickness into account
# when computing their geometry.
#
# Widget authors are encouraged to use make_child_window()
# to create child windows, as it both automates
# busywork and implements expected conventions. See:
#
#
src/lib/x-kit/widget/old/basic/widget-base.api#
src/lib/x-kit/widget/old/basic/widget-base.pkg#
# Widget cables are created using
#
# widget_cable::make_widget_cable() :
#
# from
#
#
src/lib/x-kit/xclient/src/window/widget-cable-old.pkg#
# Wrapper and layout widgets have a Momplug for each
# child, which they must service promptly. ("Parents
# should be more responsible than their children.")
#
# REQ_RESIZE and REQ_DESTRUCTION messages should be
# handled as described above.
#
# Note that a child's REQ_RESIZE message may be handled
# locally using the layout widget's current window size,
# or the layout widget may in turn send a REQ_RESIZE of
# its own to its own parent.
#
# BEWARE the deadlock potential in two-way communications
# between parent and child! E.g deadlock can easily result
# if a layout widget recomputing layout calls a child's
# size preference function when the child, in response to
# user input, is sending a REQ_RESIZE to the parent.
#
# The x-kit convention is that it is the parent's responsibility
# to always be receptive to child requests. The
#
# wrap_queue
#
# function from
#
#
src/lib/x-kit/widget/old/basic/widget-base.api#
src/lib/x-kit/widget/old/basic/widget-base.pkg#
# may be used to wrap a queue around the Momplug.from_child'
# mailop, assuring that the child will not block even
# if the parent thread is occupied.
#
#
# Mail Delivery:
# ..............
#
# The final task of layout and wrapper widgets is routing
# X event mail that arrives via its Kidplug. Each Kidplug
# message may be destined either for the widget itself or
# for one of its children. The envelope on each incoming
# mail message contains the route to its destination.
#
# The input section of the xclient api defines various functions which
# may be used to assist in deciding whether a given message is for the
# widget itself or one of its children, and if so which child:
#
#
src/lib/x-kit/xclient/xclient.api#
# The Xevent_Mail_Router api defines functionality which can
# handle most typical mail-routing situations:
#
#
src/lib/x-kit/widget/old/basic/xevent-mail-router.api#
# At realization time a layout or wrapper widget can create
# a router using make_xevent_mail_router(), passing it the
# Kidplug that it received from its realize fn. The widget
# will also need to create a new Widget_Cable, passing the
# Momplug end to the make_xevent_mail_router() call; the
# widget will read its own input from the Kidplug end of
# that cable, while the new router sits between the
# widget and its original parent, intercepting and
# processing mail:
#
# ---------------------- ----------
# (parent) >===>
| xevent_mail_router |>=====>| widget |
#
|----------------------| | |----------
#
| | | |
#
| New Momplug | New Kidplug
# Original
|
# Kidplug New
# Widget_Cable
#
# In addition, for each child widget, the layout/wrapper
# widget will need to create another Widget_Cable, passing
# the Kidplug end to the child through its realize fn
# while registering the child's Momplug and window with
# the router via its add_child() call. (The child widgets
# may also be passed in via the make_xevent_mail_router()
# call.) The resulting topology winds up looking like:
#
# ---------------------- ----------
# (parent) >===>
| xevent_mail_router |>=====>| widget |
# ---------------------- ----------
# \/ \/ \/
#
|| || ||---- more Widget_Cables
#
|| || ||
# \/ \/ \/
# ----- ----- -----
#
|kid| |kid| |kid|
# ----- ----- -----
#
# To simplify life in the special case of wrapper widgets
# with a single child, the xevent_mail_router package
# provides the route_pair() function which does everything
# necessary in one call.
#
# With either of these routers, all child mail will be routed
# to them automatically without further work on behalf of the
# layout/wrapper widget itself.
#
# The widget_base::make_child_window() function specified by
#
#
src/lib/x-kit/widget/old/basic/widget-base.api#
# handles making child windows, returning a subwindow
# of the given window of the given size in the given
# (relative) position.
#
#
# Sub-widget insert/remove
# ........................
#
# Layout and wrapper widgets may permit post-realization
# dynamic insertion and removal of child widgets.
#
# Inserted children should be assumed to be unrealized.
# The layout widget should reposition its existing children
# in light of the new widget, allot needed resources
# (e.g., fonts, window), and then realize the new child.
#
# When a layout or wrapper widget removes a child it should
# destroy the child's window and remove it from the router.
#
#
#
# Wrapper Widget Support
# ......................
#
# The x-kit design is intended to encourage re-use of
# existing widgets by wrapping them in a modulating
# widget in preference to writing a new widget from
# scratch or adding complexity to an existing widget.
#
# To encourage the writing of wrapper widgets, the
# Widget api
#
#
src/lib/x-kit/widget/old/basic/widget.api#
# defines a number of support functions:
#
# filter_mouse:
# filter_keyboard:
# filter_other:
# Wrap a new widget around a given existing
# widget while giving access to the given
# mail stream.
#
# ignore_mouse:
# ignore_keyboard:
# Wrap a new widget around a given existing
# widget which intercepts and discards all
# messages on the given mail stream.
#
#
#
# Suggested Widget Programming Conventions
# ----------------------------------------
# From: http://mythryl.org/pub/exene/dusty-thesis.pdf
#
# Parents should be more responsible than children:
#
# o A parent must guarantee kids never block indefinitely
# when attempting to mail it -- preferably not block at
# all, or only momentarily.
#
# o Child methods must always terminate, preferably quickly.
#
# o Parents should queue messages sent to children or applications,
# to prevent parent threads from being blocked by slow kids:
#
# "Because all queued messages originated with the user, there
# is little risk of the message queues becoming especially long."
#
# o Parents should call child size_preference fns only prior
# to realization:
#
# * Parent should cache child size preference pre-realization.
# * Parent should update cached value only on REQ_RESIZE
# * REQ_RESIZE should contain child's (presumably changed) size preference.
#
# Since pre-realization kids have no user input to distract
# them they should answer promptly, or at minimum deterministically,
# meaning that any deadlock should show up early rather than late.
#
# o Suggested widget life cycle:
#
# 1 Create widget; its state-encapusulating thread starts up.
#
# 2 Parent calls size_preference fn of child. An exception
# should be thrown if this fn is called a second time.
#
# 3 Parent calls realize fn of child, handing it Kidplug and Window.
# An exception should be throw if it is called a second time.
#
# -> The existing codebase has a problem with widgets which
# change size preference between (2) and (3). They need
# to remember to file a REQ_RESIZE after realization.
#
# 4 Child runs, processing user input.
# Children responding to user input should send at most
# one REQ_RESIZE. (Parent is not obligated to honor them.)
#
# 5 Widget is notified of loss of window via ETC_OWN_DEATH
# and ceases drawing. (But continues to accept incoming
# user input.)
#
# o Existing eXene irritations:
#
# * Keyboard focus defaults in X to the root window,
# in practice the one pointed to by the mouse, which
# is a nuisance. Using SetInputFocus to set it to
# a more appropriate default would be nice. So would
# being able to TAB between textfields.
#
# * Hostwindows could trap CLIENT_TakeFocus from window
# manager and restore last-active widget.
#
# * Textwidgets could highlight when they have keyboard focus.
#
# o In Dusty's version windows receive window manager CI_FocusIn
# and CI_FocusOut messages are sent.
#
# o Dusty added support for WM_DELETE_WINDOW x protocol,
# and a 'shell' deletionEvent made available which
# signals CLIENT_DeleteWindow messages.
#
# o Dusty's 'shell' has an input-focus manager supporting
# TABbing through text widgets. (See circa p23 in his
# thesis for much detail.)
#
# o Dusty added a wrapper object to highlight focusable objects.
#
# o Dusty thinks buttons should participate in the TAB protocol
# too, to support mouse-free GUI usage (I'm sure Drake would approve!)
# but did not implement this.
#
# o Dusty's X resource stuff:
#
# * "view" == "style" + "style-view":
#
# . "style" == eXene version of Xlib resource db
# . "style-view" == search key into that db, e.g. app name.
#
# * "args" list is attribute/value pair list.
#
# Widget maintains "attrs" list of triples (attrfibute, type, default_value)
#
# * eXene supports searching for attribute in (in order)
# . args list
# . style per style-view
# . attrs list default.
#
# o Dusty implemented a commandline arg parsing facility
# inspired by xlib's XrmParseCommand
#
# o Dusty implements X Selections. At least part
# of the logic appears to be in:
#
src/lib/x-kit/xclient/src/window/selection-old.pkg#
src/lib/x-kit/xclient/src/window/selection-imp-old.api#
src/lib/x-kit/xclient/src/window/selection-imp-old.pkg
#
# The above comments summarize material in Dusty's thesis
#
# http://mythryl.org/pub/exene/dusty-thesis.pdf
#
# Much the same material is repeated in the "The Future of eXene"
#
# http://mythryl.org/pub/exene/future.pdf
#
#