The magazine of the Melbourne PC User Group
Implementing a GUI with REBOL/View
Trevor Gosbell |
|
|
In an overview of REBOL last month, Trevor Gosbell showed us some of the
capabilities of the REBOL/Core package. This month he takes us on a quick tour
of the graphical version — REBOL/View... |
The main addition to the basic functionality of REBOL/Core is the inclusion of a
graphics engine that enables the programmer to produce a Graphical User
Interface (GUI) in REBOL. The graphics engine provides an array of GUI widgets
and the capacity for some nifty effects. A standard feature of REBOL/View is the
Visual Interface Dialect (VID), which gives the programmer easy access to the
graphics engine.
Face Up To Some Jargon
In VID, standard GUI widgets such as buttons, labels, and images are called
faces. Attributes of faces such as text, colour, size and actions are called
facets. Each face may have one or many facets, or none. Specific keywords set
the general layout attributes. All of this information is wrapped up in a block
and fed into the layout function.
For example, my previous article included a one-line script that loads the Melb
PC logo into a window:
|
view layout [image http://www.melbpc.org.au/pict/mpclogox.gif] |
The view function is responsible for the actual screen display, in this case
taking input from the layout function. Everything inside the layout block is VID
code. The image face displays an image, and in this example it has one facet -
the URL, http://www.melbpc.org.au/pict/mpclogox.gif.
The result (output) of the layout function can also be stored in a variable
instead of being passed straight into view, so that the following operates
identically to the previous example:
A Basic Interface Design
In the previous article we covered how to write a REBOL file renamer that
operated from the command line. Let's now put that basic functionality behind
the GUI interface shown in Figure 1.
What Is This Interface Going To Do?
- display the current working folder across the top
- display available files/folders in the left-hand scroll pane
- update current directory and file list when a new folder is chosen
- accept user input in search and replace boxes
- trigger the search-and-replace operation using the Go button
- display results of the search-and-replace in the right-hand scroll panel
- terminate the program using the Quit button
The first task is to describe the interface itself. Type the code Listing 1 into
a text editor (without the reference numbers!) and save it as rebname1.r or if
you prefer, download it from http://member.melbpc.org.au/~tgosbell/.
It can also be downloaded from this Link.
This Is What Is Happening
(1)
The REBOL header. To save space I've kept the REBOL header to a minimum -
just some text for the title bar of the window.
(2)
Set a variable my_path to the startup folder. Retrieve a copy of the
startup folder from the system variable
system/options/path.
(3)
Initialise some variables. The variables
files_data
and feedback_data
are
used as facets providing data to the
text-lists list_files
and
list_feedback
respectively. Both are initialised to empty blocks.
(4)
Set
files_data to hold a list of files in
the current working folder. First use the change-dir function to
make the current working folder the same as the folder stored in
my_path. Now if the current working
folder has a parent folder (that is, if the folder ../ exists) then add ../
to
files_data. Finally, append a sorted
list of files in the current working folder to the
files_data.
|
|
Listing 1
REBOL [Title: "REBOL Renamer"] (1)
my_path: system/options/path (2)
files_data: []
feedback_data: [] (3)
change-dir my_path
if exists? %../ [append files_data %../]
append files_data sort read my_path (4)
view layout [
across (5)
label "Current Folder:" (6)
lab_path: label to-string my_path (7)(8)
return (9)
list_files: text-list data files_data (10)
list_feedback: text-list data feedback_data
return
label "Search pattern:"
fld_search: field (11)
return
label "Replace pattern:"
fld_replace: field
return
button "Go" (12)
button "Quit" [quit] (13)
]
|
(5)
Change layout direction. By default, REBOL/View adds each new face vertically
below the previous one. Using the across keyword changes the layout so that new
faces are added horizontally, to the right of the previous.
(6)
Add a label face. A label face simply displays some text. (7)
Create a variable lab_path to hold a face. In the next version, we will need to
refer to the value of some faces by name, so we can assign variable names to
them now. Notice the other faces that have
variable names: list_files, list_feedback, fld_search, and fld_replace.
(8)
Display the data. Use my_path as a facet for the lab_path text label. The to-string function converts the file value in my_path into a string suitable for
display.
(9)
Make a "line break". Think of the return keyword as similar in function to the
return key on the keyboard. In this case it makes a new row and moves the
insertion point to the left - the next face will sit at the left of a new row.
But if the layout direction is set to vertical, return makes a new column and
moves the insertion point to the top.
Try an experiment:
Change the across keyword to below and see what happens.
What happens when you take out one or two returns?
(10)
Add a text-list face. The text-list is a scrolling text panel.
(11)
Enable some user input. The user can type text into input fields.
(12)
And it wouldn't be a GUI without a button or two.
(13)
Slip in some interactivity. We will come to full interactivity in the next
version, but this one is so simple we might as well do it now. Adding a facet
including the command quit in a block adds this action to the click action of
the button. Now when you click the Quit button, the window closes and the script
terminates.
How Do You Run The Program?
The code in this article requires REBOL/View to run. Download from http://www.rebol.com/view-platforms.html.
There are a couple of options. Try double-clicking on the rebname1.r filename in
Windows Explorer and if REBOL/View is "associated" with the .r extension it will
start the script in REBOL automatically. Otherwise open a command (DOS) Window,
change to the folder where you saved rebname1.r and enter:
All being well, the screen should show a display similar to Figure 1.
I think you'll agree that this is a very quick and easy GUI to make. Of course,
there's a catch - it doesn't do much, so let's add some interactivity.

Figure 1.
|  Figure 2.
|
Now Make It Interactive
That's about it for the purely VID part of the example, the rest is ordinary REBOL code used to provide actions to some of the faces. Actions are blocks of REBOL code added as a facet to a face, and they are triggered whenever the
default action of that face is triggered - usually a mouse click. I have already
shown this in the first version above where the action block for the Quit button
is [quit]. To avoid cluttering the VID layout code, I prefer to put as much
action code as possible into separate functions.
A Quick Look at Functions in Rebol
In their simplest form, functions are declared in REBOL according to the following template:
function-name: func [specification
block] [code block] |
The specification lists the arguments to the function. You may remember this
next example from the previous article:
| add-up: func [this that] [this + that] |
Function arguments may also be specified with a data type:
| add-up: func [this [number!] that [number!]] [this + that] |
When a data type is declared, the function will attempt to execute the code
block only if the right types of arguments are passed to it. In the last example
the arguments must be numbers.
Experiment again:
Type the last example in at the REBOL command line then try add-up 5 10 then
add-up "five" "ten". Do they both work?
The Final Revision
The final working version is included in Listing 2 - a copy of this text may be downloaded from this Link. Note that it is considerably
longer than the first version because we have added several new functions to
provide the actions.
This Is What Happens in Listing 2
(1)
New function, old code. The lines of code to change current working folder and
set a value for files_data has been made into a function. This will be called
every time the user changes folders. Note: it is acceptable to have an empty
function specification block.
(2)
A kludge. Incredibly the text-list face does not update when its data source is
changed. This function fiddles with some of the innards of the text-list
declaration to reset it. I borrowed this function from http://www.codeconscious.com/rebol/vid-notes.html.
(3)
When the user clicks on the file list... If the user has clicked on a folder
name this function changes to that folder and updates the display. Note the call to
update-text-list to clear the file list.
(4)
You may recognise this function. The code here was lifted from the example in
the previous article, with a few modifications. It doesn't refer to the command
line arguments anymore, it uses the text from fld_search and fld_replace.
(5)
Make everything happen. When the user clicks the Go button, the rename takes
place and file and feedback boxes are updated.
(6)
Initialisation. This function call initialises the file list data before the GUI
is created.
(7)
Two extra facets. Because file paths can vary in length, I have allocated some
extra space with a pre-set size for this label using a tuple 300x50 (that is width x height). The facet top ensures that text is always vertically aligned to
the top in the space allocated.
(8)
Click in the file list. This triggers a call to do-change-folder. (9)
Click the Go button. This triggers a call to do-go-action.
Save it as rebname2.r Run it with a double-click on the rebname2.r or type the
command rebol -s rebname1.r at the DOS command line. Hopefully it will look like
the program in action in Figure 2. |
|
Listing 2.
REBOL [Title: "REBOL Renamer"]
my_path: copy system/options/path
files_data: []
feedback_data: []
do-update-files-data: func [] (1)
[
change-dir my_path
if exists? %../ [append files_data %../]
append files_data sort read my_path
]
update-text-list: func [current_list [object!]] (2)
[
current_list/sld/data: 0
current_list/sn: 0
current_list/sld/redrag current_list/lc /
max 1 length? head current_list/lines
show current_list
]
do-change-folder: func [selected [file!]] (3)
[
test_selection: to-file rejoin
[my_path selected]
if dir? test_selection
[
my_path: copy test_selection
lab_path/text: clean-path my_path
show lab_path
clear files_data
do-update-files-data
update-text-list list_files
]
]
do-rename: func [] (4)
[
messages: copy []
foreach file read my_path
[
if not dir? file
[
if not none? find file fld_search/text
[
new-file: to-file replace to-string file
fld_search/text fld_replace/text
either exists? new-file
[
append messages rejoin ["FILENAME "
new-file " ALREADY IN USE"]
]
[
append messages rejoin ["Renamed
" file " to " new-file]
rename file new-file
]
]
]
]
return messages
]
do-go-action: func [] (5)
[
clear feedback_data
insert feedback_data sort do-rename
update-text-list list_feedback
clear files_data
do-update-files-data
update-text-list list_files
]
do-update-files-data (6)
view layout [
across
label "Current Folder:"
lab_path: label to-string my_path 300x50 top (7)
return
list_files: text-list data files_data
[do-change-folder value] (8)
list_feedback: text-list data feedback_data
return
label "Search pattern:"
fld_search: field
return
label "Replace pattern:"
fld_replace: field
return
btn_go: button "Go" [do-go-action] (9)
btn_quit: button "Quit" [quit]
]
|
Acknowledgements
Thanks to Brett at codeconscious.com for his help with the update-text-list
function. About the Author
Trevor Gosbell has been programming for about 20 years and his wife thinks it's
about time he stopped and mowed the lawn.
Reprinted from the June 2003 issue of PC Update, the magazine of Melbourne PC User Group, Australia
|