/* TEACH RC_CONTROL_PANEL Aaron Sloman June 1997 Updated 10 Aug 2002 Revision notes are now added at the end. Additional changes in HELP rclib_news LIB * RC_CONTROL_PANEL Provides a tool for creating control panels involving vertically layered alternating text fields and button fields, with most of the formatting done automatically, and mechanisms provided for overriding defaults easily. It is possible to leave parts of the panel clear for drawing using rc_graphic facilities, or other rclib facilities. It is also possible to use rc_widen_window_by and rc_lengthen_window_by to widen or lengthen the window to add graphical objects. it is even possible to have movable graphical objects as described in TEACH * RC_LINEPIC that move over the control panel, either under mouse control or under program control. This file is compileable. Compile the whole file (ENTER l1), before attempting any of the commands demonstrated. Then, for a quick demonstration of what rc_control_panel can do, try this: vars panel = rc_control_panel(600, 10, field_specs, 'Demo Panel'); For more detailed information follow instructions below. This will explain how to alter the format of the panel created. NB THIS FILE CONTAINS ONE LONG AND COMPLEX EXAMPLE. SHORTER EXAMPLES MAY BE FOUND IN THESE FILES: TEACH * RCLIB_DEMO.P/rc_control_panel HELP * RC_CONTROL_PANEL TEACH * RC_CONSTRAINED_PANEL TEACH * POPCONTROL LIB * RC_POLYPANEL CONTENTS -- Running the demonstration -- Building the panel -- Redrawing the panel if it gets corrupted -- Drawing inside the panel -- Adding a mobile draggable object to the panel -- Constrained movers and sliders -- The structure of a panel -- Killing the panel -- How the panel is built up -- First we start with commands to get the libraries compiled. -- Now the panel defaults specification -- -- The first text field -- -- Two fields specifying map types -- -- Building a featurespec for the someof buttons -- -- The plans menu -- -- Utilities for the plan selection and plan drawing buttons -- -- Action buttons for managing the rc_graphic scratchpad -- -- Radio buttons for selecting a colour -- A slider field with two sliders, one with a reactor -- An action field with some counters, toggles, etc. -- A dials field with three identifiers, one with a reactor -- Combine all the field specifications to form the field_specs list -- Tracing the field construction -- Some exercises -- See also -- Revision notes -- . August 2000 -- Running the demonstration ------------------------------------------ This is a compileable file. I.e. do ENTER l1 to compile the whole thing. This creates the list field_specs, and other things. The first time it may be a bit slow as it may have to compile objectclass and much of the RCLIB library. The commands below create a list called field_specs, whose length you can print out length(field_specs) => also the list itself, which is quite long, and built up piecemeal, in what follows: field_specs ==> The list starts with a few vectors which specify the default background, foreground and font of the panel window: vars x; for x from 1 to 3 do field_specs(x) ==> endfor; After the vectors specifying properties of the panel, subsequent items are lists specifying individual fields of the panel. These include TEXT fields ACTION button fields, RADIO button fields, etc. It will be easier to see which components are strings if you do true -> pop_pr_quotes; before giving the following commands: You can examine the first text field specification: ;;; Make print instructions show strings; true -> pop_pr_quotes; field_specs(4) ==> This starts with some vectors specifying properties of the field, then, after a colon there are two strings. Examine other fields in a similar way, e.g. field_specs(6) ==> field_specs(10) ==> Note that between the vectors specifying the properties of a field and the other items specifying the contents of a field there is always a colon. -- Building the panel ------------------------------------------------- Having looked at the list field_specs, you can now use the list to create the demonstration panel, based on that list. rc_control_panel requires two numbers specifying the screen location for the top left corner of the panel, the list of field specifications, and a title for the panel. Run this command: vars panel = rc_control_panel("right", "top", field_specs, 'Demo Panel'); It should create a multi-field control panel, with alternating text fields and button fields, as defined by the list field_specs created below. There is a portion left blank at the top for drawing in. The instructions for building up the list field_specs, which gives a declarative specification of the contents of the panel, are explained in detail below. Field specs is quite a complex list, with portions defining differe panel fields, e.g. text fields, graphic fields, slider fields, etc. For now you can try clicking on the various buttons to see what happens. Details are explained below. The KILL button destroys the panel, but you can rebuild it using the above command, possibly after editing some of the field specifications below, and recompiling this file. You can re-size the panel using the mouse. Clicking on the REDRAW button will invoke the rc_redraw_this_panel procedure, which redraws the panel. The colours, fonts and layouts shown are not intended to be aesthetically pleasing. They merely result from the use of examples shown below to illustrate what is possible. If you move the sliders (using mouse button 1 on the slider's "blob"), or by clicking on the slider bars, try examining their values: slider1 => slider2 => You can also alter the sliders by typing new values into the number panel on the right of the slider. When the new number is complete click with button 1 or press the RETURN key to "consolidate" the number. The slider will move to the corresponding location and the associated variable will have a new value. One of the sliders has an associated reactor procedure that prints out information about new values whenever you change the slider value. You can also change the dials by using the mouse to move the dial's pointer, thereby changing the associated value. The values can be found frome these variables dial1 => dial2 => dial3 => One of the dials also has been given a reactor procedure. The button labelled "Counter" is associated with a variable called "num_val" whose value you can check after clicking on the button with mouse button 1 or 3 to decrement or increment the value (by a minimum amount specified in the button field): num_val => The button labelled "Switch flag" is associated with a variable called "test_flag" whose value will switch between true and false when you click on it with mouse button 1: test_flag => If you click on the SCRATCHPAD button it will create a scratchpad graphic window if there is not one already. If there is one it will make it the current window, so that drawing commands will work on it, such as this. repeat 10 times rc_draw_blob( random(500) - 250, random(500) - 250, random(50), oneof(['red' 'blue' 'green' 'orange' 'black'])) endrepeat; -- Redrawing the panel if it gets corrupted --------------------------- You can also draw on the panel itself. SETWINDOW panel; That does the equivalent of this: panel -> rc_current_window_object; Now give some drawing commands to mess up the panel. repeat 10 times rc_draw_blob( random(450), random(600), random(30), oneof(['red' 'blue' 'green' 'orange' 'black'])) endrepeat; This will clear the panel (if you have not killed it): rc_start(); Now redraw it rc_redraw_panel(panel); That may redraw only the panel components. Try rc_redraw_window_object(panel); That command does roughly what the REDRAW button does. Or hide it and then redraw and then show it. Some window managers may do strange things when you show the panel after hiding it. rc_hide_window(panel); SETWINDOW panel; rc_redraw_panel(panel); rc_show_window(panel); The ability to redraw makes use of the fact that the panel contains information about the text fields and button fields in it, and where each of them is located, and what fonts, colours and sizes they have. Notice that the rc_redraw_panel command will not re-draw extraneous pictures added, such as those drawn in the next section. -- Drawing inside the panel ------------------------------------------- Note that you can use rc_graphic commands to draw on this, e.g. in the top gap, remembering that by default for a control panel the rc_graphic origin is top left, x increases to the right y downwards, and the scales are both 1. SETWINDOW panel; vars x; for x from 30 by 30 to 400 do rc_draw_blob(x, 10, 10, 'blue'); rc_draw_blob(x, 70, 10, 'green'); endfor; vars x, radius = 4; for x from 20 by 30 to 400 do rc_draw_blob(x, 40, radius, 'red'); radius + 2 -> radius; endfor; You can also draw at the bottom, using the following command to discover the width and height of the panel (ignoring the location coordinates of the top left corner): vars width = rc_panel_width(panel), height = rc_panel_height(panel); ;;; increase the height by 50 false, false, false, height+50 -> rc_window_location(panel); ;;; do some drawing on the new bit rc_draw_ob(width div 2 - 75, height + 5, 150, 40, 5, 5); rc_draw_blob(width div 2, height + 25, 20, 'black'); Clicking on the REDRAW button, or giving the command rc_redraw_window_object(panel); Will redraw the panel getting rid of all the extra pictures drawn. However: rc_redraw_panel(panel); Will merely redraw the non-blank panel fields. -- Adding a mobile draggable object to the panel ---------------------- The following sort of example is illustrated in more detail in TEACH * RC_LINEPIC define :class draggable; ;;; this class inherits from three different "mixins" is rc_keysensitive rc_selectable rc_linepic_movable; slot pic_name; slot rc_picx == 20; slot rc_picy == 20; slot rc_pic_lines == [WIDTH 3 COLOUR 'black' [SQUARE {-15 -15 30}] [rc_draw_blob {0 0 10 'red'}] ]; enddefine; vars p1 = newdraggable(); 'p1' -> pic_name(p1); define :method print_instance(obj:draggable); printf('', [%pic_name(obj), rc_coords(obj) %]) enddefine; p1 => ;;; Draw the picture (it will appear at the top left corner) SETWINDOW panel; rc_draw_linepic(p1); ;;; Note that movable pictures will not necessarily have the colours ;;; specified except when drawn on plain white regions. ;;; (See HELP * RCLIB_PROBLEMS for more on this.) ;;; Move the picture object. Try this two or three times repeat 30 times rc_move_by(p1, 3, 5, true); syssleep(5); endrepeat; ;;; Or this to move it back repeat 30 times rc_move_by(p1, -3, -5, true); syssleep(5); endrepeat; ;;; Make the picture object draggable. rc_add_pic_to_window(p1, panel, true); ;;; Now it should be possible to drag the picture around using the mouse Warning1: if there are coloured regions in the panel then the movable object will change its colour as it moves over the, because of the technique used for drawing movable objects. See HELP rclib_problems Warning2: if the draggable object is left on a mouse button and then the mouse-button is pressed the portion of the picture occupied by the draggable object may be corrupted. rc_start(); rc_redraw_window_object(panel); -- Constrained movers and sliders ------------------------------------- Note that although the motion of the picture object p1 is unconstrained, it is possible to have a constrained mover acting as a slider control. For examples see TEACH * RC_LINEPIC/Constrained To clear a portion of the background for a constrained mover, use the following procedure defined in LIB * RC_DRAWLINE_ABSOLUTE. rc_drawline_absolute(x1, y1, x2, y2, colour, width) See HELP * RCLIB/rc_drawline_absolute Note that its coordinates are always absolute pixel units, with origin in top left and y increasing downwards. We can add a slider with a "square blob" at the bottom of the picture, as follows. First make available the modified slider library: uses rc_square_slider Then extend the panel by 50, using a dark grey background: rc_lengthen_window_by(panel, 50, 'grey5'); vars (, , width, height) = rc_screen_coords(panel); (If your screen is not large enough you may not see this extended portion.) Make a white rectangle in the new portion, leaving a dark surround: rc_drawline_absolute( 5, height -25, width - 5, height -25, 'white', 40); ;;; Now draw the slider using this format ;;; rc_square_slider(x1, y1, x2, y2, range, radius, linecol, slidercol, ;;; strings, spec) ;;; -> slider; ;;; x1,y1 and x2,y2 define the coordinates of the slider's ends. It does ;;; not have to be horizontal, but we give an example where y1 = y2. ;;; Where range can be either a number or a pair or a vector, giving ;;; min max and default values vars slider = rc_square_slider( 30, height-30, 300, height-30, {-500 500 0}, 7, 'red', 'black', [[{-10 20 'MIN(-500)'}] [{-60 20 'MAX(500)'}]], { ^rc_slider_convert_out ^round}); ;;; try moving the blob on the slider and printing out the value rc_slider_value(slider) => slider => 3 -> rc_slider_value(slider); -333 -> rc_slider_value(slider); 333 -> rc_slider_value(slider); 33.333 -> rc_slider_value(slider); rc_slider_value(slider) => ;;; The value can be updated by program, and the slider will move vars v; for v from -500 by 5 to 500 do v -> rc_slider_value(slider); syssleep(1); endfor; ;;; Now try moving the slider blob with the mouse ;;; Note: as explained in TEACH * RC_LINEPIC, once you have selected the ;;; slider blob with mouse button 1, you can hold the shift key down and ;;; click anywhere on the slider to make it jump to that location. ;;; If you select another picture object that no longer works till you ;;; again select the slider blob. A similar technique could be used to make a vertical slider on the right. See LIB * RC_SLIDER, HELP * RC_SLIDER -- The structure of a panel ------------------------------------------- rc_control_panel is a subclass of rc_window_object, so each panel is also an instance of that class, and by default prints in the window object format, telling you how many live buttons there are: panel => However you can examine the fields in the panel, as they are stored in a list in the rc_panel fields slot of the panel. The rc_print_fields procedure will show all the field records in the panel, i.e. the text fields and various kinds of button fields, with each field preceded by "**". It takes two arguments, a panel and an integer. The second argument specifies the level of printing. Using level 1 gives a very rough overview rc_print_fields(panel, 1); Using level 2 gives a bit more detail, including showing the labels of fields, where they have been specified. rc_print_fields(panel, 2); The next level is probably as deep as you'll want to go: rc_print_fields(panel, 3); To examine the specifications for a particular field you can select it thus, e.g. to look at the field contents of fields 3 amd 7: rc_field_specs(rc_panel_fields(panel)(3)) ==> rc_field_specs(rc_panel_fields(panel)(7)) ==> You can look at the actual contents created from the specifications using these commands: rc_field_contents(rc_panel_fields(panel)(3)) ==> rc_field_contents(rc_panel_fields(panel)(7)) ==> -- Killing the panel -------------------------------------------------- The panel includes a button marked "KILL" at the bottom which can be used to kill the panel. After killing it you can always rebuild it by recompiling this file and repeating this command: vars panel = rc_control_panel("right", 10, field_specs, 'Demo Panel'); These commands may be used to try killing the panel if the KILL button does not work, or if something goes wrong before the panel has been fully constructed. rc_kill_window_object(panel); rc_kill_window_object(rc_current_window_object); sysgarbage(); -- How the panel is built up ------------------------------------------ The panel is built up of several fields. At present the following field types are supported: TEXT, A text field displaying strings of text SLIDERS, A field containing one or more horizontal sliders ACTIONS, SOMEOF, RADIO Three sorts of button fields, for actions of many kinds, for sets of options that can be turned on and off, and a "radio buttons" field allowing at most one button to be "on" at a time (e.g. the buttons with colour names at the bottom). GRAPHIC A field in which pictures can be drawn. For examples see TEACH * RCLIB_DEMO.P/rc_control_panel HELP * RC_CONTROL_PANEL/GRAPHIC HELP * RC_CONTROL_PANEL/panel5 TEXTIN, NUMBERIN These are fields into which you can type some text or a number. Examples are in HELP * RC_CONTROL_PANEL Other types of fields may be added later. However the RC_GRAPHIC and RCLIB facilities allow many additional items to be added, including the movable and rotatable objects demonstrated in TEACH RC_LINEPIC and HELP RC_LINEPIC. Each field is specified by a list of information, starting with a word specifying the type of field, followed optionally by some property (resource) specifications, followed by a colon, followed by a list of contents specifiers for the field. The contents specifiers are strings in the case of TEXT fields and a button descriptors in the case of button fields. The permitted types of button descriptors depend on whether it is an ACTIONS field and SOMEOF field or a RADIO field. These cases are illustrated below, though more detailed examples are given in HELP * RC_BUTTONS Other types of contents specifiers are illustrated below and defined in HELP * RC_CONTROL_PANEL The rest of this file shows you how to build up the specifications of the fields, and how to combine the field specifications in one list. -- First we start with commands to get the libraries compiled. */ uses rclib uses rc_buttons uses rc_control_panel uses rc_scratchpad uses rc_message uses rc_slider /* -- Now the panel defaults specification ------------------------------- */ vars panel_specs = [ ;;; Default settings for the background and foreground of ;;; the panel {bg 'grey90'} ;;; try other colours, e.g. 'grey75' 'pink', 'ivory' ;;; These are not needed unless you leave blank spaces and ;;; later print or draw directly into them, as above {fg 'grey10'} {font '10x20'} ;;; specify that the window can be resized using the mouse {resize true} ;;; make sure that mouse movements do not trigger events in ;;; the panel, except when dragging. {events [dragonly]} ]; /* -- -- The first text field Note that the word "TEXT" is followed by some *optional* specifiers of field properties. In this case we include specification of a vertical gap of size 100, the font, and the margins of 5 pixels above and below the text in the background. The default dark grey background and light grey foreground for printing are used here. Note that a colon occurs at the end of the specifications. The following items are text strings in this case. */ vars panel_header = [TEXT ;;; Leave a big gap so that a drawing can be added {gap 80} ;;; Use a large font {font '12x24'} {margin 4} : ;;; Each string in a text field is presented on a ;;; separate line. 'A DEMONSTRATION OF' 'rc_control_panel' ]; /* -- -- Two fields specifying map types This panel is derived from one that was prepared originally for a demonstration which included drawing maps of various kinds showing plans that had been found by a planner. The nex two fields are a TEXT field giving instructions, and a SOMEOF buttons field offering four types of maps that can be chosen in any combination. In this panel we want to specify four SOMEOF buttons which can be turned on or off individually. it is sometimes useful to have procedures that specify what to do when the button options are turned on or off. We demonstrate with two dummy procedures that merely print out what is happening, and later show how to install the procedures in the panel. (In this case they are redundant, since the selection and deselection are recorded internally by the buttons, and another procedure, defined later, will scan the button records making a list of the rc_informant_value values for the buttons that are turned on.) */ define selecting_someof(button); ;;; Report selection of a someof button [Selecting ^(rc_button_label(button)) ] => enddefine; define deselecting_someof(button); ;;; Report deselection of a someof button [Deselecting ^(rc_button_label(button)) ] => enddefine; /* -- -- Building a featurespec for the someof buttons In order to pass those procedures in to the SOMEOF buttons to be stored in their two relevant slots, i.e. rc_radio_select_action(button) and rc_radio_deselect_action(button) we can create a button featurespec (See HELP * FEATURESPEC) to be combined with the SOMEOF specification. At the same time we choose a different colour for the button borders, i.e. blue. (You can change this, or try removing it, to see the default.) */ ;;; A featurespec to be used below. It associates button slot procedures ;;; with desired non-default values. vars someof_button_spec = {spec {^rc_button_blobcolour 'blue' ;;; Make buttons turn yellow when selected ^rc_chosen_background 'yellow' }}; vars ;;; A text field to precede the SOMEOF field map_strings = [TEXT ;;; Give the field an internal label {label instruct_maps} ;;; Centre the strings (the default, so this is redundant) {align centre} ;;; Comment out the following to get defaults for ;;; the foreground (text) and background {fg 'blue'} {bg 'pink'} ;;; text printed in the default font. Or try '10x20' ;;; {font '10x20} ;;; Margin above and below the text {margin 2} ;;; leave a gap of 2 after previous field, showing the panel ;;; background {gap 2} : ;;; Now the strings 'Select types of display' 'in which plan should be shown' ], map_types = ;;; The list of SOMEOF buttons [SOMEOF ;;; We want this field to have a label for future reference {label maps} ;;; leave small gaps between the buttons {spacing 2} ;;; insert the featurespec defined above ^someof_button_spec {height 16} ;;; specify select and deselect actions {select selecting_someof} {deselect deselecting_someof} {gap 2} ;;; above the field ;;; give it a coloured background different from ;;; the default ;;; {fieldbg 'pink'} {margin 4} ;;; above and below the buttons {cols 2} {align centre} : ;;; Here we represent each map type as a list of two ;;; elements, the string to be used on the button label, ;;; and a word to be accessible if the button is turned ;;; on. There are four map types. (If the map types were ;;; data-structures rather than words, they could still be ;;; inserted as the second elements of the button specifiers.) ['TERRAIN MAP' terrain_map] ['NOGO MAP' nogo_map] ['VISIBLE MAP' visible_map] ['ROADS' roads_map] ] ; /* -- -- The plans menu The next two fields are concerned with two ACTION buttons, preceded by some text. The first of the action buttons will, if selected, use rc_popup-query to create a menu panel, showing a list of possible plans, in this case represented only as strings. */ vars plan_action_strings = [TEXT {gap 2} {margin 5} {align centre} : 'Choose plan, then map types' 'then draw' ], plan_action_buttons = [ACTIONS {gap 2} {margin 4} ;;; above and below buttons {cols 0} ;;; I.e. use horizontal row {fieldbg 'pink'} {textbg 'blue'}{textfg 'yellow'} {spacing 4} : ;;; A button to invoke the menu to select a plan, via a ;;; pop-11 action deferred till after the event handler is ;;; finished, to prevent interactions between events. ['SELECT PLAN' [POP11 get_selected_plan(plan_strings)]] ;;; A button to invoke a plan drawing procedure ;;; defined below. ['DRAW PLAN' draw_selected_plan] ]; /* -- -- Utilities for the plan selection and plan drawing buttons First some global variables controlling the plan selection pop up menu, invoked by the ACTION button specified above as ['SELECT PLAN' [POP11 get_selected_plan(plan_strings)]] When this button is selected it should pop up a menu of enumerated plan descriptions. You can then choose one by selecting its number, or select "None" First some global variables controlling the format of the popup menu. */ global vars plan_menu_x = 300, plan_menu_y = 5, plan_menu_button_width = 45, plan_menu_button_height = 25, plan_menu_font = '9x15', plan_menu_bg = 'grey90', plan_menu_fg = 'grey10', ;;; The set of strings describing plans might be incrementally ;;; created by other procedures. ;;; It would be possible to make each field a two element list, the ;;; string and a plan label. Instead we get the menu to present ;;; numerical options, and one of them will be returned after the ;;; user has made a selection. plan_strings = [ 'plan1 {10 100}{190 5} cost < 1110 avoid visibility slope < 3 nogo(2.4)' 'plan2 {30 40}{100 20} cost < 2000 avoid ridges slope < 3 ' 'plan3 {20 40}{200 50}avoid values slope > 1.5 ' 'plan4 {10 100}{190 5}avoid visibility slope < 3 nogo(2.4)' ], ; ;;; Stuff for "persistent" message panel to display current plan ;;; after the selection has been made global vars message_panel_font = '8x13bold', message_panel_bg = 'gray10', message_panel_fg = 'gray90'; ; ;;; A global variable to hold the selected plan description, ;;; set via the menu created by get_selected_plan global vars current_selected_plan = false; lvars current_plan_message = false; /* ;;; Now define the procedure to be invoked when the 'SELECT PLAN' ;;; action button is selected. It will pop up a menu of plans, here ;;; identified by strings. ;;; Test the following procedure, and print the global variable ;;; it updates when finished. get_selected_plan(plan_strings), current_selected_plan => */ define get_selected_plan(strings); ;;; Procedure to present list of plan options in a menu panel, to be ;;; selected by using mouse button 1. if current_plan_message then rc_kill_window_object(current_plan_message); false -> current_plan_message endif; ;;; Present the menu panel, and get the plan number lvars answer = rc_popup_query( plan_menu_x, plan_menu_y, strings, "NUMBERS", false, false, plan_menu_button_width, plan_menu_button_height, plan_menu_font, plan_menu_bg, plan_menu_fg, false, false); if isinteger(answer) then strings(answer) else ;;; It could be the word "None" false endif -> current_selected_plan; ;;; Now put up a "persistent" message with a reminder of the ;;; chosen plan. if isinteger(answer) then rc_message(5,5, ['CURRENT PLAN' ^current_selected_plan], 0, true, message_panel_font, message_panel_bg,message_panel_fg) -> current_plan_message ; endif; enddefine; /* The specification for one of the action buttons to be shown on the control panel was as follows: ['DRAW PLAN' draw_selected_plan] We need to define the procedure draw_selected_plan which will be run when the button is selected. In order to find which "map" to draw it has to find which one was selected in the field offering map selections. Fortunately this had the specifier {label maps}. So we can access that field as rc_field_of_label(rc_current_window_object, "maps") since the variable rc_current_window_object will be set while the action is being performed. From the map type field we can extract the list of buttons using rc_field_contents, and we can use the procedure rc_options_chosen, which, if applied to alist of radio buttons returns the selected option, and for a list of someof buttons returns a list of selected options. The following procedure can be tested before and after selecting a plan, and selecting or deselecting map types, by clicking on the 'DRAW PLAN' button: */ define draw_selected_plan(); ;;; Dummy procedure, invoked by action button. ;;; Find which map types have been selected by looking at the ;;; maps option buttons. Makes use of the fact that the SOMEOF ;;; field giving the options has the specifier {label maps} if current_selected_plan then lvars map_types = rc_options_chosen( rc_field_contents( rc_field_of_label(rc_active_window_object, "maps"))); ;;; NB this cannot rc_current_window_object, because ;;; button actions are "deferred" if map_types == [] then 'Please select required map types first.' => else ;;; Dummy action for demonstration purposes [Drawing plan ^current_selected_plan ^map_types] ==> endif; else 'Please select a plan first'=> endif; enddefine; /* -- -- Action buttons for managing the rc_graphic scratchpad The idea of a scratchpad for drawing on without clobbering current rc_window_object windows was due to Brian Logan. See LIB * RC_SCRATCHPAD We here define two fields, one text field introducing scratchpad buttons and one action buttons field. The four scratchpad control buttons are. 'SCRATCHPAD' makes the scratchpad window active, so that rc_graphic commands will use it, and not clobber other windows 'TEAROFF' This saves the previous scratchpad (which then becomes inaccessible though remaining visible, and starts a new one 'KILL OLDPADS' This gets rid of previously saved scratchpad windows 'HIDE PAD' Removes the current scratchpad window. A new one can be created using the first button */ vars scratchpad_strings = [TEXT {gap 2} {align left} : 'Scratchpad actions:' ], picture_colours = [ 'black' 'blue' 'brown' 'cyan' 'darkgreen' 'gold' 'green' 'grey20' 'grey40' 'grey60' 'grey80' 'ivory' 'lavender' 'lightyellow' 'navy' 'orange' 'pink' 'skyblue' 'tan' 'white' 'yellow' ], scratchpad_buttons = ;;; This time we include a featurespec in the list [ACTIONS {width 125} {height 20} {margin 2} ;;; top and bottom margins {offset 1} ;;; left and right margins {spec { ^rc_button_stringcolour 'yellow' ^rc_button_bordercolour 'red' ^rc_button_labelground 'brown' ^rc_button_blobcolour 'ivory' } } {cols 3} : ;;; a button to make the scratchpad current, ;;; so that rc_graphic commands can be tried ['SCRATCHPAD' [POP11 rc_make_current_window(rc_scratch_window)]] ;;; This saves the previous scratchpad and starts a new one ['TEAROFF' rc_tearoff] ;;; this kills all the saved tearoffs ['KILL TEAROFFS' rc_kill_tearoffs] ;;; This hides the current scratchpad window ['HIDE PAD' [POP11 false -> rc_scratch_window ]] ['DRAW LINES' [POP11 rc_scratch_window -> rc_current_window_object; repeat 10 times oneof(picture_colours) -> rc_foreground(rc_window); random(15) -> rc_line_width(rc_window); rc_drawline( repeat 4 times random(600) - 300 endrepeat) endrepeat]] ['DRAW BLOBS' [POP11 ;;; draw some blobs but get the number of blobs to ;;; draw from the user. lvars num; ved_read_from_file( 'Type number of blobs, RETURN', 'getnumber') -> num; ;;; if Ved is running, exit the temporary file if vedediting or vedusewindows = "x" then ved_q(); endif; ;;; get a drawing panel ready rc_scratch_window -> rc_current_window_object; repeat hd(num) times rc_draw_blob( random(600) - 300, random(600) - 300, random(50), oneof(picture_colours)) endrepeat]] ]; /* -- -- Radio buttons for selecting a colour This part of the panel assumes that you want to select a colour for some purpose. When you choose the colour, a textual window, with that colour as background appears and remains until either you click on it to get rid of it, or you select another colour, in which case the new window replaces the old one. */ ;;; Use this to record previous message put up, in case it needs to be ;;; removed vars last_colour_message = false; define show_selected_colour(button); ;;; When a radio button has been selected with a colour name, put ;;; up a message using the colour as the background. lvars label = rc_button_label(button), strings = ['The selected item is now' ^label '' 'Thank you']; if last_colour_message then rc_kill_window_object(last_colour_message); false -> last_colour_message; endif; rc_message(300,300, strings, 0, true, '10x20', label, 'black') -> last_colour_message; enddefine; vars colour_field_instructions = [TEXT : 'Some radio buttons' 'Select one only' ], colour_field_radio_buttons = [RADIO ;;; a field label for these buttons {label radio_buttons} {gap 2} {spacing 2} {width 70}{height 20} {margin 2} {textbg 'yellow'}{textfg 'blue'} {chosenbg 'ivory'} {fieldbg 'red'} {select show_selected_colour} {cols 4} : 'yellow' 'pink' 'orange' 'gold' 'ivory' 'grey' 'green' 'blue' ]; /* -- A slider field with two sliders, one with a reactor */ ;;; Define a demonstration reactor to be used in sliders ;;; If a slider has this reactor all its changes will trigger ;;; the reactor define slider_reactor(slider,val); if vedediting then vededit('Reactor_out', vedhelpdefaults); vedendfile(); dlocal cucharout = vedcharinsert; endif; ['Slider' %rc_informant_ident(slider)% 'now set to:' ^val]==> enddefine; vars slider1, slider2; ;;; identifiers changed by the sliders vars slider_fields = [SLIDERS {gap 2} {margin 4} {fieldbg 'grey90'} {width 360} {height 25} ;;; We need a label for the field, so that these sliders ;;; can be accessed by programs to interrogate or set ;;; their values {label slider} ;;; Uncomment this to disable the text input panels on sliders ;;; {textin ^false} ;;; Make sliders have white background to show true colour {barcol 'white'} {blobcol 'red'} ;;; Specify font for labels {labelfont '6x13bold'} {spacing 8} {radius 6} : [slider1 ;;; range and default ensure integer value {-500 500 0} round ;;; labels at ends of slider 1. [[{-5 10 'MIN(-500) Slider1'}] [{-50 10'MAX(500)'}]] ;;; give it an individual label to make accessing easy {itemlabel S1} ] ;;; The next slider has range 0 to 10 but does not round values ;;; its default value is 5, and changes are allowed only in ;;; 0.25 steps. Consequently the value will always be a decimal [slider2 {0 10 5 0.25} noround ;;; labels on left and right ['10x20' ;;; override default labelfont [{-5 10 'Lo Slider2'}] [{-15 10 'Hi'}]] {reactor slider_reactor itemlabel S2} ] ]; /* -- An action field with some counters, toggles, etc. ------------------ ;;; after building the panel, click on the counter and ;;; toggle buttons and check these variables num_val => test_flag => */ vars num_val = 0, test_flag = false; vars button_action_fields = [ACTIONS {gap 2} {margin 2} {spacing 4} {width 110} {height 20} {align left}: ;;; A counter button, with num_val constrained to be >= 0 {counter 'Counter' ^(ident num_val) {5 0} {itemlabel Counter1}} ;;; A toggle button controlling the variable test_flag {toggle 'Switch flag' ^(ident test_flag) {itemlabel Toggle1}} ]; /* -- A dials field with three identifiers, one with a reactor ----------- See TEACH rc_dial for more information. */ ;;; some variables to be associated with three dials. vars dial1, dial2, dial3; ;;; A reactor procedure to report movement of dials define dial_reactor(dial, val); if vedediting then vededit('Reactor_out', vedhelpdefaults); vedendfile(); dlocal cucharout = vedcharinsert; endif; ['Dial changed:' ^(rc_informant_ident(dial)) 'New value:' ^val] ==> enddefine; vars dial_fields = [DIALS {label threedials} {fieldbg 'grey95'}{spacing 5}{fieldheight 40} {dialwidth 90} {dialheight 100} {dialbase 30} {margin 4} {offset 60}{gap 3}: [dial1 0 0 180 180 {0 10 5 1} 40 15 'yellow' 'blue' [MARKS ;;; {extra radius, angular gap, mark width, length, colour} {5 18 2 8 'blue'} {8 90 2 10 'black'}] [LABELS ;;; {extra radius, angular gap, initial value, increment, ;;; colour font} {44 18 180 -18 'red' '6x13'} {20 18 0 1 'blue' '6x13'}] [CAPTIONS ;;; {relative location, string, colour, font} {-100 20 'Degrees shown in red' 'red' '9x15'} {-80 40 'Values in blue' 'blue' '10x20'}] {itemlabel D1} ] ;;; Offset 60 units to right of default location. ;;; This dial has a reactor [dial2 60 -40 -90.0 180 {0 50 25 1} 40 15 ^false ^false [LABELS {15 36 0.0 10 'blue' '6x13'}] {reactor dial_reactor itemlabel D2} ] [dial3 -10 -40 90 180 {0 50 40 1} 40 15 ^false ^false [LABELS {15 18 0 5 'blue' '6x13'}] {itemlabel D3} ] ]; /* -- Combine all the field specifications to form the field_specs list */ vars field_specs = [ ^^panel_specs ^panel_header ^map_strings ^map_types ^plan_action_strings ^plan_action_buttons ^scratchpad_strings ^scratchpad_buttons ^colour_field_instructions ^colour_field_radio_buttons ^slider_fields ^dial_fields ^button_action_fields ;;; Finaly a field containing a single button to kill ;;; the window, offset a long way to the right. ;;; and raised into the previous field, using a negative ;;; gap [ACTIONS {align right} {width 85} {height 25} {gap -25} : ;;; Change defaults for this blob button {blob 'KILL' rc_kill_panel {^rc_button_font '10x20' ^rc_button_stringcolour 'white' ^rc_button_bordercolour 'red' ^rc_button_labelground 'brown' ^rc_button_blobcolour 'ivory' } } {blob 'REDRAW' rc_redraw_this_panel {^rc_button_font '10x20' ^rc_button_stringcolour 'white' ^rc_button_bordercolour 'red' ^rc_button_labelground 'brown' ^rc_button_blobcolour 'ivory' } } ] ]; /* -- Tracing the field construction ------------------------------------- You can get more information on the construction process if you do this trace rc_list_to_field; then rebuild the panel. Warning: there's a lot of trace printing, and it needs to be studied carefully if you want to see what goes into each field. vars panel = rc_control_panel(400, 10, field_specs, 'Demo Panel'); Turn it off with untrace rc_list_to_field; */ /* -- Some exercises ----------------------------------------------------- 1. Try adding a button which draws without assigning the scratchpad to be the current window. It will then draw in the gap at the top of the panel. 2. Make it use the selected colour in the radio button field to choose the colour with which to draw. 3. Find a way to use the two sliders (or add more) to control the drawing parameters. For examples showing how to access slider values see TEACH RCLIB_DEMO.P/rc_control_panel */ /* -- See also ----------------------------------------------------------- HELP * RCLIB TEACH * RCLIB_DEMO.P HELP * RC_CONTROL_PANEL LIB * RC_POLYPANEL.P This describes a more complex working example of a control panel TEACH * RC_CONSTRAINED_PANEL This shows how to impose constraints on user editable components of panels, and how to use reactors to set up linkages between components of panels, e.g. between a number input field and a slider. HELP RCLIB_COMPATIBILITY */ /* -- Revision notes See HELP * RCLIB_NEWS for changes -- . 10 Aug 2002 New option to make panel re-sizable {resize true} Added [REDRAW] button. Allowed individual labels for sliders, dials, counter buttons, toggle buttons. More facilities for accessing fields of panels, especially via labels. -- . August 2000 New [DIALS ...] field type See TEACH RC_DIAL --- $poplocal/local/rclib/teach/rc_control_panel --- Copyright University of Birmingham 2002. All rights reserved. */