I have found the propsheet facilities very powerful but very hard to
learn to use. The file below provides a much simpler pop-11 interface
to a useful subset of propsheet facilities, e.g. creating a control
panel with a slider or numeric field to control a varaible.
It includes a number of demonstration examples.
Further procedures for easy creation pop-up menus, dialogue boxes, more
general asynchronous control panels, etc. will shortly be available.
Aaron
=======================================================================
/* --- The University of Birmingham 1995. --------------------------------
> File: $poplocal/local/lib/puitools.p
> Purpose: Procedures to create a variety of control panels
> Author: Aaron Sloman, Feb 1 1995
> Documentation: Test examples below for now
> Related Files: TEACH * PROPSHEET, REF * PROPSHEET
*/
/*
Note: the contents list is below.
-- INTRODUCTION: A NEW PROCEDURAL INTERFACE TO PROPSHEET
Although propsheet is very powerful and general, it is quite hard for a
Pop-11 programmer to use because it uses many non-Pop idioms, e.g.
specifying options for control panels using a language composed of lists
of words and numbers where much is implicit. E.g. the instruction to
build a slider is an expression like "3-4" which does not use the word
"slider" at all.
Also the Propsheet documentation does not give useful information on how
to control colours or location of propsheet panels or menus.
The POP_UI facilities described in REF * POP_UI are a heroic attempt to
make the tools more accessible, but I believe they do not provide a good
collection of the most basic required features, especially for building
menus for useful interaction, or control panels for asynchronous control
of a running program (e.g. changing a variable). Moreover, learning how
to do these things from the documentation provided is not easy.
So I have been trying to produce a collection of procedures which can be
used in the familiar Pop-11 idiom for creating menus, control panels,
and dialogue boxes, building on the powerful facilities available in
Propsheet, but making them much easier to handle. What follows is a
first draft incomplete set of tools. Additional tools have been designed
already and will soon be made generally available.
These facilities have been tested in Poplog V14.5 and may not work in
earlier versions. I believe most of them will work equally well under
Motif or Openlook, though you may have to check that Motif locates
objects where you expect it to.
The general idea is to provide a procedure that creates a propbox with
one propsheet, and a collection of tools for adding propfields to a
propsheet, or adding extra propsheets. The fields may include numeric
control panels, such as a slider or a numeric field with increment and
decrement arrows. These are added by the two procedures
pui_add_slider_field(sheet, w_id_proc, lo, hi, default, units, label);
pui_add_number_field(sheet, w_id_proc, default, units, label);
There are also procedures for changing the width or location of a
propbox.
In the final version these procedures will be split into separate
autoloadable libraries.
When you create a new field that controls a variable (e.g. a slider
field) you have the option to specify the variable either as a word
(e.g. "x"), or an identifier (e.g. ident x). The latter is generally
preferable as it will work across section boundaries, and may even be
used for variables defined as lvars. Instead of a word or identifier you
can provide a callback procedure which changes whatever is necessary.
These options are provided through the second argument of the two
procedures mentioned above.
Note: See REF * XptDeferApply for a warning on callbacks.
-- Bugs
There are some problems, which may arise only in the context of Openlook
to do with the difficulty of re-sizing a control panel, since default
sizes tend to be too large and ungainly (i.e. too wide).
-- The demonstrations below
This file can be loaded with the command
ENTER l1
Below is a collection of test examples all of which are inside comments
and therefore will not be run if the whole file is run.
*/
/*
CONTENTS - (Use <ENTER> g to access required sections)
-- INTRODUCTION: A NEW PROCEDURAL INTERFACE TO PROPSHEET
-- Bugs
-- The demonstrations below
-- Some test examples
-- pui_location: get or alter a widget's screen location
-- pui_width: get or alter a widget's width
-- pui_newbox: A procedure to create a propbox with one sheet
-- Now a collection of procedures to add extra fields to a sheet
-- pui_add_text_field(sheet, text, label) -> label
-- pui_add_text_fields(sheet, list) -> labels;
-- The main callback for numeric fields
-- pui_add_slider_field(sheet, w_id_proc, lo, hi, default, units, label);
-- pui_add_number_field(sheet, w_id_proc, default, units, label);
-- pui_slider(sheet, w_id_proc, lo, hi, default, units, loc, label)
*/
/*
-- Some test examples
;;; A DEMONSTRATION OF (SOME OF) THE NEW PUI FACILITIES
;;; First ENTER l1 to load this file.
;;; Then try the following:
;;; USE pui_newbox TO CREATE A PROPBOX WITH ONE SHEET
;;; Create prop box b1, with sheet s1, at location specified by the
;;; third argument to pui_newbox.
;;; pui_newbox returns a propbox and a propsheet inside the box, but
;;; does not show them on the screen, as you will typically want to
;;; add more before showing the box. The first two arguments are strings
;;; and the third false, or a widget or a location in a vector
vars (b1, s1) = pui_newbox('TEST', 'EXAMPLE', {500 30});
;;; show the box, then hide it before adding more to the sheet
propsheet_show(b1);
;;; Note that the first string is on the title bar, the second in the
;;; propsheet. There is only one button, "Dismiss". Clicking on it is
;;; equivalent to:
propsheet_hide(b1);
;;; You can restore the box with propsheet_show
;;; Try moving the box around, and making it appear and disappear.
;;; Declare some variables to be controlled by sliders, etc., below
vars www, xxx, yyy;
;;; We want to add a slider. So lets add some text explaining what the
;;; Slider is for and how to use it.
;;; Add a non-writeable text field to the sheet
pui_add_text_field(s1, 'Select a number by sliding', false) =>
;;; Ignore the internal field label generated automatically and returned
;;; as a result (e.g. "text1"). You can use the label with advanced
;;; facilities described in REF PROPSHEET (ignored here.)
;;; Now show the box with the field
propsheet_show(b1);
;;; Dismiss it and add then a field with a label
pui_add_text_field(s1, 'Drag the slider button', 'Do this') =>
;;; Note that the label provided as fourth argument is returned.
;;; Look at the enlarged box
propsheet_show(b1);
;;; Hide it and add a slider
propsheet_hide(b1);
;;; Add a slider to control variable xxx,
;;; with range 1 - 100, initially set to 50
;;; No need for a label for the slider, and ignore the internal label,
;;; which is returned as a result
pui_add_slider_field(s1, "xxx", 0, 100, 50, 'cms', false) =>
;;; Now show the box again.
propsheet_show(b1);
;;; CHANGING WIDTH OR LOCATION OF A PROPBOX
;;; Use pui_width (defined below) to shrink the width of the sheet
430 -> pui_width(s1);
;;; Under Openlook not all the components respond well to shrinking.
;;; change the location
600, 200 -> pui_location(b1);
;;; or use a coordinate vector:
{600 30} -> pui_location(b1);
;;; Check the value of xxx, and see if moving the slider changes it
xxx =>
;;; try moving the slider around while this loop prints out the value
;;; of xxx
repeat 30 times xxx => syssleep(50) endrepeat;
;;; The control should be asynchronous if you click on the bar to either
;;; side of the slider "thumb"
;;; Click on "Dismiss", or hide the box
propsheet_hide(b1);
;;; ADDING A SECOND SHEET, WITH EXTRA CONTROLS
;;; Add a slider to control the value of variable www, with
;;; lines of text first, in a new propsheet, s2.
;;; First create Sheet s2, with instructions for the next slider:
vars s2 = pui_add_sheet('Slide to choose \na percentage of 4000', b1);
;;; Get ready to show the new sheet (only when the box is visible).
propsheet_show(s2);
;;; Now add another slider to sheet 2, to control the identifier www
pui_add_slider_field(s2, ident www, 0, 100, 25, '(% of 4000)', false) -> ;
;;; note that 'ident www' is the same as 'identof("www")' except that
;;; the former is invoked at compile time, so it can be used inside
;;; a procedure definition to control the value of a lexical variable.
;;; Also we have added an explanatory string instead of a "units" field.
;;; show everything
propsheet_show(b1);
;;; That may be too wide. Try to shrink the new sheet
430 -> pui_width(s2);
;;; That may not work, presumably because there is another sheet.
;;; Compare trying
460 -> pui_width(b1);
;;; Test that the new slider controls the value of www, by moving the
;;; lower slider while this loop runs
repeat 35 times www => syssleep(50) endrepeat;
;;; Hide the panel in order to add another numeric field, controlling
;;; the variable yyy. Click on "Dismiss" or do:
propsheet_hide(b1);
;;; First use pui_add_text_fields to add three lines of text, the first
;;; one blank. Note that this returns a list of three internally
;;; generated field labels.
pui_add_text_fields(s2,
['' 'Click on Arrow' 'this represents temperature']) =>
;;; Now add a numeric field controlled by two arrows, default 20 cms
;;; with label Temp, and controlly the variable yyy:
pui_add_number_field(s2, ident yyy, 20, 'cms', 'Temp') =>
;;; show all
propsheet_show(b1);
;;; Test that changing the Temp field alters yyy, when you click on
;;; the arrows
repeat 35 times yyy => syssleep(50) endrepeat;
;;; You can alter the number in the text field by directly editing it.
;;; The revised version will not be transferred to the variable, unless
;;; you press the RETURN key. However if you do that the control panel
;;; will disappear as well. So directly editing the text does not work
;;; well for a continuous controller. Alternatively, you may find that
;;; using TAB instead of RETURN works.
;;; USING A CALLBACK PROCEDURE
;;; Now add a numeric field that invokes callback procedure every
;;; time it is changed. It needs to take three arguments and return
;;; one result.
define number_callback(sheet, field, new_value) -> new_value;
lvars sheet, field, new_value;
[field ^field now has value ^new_value] =>
vedcheck();
enddefine;
pui_add_number_field(s2, number_callback, 50, 'cms', false) -> ;
;;; or a slider
pui_add_slider_field(
s2, number_callback, 0, 100, 25, '(% of 4000)', false) -> ;
propsheet_show(b1);
;;; PUI_SLIDER
;;; A packaged procedure to create a slider: pui_slider. Invoke it with:
;;; 1. A titlebar string, 2. a string for the main sheet, 3. a variable,
;;; identifier or procedure to be set or invoked when the slider is
;;; moved, 3,4,5, the three integers specifying slider range and default,
;;; 6. a possibly empty units string, 7. false or a location vector (or a
;;; widget or windwo to be covered, and 8. false or a label for the slider.
vars box, sheet, label, yyy;
;;; This will create a slider to control the variable yyy
pui_slider('TEST', 'A slider to try out now', ident yyy,
-20, 20, 5, 'pounds', {400 20}, false) -> (box, sheet, label);
;;; Show the box. Everything else will then be shown.
propsheet_show(box);
;;; Shrink it
460 -> pui_width(sheet);
;;; test its effect on yyy
yyy =>
;;; END OF TESTS
*/
section;
/*
-- pui_location: get or alter a widget's screen location
*/
uses xpt_screeninfo;
define global pui_location(widget) -> (x,y);
;;; return window coordinates
;;; The updater is defined below.
lvars widget, x,y;
XptWidgetCoords(widget) -> (x,y,/*w*/,/*h*/)
enddefine;
lvars screen_w = false, screen_h = false;
define lconstant transform_coords(x,y, widget) -> (x,y);
;;; Used for updaterof pui_location
;;; if x and y are >= 0 leave them unchanged.
;;; If either is negative use screen dimensions and
;;; widget dimensions to find new widget location
;;; add 20 to y to cope with titlebar
lvars x,y, widget, width, height;
returnif(x >= 0 and y >= 0)(max(20,y) -> y);
unless screen_w and screen_h then
XWidthOfScreen(XtScreen(widget).dup) -> screen_w;
XHeightOfScreen(/*screen_ptr*/) -> screen_h;
endunless;
XptWidgetCoords(widget) -> (/*x*/,/*y*/,width,height);
if x < 0 then screen_w - width + x -> x endif;
if y < 0 then screen_h - height + y -> y endif;
;;; ensure results are non-negative;
max(x,0) -> x;
max(y,20) -> y;
enddefine;
define updaterof pui_location(loc, widget);
lvars loc, x,y;
;;; Set location if given, using loc
if isinteger(loc) then
transform_coords(/*x,*/ loc, widget) -> (x,y);
x,y, false, false -> XptWidgetCoords(widget)
elseif isvector(loc) then
transform_coords(explode(loc), widget), false, false
-> XptWidgetCoords(widget)
elseunless loc then
;;; false, so centre on screen
XptCenterWidgetOn(widget, "screen")
else
;;; it must be a widget
XptCenterWidgetOn(widget, loc)
endif;
enddefine;
/*
-- pui_width: get or alter a widget's width
*/
define constant pui_width(widget) -> w;
lvars widget,(x,y,w,h) = XptWidgetCoords(widget);
enddefine;
define updaterof pui_width(w, widget);
lvars widget, w;
false, false, w, false -> XptWidgetCoords(widget);
enddefine;
/*
-- pui_newbox: A procedure to create a propbox with one sheet
;;; Test
vars (b1, s1) = pui_newbox('TEST', 'TEST', {500 30})
propsheet_show(b1);
*/
define pui_newbox(title, header, loc) -> (box, sheet);
;;; Title is used for the titlebar of the propbox
;;; Header is used for the header of the propsheet
;;; loc is false, a vector with two numbers, or a widget
lvars title, header,
box = propsheet_new_box(title,false,false,[Dismiss]),
sheet = propsheet_new(header, box, false);
XtRealizeWidget(box);
loc -> pui_location(box);
propsheet_show(sheet);
enddefine;
define pui_add_sheet(string, box) -> sheet;
;;; This is essentially just popsheet_new, but could be redefined
;;; to do extra things, e.g. respect a global width variable.
lvars string, box, sheet = propsheet_new(string, box, false);
enddefine;
/*
-- Now a collection of procedures to add extra fields to a sheet
Typically a box will have been created with a sheet, and these
procedures can add fields, for control of running programs, interacting
with the user, etc.
*/
/*
-- pui_add_text_field(sheet, text, label) -> label
*/
define pui_add_text_field(sheet, text, label) -> label;
;;; Add a text field with instructions, etc. to the sheet.
;;; If label is false, use an internal label, otherwise use the
;;; label, which should be a word or string.
lvars sheet, text, label, nolabel = not(label);
unless label then
gensym("text") -> label;
endunless;
propsheet_field(sheet,
[^label ^text
%if nolabel then dl([(nolabel aligned = false )]) endif %] );
false -> propsheet_sensitive(sheet, label);
enddefine;
/*
-- pui_add_text_fields(sheet, list) -> labels;
*/
define pui_add_text_fields(sheet, list) -> labels;
;;; add several unlabelled fields from the list to the sheet.
;;; Return a list of the internally generated labels.
lvars sheet, list, text, labels;
[%for text in list do pui_add_text_field(sheet, text, false) endfor%] -> labels
enddefine;
/*
-- The main callback for numeric fields
*/
define lconstant
number_update_cb(sheet, field, new_value, w_id_proc) -> new_value;
;;; this is partially appled to w_id_proc to create an accepter
;;; for slider fields or number fields (with up and down arrows).
;;; wi_id_proc can be either a word, in which case its valof is
;;; updated with the new_value, or an identifier, in which case its
;;; idval is updated, or a procedure, in which case it is applied to
;;; the new value.
lvars sheet, field, new_value, w_id_proc;
if isword(w_id_proc) then
new_value -> valof(w_id_proc);
elseif isident(w_id_proc) then
new_value -> idval(w_id_proc)
elseif isprocedure(w_id_proc) then
w_id_proc(sheet, field, new_value) -> new_value;
else
mishap('Word, Identifier or Procedure needed',[^w_id_proc]);
endif;
enddefine;
/*
-- pui_add_slider_field(sheet, w_id_proc, lo, hi, default, units, label);
*/
define pui_add_slider_field(sheet, w_id_proc, lo, hi, default, units, label)
-> label;
;;; w_id_proc is a word, an identifier or a procedure
;;; lo, hi, and default are integers,
;;; units is a string
;;; if label is false, an internal label is generated. Otherwise the
;;; label is used on left of slider. It should be a word or string.
lvars
sheet, w_id_proc, lo, hi, default, units, label, nolabel = not(label);
if nolabel then
gensym("slider") -> label
endif;
propsheet_field(sheet,
[^label ^lo - ^hi ^units
%if nolabel then dl([(nolabel aligned = false]) endif%
default = ^default)]);
;;; set the callback
number_update_cb(%w_id_proc%) -> propsheet_field_accepter(sheet, label);
;;; run the callback with the default value, to set variables, etc.
number_update_cb(sheet, false, default, w_id_proc) ->;
enddefine;
/*
-- pui_add_number_field(sheet, w_id_proc, default, units, label);
*/
define pui_add_number_field(sheet, w_id_proc, default, units, label) -> label;
lvars sheet, w_id_proc, default, units, label, nolabel = not(label);
;;; w_id_proc is a word, an identifier or a procedure
;;; units is a string
;;; default is an integer
;;; label is as in pui_add_slider
if nolabel then gensym("number") -> label endif;
propsheet_field(sheet,
[^label ^default ^units
%if nolabel then dl([(nolabel aligned = false]) endif%
default = ^default)]);
;;; set the callback
number_update_cb(%w_id_proc%) -> propsheet_field_accepter(sheet, label);
;;; run it once with the default value
number_update_cb(sheet, false, default, w_id_proc) ->;
enddefine;
/*
-- pui_slider(sheet, w_id_proc, lo, hi, default, units, loc, label)
-> (box, sheet, lab);
*/
define pui_slider(title, header, w_id_proc, lo, hi, default, units, loc, label)
-> (box, sheet, label);
;;; return a propbox with a slider
;;; w_id_proc is a word, an identifier or a procedure
;;; title, header and units are strings
;;; lo, hi, and default are integers,
;;; loc is a location specifier as in HELP * POPUPTOOL
lvars
w_id_proc, title, header, lo, hi, default, units, loc,
(box, sheet) = pui_newbox(title, header, loc);
pui_add_slider_field(sheet, w_id_proc, lo, hi, default, units, label)
-> label;
enddefine;
endsection;
|