/* TEACH RC_CONSTRAINED_PANEL A.Sloman April 1999 This file presents a tutorial on using rc_control_panel with fields constrained by "constrainer" procedures and linked by "reactor" procedures. It introduces you to the use of rc_control_panel with fields linked to Pop-11 variables, then to fields with constrainers which limit their possible values, then to fields with reactors which can cause changes to be propagated. This presupposes that you already know about the basic functionality of rc_control_panel. Modified 17 Apr 1999, to show how to use the procedures rc_update_fields and panel_update and the syntax {reactor [....]} using a list of vectors instead of a procedure. CONTENTS - (Use g to access required sections) -- Introduction and demonstration -- -- Running the demonstrations -- -- Play with panel1 (unconstrained) -- -- Play with panel2 (constrained but not linked) -- -- Play with panel3 (constrained and linked) -- -- Play with panel4 (linking a slider and a counter button) -- -- Play with panel5 (RADIO and SOMEOF buttons) -- Find out how it is all done -- Constraining panel components to change together -- First example: creating an unconstrained panel: panel1 -- -- Testing unconstrained fields with identifiers: panel1 -- Constraining the values of the panel fields -- -- Constraining the TEXTIN field: constrain_textin -- -- Constraining the NUMBERIN field: constrain_number -- -- The procedure create_constrained_panel -- -- Testing the constrained fields: panel2 -- -- Exercise: give the text field an Error panel -- Linking the fields -- -- Linking the TEXTIN field to the other two: textin_reactor -- -- Linking the NUMBERIN field to the other two: number_reactor -- -- Linking the SLIDER field to the other two: a simpler method -- -- The procedure create_linked_panel -- -- Testing the constrained and linked fields: panel3 -- Linking the above panel to an active variable -- Linking a counter (increment) button and a slider: panel4 -- -- Exercise: Add a number input button -- Associating variables with RADIO buttons or SOMEOF buttons: panel5 -- -- Accessing or altering the state of a RADIO or SOMEOF field -- -- Exercise: link RADIO field and SLIDER field -- Associating a panel field with an lvars variable -- Introduction and demonstration ------------------------------------- The RCLIB package provides a variety of flexible tools for combing graphical displays (including moving images) with interface components for interacting with a user, e.g. sliders, action buttons, text or number input fields, etc. In particular the rc_control_panel library makes it easy to create control panels containing various kinds of fields, which are automatically formatted. This file shows how to add constraints and reactors to changeable panel fields. A reactor is a procedure which does something when the field is changed, e.g. when a slider is moved. An introductory tutorial overview of the whole RCLIB package, including examples of the use of rc_control_panel can be found in TEACH RCLIB_DEMO.P and a more general overview in HELP RCLIB The rc_control_panel utility makes it relatively easy to assemble a panel displaying a number of different text fields, graphical fields and other interface components, which are automatically configured. General information about it, with examples, can be found in HELP RC_CONTROL_PANEL with an extended tutorial example in TEACH RC_CONTROL_PANEL This file assumes you have seen the sort of thing rc_control_panel can do. Using a mixture of Pop-11 code and commented out text, it introduces some of the more advanced features of rc_control_panel. Before reading this you should have played with some of the simple examples in the above help and teach files. -- -- Running the demonstrations You can get a feel for what this file is about if you first compile it all ("ENTER l1" in VED). Then compile each of the following Pop-11 commands (ESC d): vars panel1 = create_simple_panel(20, 5); vars panel2 = create_constrained_panel(360, 5); vars panel3 = create_linked_panel(700, 5); vars panel4 = create_slider_counter_panel(700, 280); vars panel5 = create_colours_panel(700, 460); That will produce 5 panels created by rc_control_panel, differing as explained below. If you move the mouse pointer into a text input or number input field then either press a keyboard key or click with mouse button 1, it will go into edit mode, indicated by a change of background colour. You can then type into it, though if what you type is rejected you will see a message printed out. When you have completed an edit in a text input or number input field, either click on the text with mouse button 1, or press RETURN with the mouse cursor in the input area. The panel will change back to its "consolidated" colour and the new value installed in the associated data-structure. -- -- Play with panel1 (unconstrained) After changing anything in panel1, look at the values of these variables: text1, number1, slider1 => Note that the fields in panel1 are totally unconstrained, apart from the fact that the slider is limited to integer values between 0 and 5. You can dismiss that panel by clicking on the "Kill panel" button. -- -- Play with panel2 (constrained but not linked) The text input and number input fields in panel2 have had constrains associated with them limiting what they will accept. After changing things on panel2, try these variables: text2, number2, slider2 => The values you can give text2 by editing the text input field should now be restricted to the strings 'zero', 'one', 'two', 'three', 'four', five. Similarly the values you can give the variable number2 by editing the number input field should be restricted to integers in the range 0 to 5. If you attempt to put an excluded value into the textin or number window you will get a warning message, and the previous value will be restored. The slider behaves as in panel1. After testing panel2, dismiss it. -- -- Play with panel3 (constrained and linked) In panel3 has the text input, the number input and the slider fields linked (by using "reactor" procedures as explained below). This means that in addition to having the constraints of panel2 associated with the fields they are also programmed to react to changes so that the other fields are also changed to show the appropriate value. The variable one_to_five is also changed. After changing anything on panel3, try this: one_to_five => After playing with panel3, dismiss it. -- -- Play with panel4 (linking a slider and a counter button) Panel4 has a slider, as in previous panels, and also a counter button, displaying the value of the slider. You can increment the counter by clicking in it using mouse button 3, and decrement it using mouse button 1. Try that and see what happens to the variable: number4 => Dismiss panel4 after playing with it. -- -- Play with panel5 (RADIO and SOMEOF buttons) Then try clicking on the colour names on panel5, and print out the values of these two variables: colour, colours => Dismiss panel5 after playing with it. -- Find out how it is all done ---------------------------------------- After experimenting with the panels, click on the KILL buttons to get rid of them and read on to see how it is all done. The file introduces you to the use of rc_control_panel with fields linked to Pop-11 variables, then to fields with constrainers which limit their possible values, then to fields with reactors which can cause changes to a field to be propagated to one or more other fields. -- Constraining panel components to change together ------------------- We first show how to create panel1, with three uconstrained, unliked fields. Then we show how to create panel2 with constrained text input and number input fields. This leads towards the definition of panel3 a control panel which includes: 1. A text input field which can contain a string in the list ['zero' 'one' 'two' 'three' 'four' 'five'] 2. A number input field which allows only an integer value in the range 0 to 5. 3. A slider, whose values range between 0 and 5, constrained to be integers, The fields are linked by reactors so that if any one of the three is changed, the other two should change accordingly. In addition 4. The pop-11 variable "one_to_five" should have a value which is automatically updated to reflect the number specified on the panel. Later we show how to define an active variable which has the ability to alter the value displayed in a panel whenever the variable's value is changed. An active variable is essential a procedure with an updater, disguised as a variable, as explained in HELP ACTIVE_VARIABLES -- First example: creating an unconstrained panel: panel1 --------- First, here is a procedure which creates a panel with three fields, a text input field a number input field and a slider field, without the components being constrained. We use three separate variables, text1, number1, and slider1, associated with the three fields. We'll use three different variables because the fields will not yet be linked to each other. To associate the fields with variables, we use these expressions at appropriate points in the panel description list given to rc_control_panel: {ident text1} {ident number1} {ident slider1} First ensure that relevant libraries are compiled. */ uses rclib uses rc_informant uses rc_window_object uses rc_slider uses rc_buttons uses rc_text_input uses rc_control_panel ;;; Declare and initialise variables to be associated with each field. global vars text1 = 'one', number1 = 2, slider1 = 3; ;;; Define the first of several panel creation procedures. define create_simple_panel(x,y) -> panel; ;;; A procedure to create the unconstrained panel, with ;;; fields of type TEXT, ACTIONS, TEXTIN, NUMBERIN, ;;; and SLIDERS rc_control_panel( x, y, [ {width 30} [TEXT : 'Demonstrating' 'unconstrained panel components' ] [ACTIONS {width 100} {align centre} : ['KILL PANEL' rc_kill_menu] ] [TEXTIN {ident text1} {margin 5} {align centre} {labelcolour 'blue'} {labelstring 'Number word:'} {width 50} : '' ;;; will be replaced by value of text1 ] [NUMBERIN {ident number1} {margin 5} {align centre} {fg 'blue'} {activebg 'yellow'} {labelcolour 'blue'} {labelstring 'Number:'} {width 80} : '' ;;; will be replaced by value of number1 ] [SLIDERS {ident slider1} {fieldbg 'grey75'} {margin 10} {offset 40} {width 250} {height 30} {framecol 'black'} {barcol 'white'} {radius 6} {blobcol 'blue'} : ;;; the word "round" constrains the value to integers [{0 5 ^slider1 1} round [{5 10 'Integer value: [0 to 5]'}] ] ] ], 'Panel1') -> panel; enddefine; /* -- -- Testing unconstrained fields with identifiers: panel1 ;;; Test that vars panel1 = create_simple_panel(500, 20); ;;; You can now alter the text field or the number field by putting ;;; the mouse in the box and typing. When done click with mouse button 1 ;;; or press the RETURN key. You can also change the slider by using the ;;; mouse to move the blob. You'll see that it moves discretely. ;;; After changing things on the panel print these values out text1, number1, slider1 => Each field can be changed independently of the others. The values of the appropriate variable will be changed whenever a field is changed. -- Constraining the values of the panel fields ------------------------ We now define a new procedure create_constrained_panel with the textin and numberin fields constrained suitably. This time we link their values to the variabiles text2, number2, slider2. The slider field was already constrained to integer values by the use of "round" in the field description (as opposed to "noround"). It was also constrained to the range 0 to 5 by the expression {0 5} But we still need to constrain the TEXTIN and NUMBERIN fields. We'll define two constraint procedures, one to constrain the text input string, and another to constrain the number in the NUMBERIN field. We'll then define a new version of the panel creation procedure which includes these constraints in the panel specification. -- -- Constraining the TEXTIN field: constrain_textin We can constrain the TEXTIN field to be one of the desired strings as follows. */ ;;; Make a list of permitted strings used in the constraint vars allowed_strings = ['zero' 'one' 'two' 'three' 'four' 'five']; ;;; Define a procedure which, when given a string checks that it is ;;; permitted, and if not returns undef and print out a message. ;;; Returning undef makes it use the old value. You could instead ;;; return 'zero' define constrain_textin(string) -> string; returnif(string = undef); unless member(string, allowed_strings) then ;;; printing out a warning, may or may not be desirable [^string is not a permitted string here] => ;;; replace the illegal string with undef undef -> string; endunless; enddefine; /* Below, we can then use that procedure in the specification of the TEXTIN field using this expression: {constrain constrain_textin} -- -- Constraining the NUMBERIN field: constrain_number Similarly we can provide a constraint for the NUMBERIN field, which will be used to constrain the NUMBERIN field thus: {constrain constrain_number} This time, instead of printing out a message about the violated constraint, we use the procedure rc_message_wait to create a "pop up" panel to explain why the number is rejected. */ ;;; Library used in next procedure uses rc_message_wait ;;; Define a procedure to pop up a panel explaining which numbers are ;;; permitted in the number input window. define explain_nums(num); ;;; Create strings to be displayed in the pop up panel. lvars errstrings = [%num >< nullstring, 'Is not permitted.', 'Only numbers 0 to 5 are.' %]; ;;; rc_defer_apply will put the panel up after the current ;;; event has been processed. Give it a Pop-11 closure of ;;; rc_message_wait as argument. (See HELP CLOSURES) rc_defer_apply( rc_message_wait(%300, 300, errstrings, 1, true, '9x15', 'black', 'white'%)); enddefine; ;;; Now a procedure which, when given a number, checks that it is ;;; permitted. If not it displays a message panel, and returns ;;; undef. (It could do something more fancy) define constrain_number(num) -> num; returnif(num = undef); unless isinteger(num) and 0 <= num and num <= 5 then ;;; Display the panel (Optional) explain_nums(num); ;;; replace the illegal value with undef undef -> num; endunless; enddefine; /* -- -- The procedure create_constrained_panel After compiling the two procedures above, define a new procedure which creates a panel with the fields constrained to have only permitted valus. */ ;;; Set up the variables linked to the fields, as before. global vars text2 = 'one', number2 = 2, slider2 = 3; define create_constrained_panel(x,y) -> panel; rc_control_panel( x, y, [ {width 30} [TEXT : 'Demonstrating' 'constrained panel components' 'Select numbers:' ] [ACTIONS {width 100} {align centre} : ['KILL PANEL' rc_kill_menu] ] [TEXTIN {ident text2} ;;; altered to include constraint {constrain constrain_textin} {margin 5} {align centre} {labelcolour 'blue'} {labelstring 'Number word:'} {width 50} : ;;; This will be over-ridden by the identifier value '' ] [NUMBERIN {ident number2} ;;; altered to include constraint {constrain constrain_number} {margin 5} {align centre} {fg 'blue'} {activebg 'yellow'} {labelcolour 'blue'} {labelstring 'Number:'} {width 30} : '' ;;; to be replaced by identifier value ] [SLIDERS {ident slider2} {fieldbg 'grey75'} {margin 10} {offset 40} {width 250} {height 30} {framecol 'black'} {barcol 'white'} {radius 6} {blobcol 'blue'} : ;;; the word "round" constrains the value to integers [{0 5 ^slider2 1} round [{-25 10 'Integer value: [0 to 5]'}] ] ] ], 'Panel2') -> panel; enddefine; /* -- -- Testing the constrained fields: panel2 ;;; test that procedure vars panel2 = create_constrained_panel(500, 20); ;;; See what happens if you try to give the text or number input ;;; field a value which is not permitted. text1, number1, slider1 => All three fields are now constrained to have the required values, so all we need to do is link them, so that if one is changed the others are also changed. -- -- Exercise: give the text field an Error panel If you type an number which is not permitted, into the NUMBERIN field, you are shown a message in a popup display panel. Try doing that for the TEXTIN field. I.e. copy and edit the procedure explain_nums to define a procedure called explain_text which you can then allow constrain_textin to invoke, instead of printing out a message. It should cause an explanatory panel to be displayed if you type a disallowed string into the TEXTIN field. If you redefine constrain_textin, the new version should work without your having to recreate the panel. -- Linking the fields ------------------------------------------------- We wish to make the fields linked. In order to do that we have to give each field a label (not to be confused with the labelstrings to the left of the text input and number input fields). The labels can then be used to access the compents of the fields, as explained in HELP RC_CONTROL_PANEL; The new fields defined in the procedure create_linked_panel, below, will use these label specifications: {label textin1} {label numberin1} {label slider1} This will enable the first field to be accessible using the word "textin1" as the