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:
 

melbpc_logo: layout [image http://www.melbpc.org.au/pict/mpclogox.gif]
view melbpc_logo

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:

rebol -s rebname1.r

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

[ About Melbourne PC User Group ]