REBOL

Learn REBOL

By: Nick Antonaccio
Updated: 7-22-2010
Go to http://rebolforum.com to ask questions.
All of the examples in this text are available at http://re-bol.com/examples.txt
Be sure to see the 68 YouTube video tutorials that cover this material (10 hours of video).
A quick game programming tutorial is available here.
A simple introductory tutorial application for children is also available.

Contents:

1. Introducing REBOL
2. How This Tutorial Is Organized
3. Getting Started: Downloading and Installing REBOL, Hello World
4. An Amazingly Tiny Demo and Some Simple Examples
4.1 Opening REBOL Directly to the Console
5. Some Perspective for Absolute Beginners
6. A Quick Summary of the REBOL Language
6.1 Built-In Functions and Basic Syntax
6.2 More Basics: Word Assignment, I/O, Files, Built-In Data Types and Native Protocols
6.3 GUIs (Program Windows)
6.4 Blocks, Series, and Strings
6.5 Conditions
6.6 Loops
6.7 User Defined Functions and Imported Code
6.8 Quick Review and Synopsis
6.9 A Telling Comparison
7. More Essential Topics
7.1 Built-In Help and Online Resources
7.2 Saving and Running REBOL Scripts
7.3 "Compiling" REBOL Programs - Distributing Packaged .EXE Files
7.4 Embedding Binary Resources and Using REBOL's Built In Compression
7.5 Running Command Line Applications
7.6 Responding to Special Events in a GUI - "Feel"
7.7 Common REBOL Errors, and How to Fix Them
8. EXAMPLE PROGRAMS - Learning How All The Pieces Fit Together
8.1 Little Email Client
8.2 Simple Web Page Editor
8.3 Card File
8.4 Little Menu Example
8.5 Loops and Conditions - A Simple Data Storage App
8.6 FTP Chat Room
8.7 Image Effector
8.8 Guitar Chord Diagram Maker
8.9 Shoot-Em-Up Video Game
8.10 Blogger
8.11 Listview Multi Column Data Grid Example
8.12 Thumbnail Maker
9. Additional Topics
9.1 Objects
9.2 Ports
9.3 Parse (REBOL's Answer to Regular Expressions)
9.4 2D Drawing, Graphics, and Animation
9.5 Using Animated GIF Images
9.6 3D Graphics with r3D
9.7 Multitasking
9.8 Using DLLs and Shared Code Files in REBOL
9.9 Web Programming and the CGI Interface
9.10 WAP - Cell Phone Browser CGI Apps
9.11 REBOL as a Browser Plugin
9.12 Using Databases
9.13 Menus
9.14 Multi Column GUI Text Lists (Data Grids)
9.15 RebGUI
9.16 Creating PDF files using pdf-maker.r
9.17 Creating .swf Files with REBOL/Flash
9.18 Rebcode
9.19 Useful REBOL Tools
9.20 6 REBOL Flavors
9.21 Bindology, Dialects, Metaprogramming and Other Advanced Topics
10. REAL WORLD CASE STUDIES - Learning To Think In Code
10.1 Case 1 - Scheduling Teachers
10.2 Case 2 - A Simple Image Gallery CGI Program
10.3 Case 3 - Days Between Two Dates Calculator
10.4 Case 4 - Simple Search
10.5 Case 5 - A Simple Calculator Application
10.6 Case 6 - A Backup Music Generator (Chord Accompaniment Player)
10.7 Case 7 - FTP Tool
10.8 Case 8 - Jeopardy
10.9 Case 9 - Creating a Tetris Game Clone
10.10 Case 10 - Scheduling Teachers, Part Two
10.11 Case 11 - An Online Member Page CGI Program
10.12 Case 12 - A CGI Event Calendar
10.13 Case 13 - Ski Game, Snake Game, and Space Invaders Shootup
10.14 Case 14 - Media Player (Wave/Mp3 Jukebox)
10.15 Case 15 - Creating the REBOL "Demo"
10.16 Case 16 - Guitar Chord Chart Printer
10.17 Case 17 - Web Site Content Management System (CMS), Sitebuilder.cgi
10.18 Case 18 - A GUI Playing Card Framework (Creating a Freecell Clone)
10.19 Case 19 - Downloading Directories - A Server Spidering App
10.20 Case 20 - Vegetable Gardening
10.21 Case 21 - An Additional Teacher Automation Project
11. Other Scripts
12. Learning More About REBOL - IMPORTANT DOCUMENTATION LINKS
13. Beyond REBOL
14. Appendix 1: A REBOL Song

1. Introducing REBOL

What is REBOL? Why use it?

  • REBOL is a uniquely small and productive development tool that can be used to create powerful desktop software, dynamic CGI web site and server applications, rich distributed browser plugin applications, mobile apps, and more. REBOL's blend of capability, compact size, ease of use, cross-platform functionality, and variety of interpreter platforms enable it to gracefully replace many common tools such as Java, Python, Visual Basic, C, C++, PHP, Perl, Ruby, Javascript, toolkits such as wxWidgets, graphic/multimedia platforms such as Flash, DBMSs such as Access, MySQL, and SQLite, a variety of system utilities, and more, all with one simple paradigm. Despite its broad usefulness, REBOL is far easier to implement than any other comparable tool.
  • REBOL is ultra compact. Its uncompressed file size is about 1/2 Meg on most platforms. It can be downloaded, installed, and put to use on all supported operating systems in less than a minute, even over a slow dialup connection.
  • REBOL can also be used immediately, without installation, on over 40 operating systems as a lightweight file manager, text editor, calculator, database manager, email client, ftp client, news reader, image viewer/editor, OS shell, and more. You can use it as a simple utility program with a familiar interface to common computing activities, on just about any computer, even if you're unfamiliar with the operating system.
  • REBOL includes GUI, network, graphics, sound, database, image manipulation, math, parsing, compression, CGI decoding, secure network services, text editing, and other functions built-in. No external modules, tool kits, or IDEs are required for any essential functionality.
  • REBOL is easy enough for absolute beginners and average computer users to operate immediately, but powerful and rich enough for a wide variety of complex professional work.
  • REBOL has useful built-in help for all available functions and language constructs.
  • REBOL is supported by a friendly and knowledgeable community of active developers around the world.
  • REBOL is available in both free and supported commercial versions. The free version can be used to create commercial applications, with very few license restrictions. Part of the REBOL language is open source, and that code is available directly in the interpreter. The closed components are kept in an escrow account, in case the Rebol Technologies company ever goes out of business (source code escrow licenses are available for those who use it in critical work).
  • REBOL has a facility for ultra fast performance using "Rebcode", which can be optimized like assembly language, but works the same way across all supported hardware and operating systems (using the exact same code).
  • REBOL was created by Carl Sassenrath, who developed the Amiga operating system executive in 1985 (the first preemptive multitasking OS kernel for personal computers). REBOL has been in commercial use since its first release in 1998, and a 3rd major release of the language is in active development as of 2009.
  • REBOL is a modern, multi paradigm development tool (procedural, object oriented, and functional), but its unique syntax goes well beyond traditional approaches to computer language design. REBOL code is typically much shorter and more readable than other languages, and REBOL is often far more productive than other development tools (very often, dramatically so). There's absolutely no simpler solution for cross-platform GUI creation, anywhere (the code for a complete program window with a button is simply: view layout [button] ). But that just scratches the surface. REBOL has a striking ability to simplify difficult computing tasks of all types, with straightforward, high level code dialects. No other development tool is as adept at creating practical domain specific languages. On a more basic level, the storage/manipulation/transfer of all data is managed by a single ubiquitous code structure. Arrays, lists, tables, and even sizable databases of mixed text, code, and binary data are all stored using one consistent "block" syntax. Blocks are created simply by surrounding any list of data with square brackets. Like everything else in REBOL, the format is extremely simple, but it enables many powerful features for searching, sorting, comparing, dissecting, evaluating, storing, retrieving, transferring and otherwise manipulating information of all types. Common network protocols and data values are also natively usable in REBOL. You can read and write data directly to/from web servers, email accounts, databases, and more, add/subtract time, date, and other values automatically, manipulate XML, HTML, CSV and other formats natively, display and apply effects to images, play sounds, etc., all without any preparation, complex formatting, or use of any external library code. REBOL has a built in, powerful "parse" dialect which elegantly replaces the need for regular expressions in most cases. The list of such practical features is long, but REBOL is not built from simple gimmicks - it's a deep and powerful tool. Because so many practical computing elements are all built into REBOL, and interact natively, the learning curve required to get real work done is much easier than in other development environments. No other language includes such straightforward and versatile mechanisms for accomplishing the most basic work of computers - managing data of all types.
  • REBOL is small, practical, portable, extremely productive, and different than the typical mess of modern computing tools. It does not rely on a large stack of disparate technologies to accomplish useful computing goals. All it's features exist inside one tiny downloadable executable that anyone can get running, on just about any computer, in less than a minute. It can be used as anything from compact utility application to powerful professional development environment. Even average computer users with absolutely no coding experience can learn to create real, useful and powerful REBOL scripts very quickly.

Here are a few screen shots of examples covered in this tutorial:

Downloadable Windows executables of these programs are available at:

http://musiclessonz.com/rebol_tutorial/examples

2. How This Tutorial Is Organized

There are 5 main parts to this text:

  1. Fundamentals:
    The first sections cover how to use the REBOL interpreter (typing in the console, creating scripts, navigating built-in help, creating .exe's, etc.), basic language constructs and syntax (variables, functions, data types, conditions, loops, i/o, etc.), and the fundamentals of creating GUI program windows (~65 pages).
  2. Examples:
    12 fully documented programs which demonstrate, line by line, how the above fundamentals are put together to form complete applications (~35 pages).
  3. Other Important Topics:
    Graphics, animation, 3D, using databases, accessing DLLs and the OS API, web site CGI programming, writing multitasking code, 3rd party tool kits, parse, objects, ports, and more (~100 pages).
  4. Real World Case Studies:
    21 full case studies covering how a wide variety of complete desktop, network, and web site applications were conceived and created using REBOL. This section demonstrates, step by step, how each of the examples grew from concept to final design using outlines, pseudo code, and detailed finished code. Each example demonstrates a variety of practical REBOL concepts, code patterns, and tools, and helps guide you towards "thinking in REBOL" (~230 pages).
  5. Additional Scripts and Resources:
    More code and resources to help complete your understanding of REBOL and continue learning.

This tutorial is less than 450 pages, yet it covers the REBOL language from the ground up, fully documents the creation of more than 50 applications, and contains many additional short scripts and useful concepts. Links to other online documentation resources are provided to more fully learn many topics, but no third party reference materials are required to understand any example in this text, even if you've never programmed a line of code before. If you're familiar with other programming languages, be prepared to think about coding in ways that are a bit different from your accustomed patterns. You won't find the typical explanations of object oriented programming, modules, pointers, arrays, string management, regular expressions, or other common topics in this text. That's because REBOL's design provides straightforward solutions to reduce or eliminate the need for many complex syntax structures and coding techniques you may know. Every step of the way through this tutorial, you'll pick up practical approaches to easily achieve computing goals of all types. By learning REBOL, you'll learn to get many things done more quickly and easily than you can with any other tool. Enjoy!

3. Getting Started: Downloading and Installing REBOL, Hello World

The REBOL interpreter is a program that runs on your computer. It translates written text in the REBOL language syntax ("source code") to instructions the computer understands. To get the free REBOL interpreter, go to:

http://www.rebol.com/download-view.html

For Microsoft Windows, download the rebview.exe file - just click the link with your mouse and save it to your hard drive. If you want to run REBOL on any other operating system (Macintosh, Linux, etc.), just select, download and run the correct file for your computer. It works the same way on every operating system. You can use the stand-alone versions on just about any desktop machine. Upload the correct interpreter version to your web server and you can also execute REBOL CGI programs directly on your web site. You can also install a plugin version to run full REBOL desktop applications directly on pages in a web browser.

Once you've got the tiny REBOL desktop interpreter downloaded, installed, and running on your computer (Start -> Programs -> REBOL -> REBOL View), click the "Console" icon, and you're ready to start typing in REBOL programs. To run your first example, type the following line into the REBOL interpreter, and then press the [Enter] (return) key on your keyboard:

alert "Hello world!"

Before going any further, give it a try. Download REBOL and type in the code above to see how it works. It's extremely simple and literally takes just a few seconds to install. To benefit from this tutorial, type or paste each code example into the REBOL interpreter to see what happens.

Install Notes:

To enable REBOL/View console support in Ubuntu Linux, follow these instructions:

  1. Download the tar.gz file for Linux x86 - Fedora (Kernel 2.6).
  2. Open with archive manager (default).
  3. Look in the rebol-276 folder (or whatever version you've downloaded).
  4. Select and extract the "rebview" file into Ubuntu's HOME directory (the parent of the Ubuntu folder in the file system, or /home/ubuntu/ at the command line). In some versions, the file name is "rebol" instead of "rebview".
  5. Open a terminal window (Applications -> Accessories -> Terminal)
  6. Type "./rebview" (without the quotes), or "./rebol", depending on the version you've downloaded.

On some versions of Linux, you may need to run "./rebview +i" to install the required libs. For Ubuntu 10.04 and some versions of REBOL, you may need to run the following on the command line: http://re-bol.com/rebol_ubuntu1004.txt.

4. An Amazingly Tiny Demo and Some Simple Examples

Many of the examples programs in this tutorial are available as downloadable Windows executables, at:

http://musiclessonz.com/rebol_tutorial/examples

To whet your appetite, here's an example that demonstrates just how potent REBOL code can be. The following script contains 10 useful programs in LESS THAN HALF A PRINTED PAGE OF CODE:

  1. FREEHAND PAINT: Draw and save graphic images
  2. SNAKE GAME: Eat the food, avoid hitting the walls and yourself
  3. TILE PUZZLE, "15": Arrange the tiles into alphabetical order
  4. CALENDAR: Save and view events for any date
  5. VIDEO: Live webcam video viewer (not just a static image)
  6. IPs: Display your LAN and WAN IP addresses
  7. EMAIL: Read emails from any pop account
  8. DAY CALCULATOR: Count the days between 2 selected dates
  9. PLAY SOUNDS: Browse your computer for wave files to play
  10. FTP TOOL: Web site editor (browse folders on your web server, click files to edit and save changes back to your server, create and edit new files, etc.)

This example is 100% native REBOL code. No external libraries, images, GUI components, or other resources of any kind are imported or called. It runs on Windows, Mac, Linux, and any other OS supported by REBOL/View. To run it, just download the tiny REBOL interpreter and copy/paste the code below into the console (a Windows .exe is also available here):

REBOL[title:"Demo"]p: :append kk: :pick r: :random y: :layout q: 'image
z: :if gg: :to-image v: :length? g: :view k: :center-face ts: :to-string
tu: :to-url sh: :show al: :alert rr: :request-date co: :copy g y[style h
btn 150 h"Paint"[g/new k y[s: area black 650x350 feel[engage: func[f a e][
z a = 'over[p pk: s/effect/draw e/offset sh s]z a = 'up[p pk 'line]]]
effect[draw[line]]b: btn"Save"[save/png %a.png gg s al"Saved 'a.png'"]btn
"Clear"[s/effect/draw: co[line]sh s]]]h"Game"[u: :reduce x: does[al join{
SCORE: }[v b]unview]s: gg y/tight[btn red 10x10]o: gg y/tight[btn tan
10x10]d: 0x10 w: 0 r/seed now b: u[q o(((r 19x19)* 10)+ 50x50)q s(((r
19x19)* 10)+ 50x50)]g/new k y/tight[c: area 305x305 effect[draw b]rate 15
feel[engage: func[f a e][z a = 'key[d: select u['up 0x-10 'down 0x10 'left
-10x0 'right 10x0]e/key]z a = 'time[z any[b/6/1 < 0 b/6/2 < 0 b/6/1 > 290
b/6/2 > 290][x]z find(at b 7)b/6[x]z within? b/6 b/3 10x10[p b u[q s(last
b)]w: 1 b/3:((r 29x29)* 10)]n: co/part b 5 p n(b/6 + d)for i 7(v b)1[
either(type?(kk b i)= pair!)[p n kk b(i - 3)][p n kk b i]]z w = 1[clear(
back tail n)p n(last b)w: 0]b: co n sh c]]]do[focus c]]]h"Puzzle"[al{
Arrange tiles alphabetically:}g/new k y[origin 0x0 space 0x0 across style
p button 60x60[z not find[0x60 60x0 0x-60 -60x0]face/offset - x/offset[
exit]tp: face/offset face/offset: x/offset x/offset: tp]p"O"p"N"p"M"p"L"
return p"K"p"J"p"I"p"H"return p"G"p"F"p"E"p"D"return p"C"p"B"p"A"x: p
white edge[size: 0]]]h"Calendar"[do bx:[z not(exists? %s)[write %s ""]rq:
rr g/new k y[h5 ts rq aa: area ts select to-block(find/last(to-block read
%s)rq)rq btn"Save"[write/append %s rejoin[rq" {"aa/text"} "]unview do bx]]
]]h"Video"[wl: tu request-text/title/default"URL:"join"http://tinyurl.com"
"/m54ltm"g/new k y[image load wl 640x480 rate 0 feel[engage: func[f a e][
z a = 'time[f/image: load wl show f]]]]]h"IPs"[parse read tu join"http://"
"guitarz.org/ip.cgi"[thru<title>copy my to</title>]i: last parse my none
al ts rejoin["WAN: "i" -- LAN: "read join dns:// read dns://]]h"Email"[
g/new k y[mp: field"pop://user:pass@site.com"btn"Read"[ma: co[]foreach i
read tu mp/text[p ma join i"^/^/^/^/^/^/"editor ma]]]]h"Days"[g/new k y[
btn"Start"[sd: rr]btn"End"[ed: rr db/text: ts(ed - sd)show db]text{Days
Between:}db: field]]h"Sounds"[ps: func[sl][wait 0 rg: load sl wf: 1 sp:
open sound:// insert sp rg wait sp close sp wf: 0]wf: 0 change-dir
%/c/Windows/media do wl:[wv: co[]foreach i read %.[z %.wav = suffix? i[p
wv i]]]g/new k y[ft: text-list data wv[z wf <> 1[z error? try[ps value][al
"Error"close sp wf: 0]]]btn"Dir"[change-dir request-dir do wl ft/data: wv
sh ft]]]h{FTP}[g/new k y[px: field"ftp://user:pass@site.com/folder/"[
either dir? tu va: value[f/data: sort read tu va sh f][editor tu va]]f:
text-list[editor tu join px/text value]btn"?"[al{Type a URL path to browse
(nonexistent files are created). Click files to edit.}]]]]

That's the entire application - all 10 programs. Go ahead, give it a try. Download the REBOL interpreter and copy/paste the code above into the console. It only takes a few seconds. By the end of this tutorial you'll know exactly how all that code works, and much more...

4 - Several Basic Examples

The above example is obfuscated to demonstrate just how malleable and compact REBOL code can be. The following examples represent more typical, readable REBOL code. This first example demonstrates how to create a basic GUI program window (the size info in this example is optional):

view layout [size 500x400]

Here's a program window with a text area and a button:

view layout [area btn "Click Me"]

In this example, the button does something when clicked:

view layout [
    area 
    btn "Click Me" [alert "You can type in the square area."]
]

The following example demonstrates how the button can be made to save any text typed into the area, to a file on the hard drive:

view layout [
    a: area 
    btn "Save" [
        write %reboltut.txt a/text
        alert "Saved"
    ]
]

Here's a little text editor application that builds on the idea above. You can likely get a sense of how it works just by glancing through the code:

view layout [
    h1 "Text Editor:"
    f: field 600 "filename.txt"
    a: area 600x350 
    across 
    btn "Load" [
        f/text: request-file
        show f
        a/text: read to-file f/text
        show a
    ]
    btn "Save" [
        write to-file request-file/save/file f/text a/text
        alert "Saved"
    ]
]

Here's an email client you can use to read and send emails to/from any pop/smtp server:

view layout[
    h1 "Send:"
    btn "Server settings" [
        system/schemes/default/host: request-text/title "SMTP Server:"
        system/schemes/pop/host:     request-text/title "POP Server:"
        system/schemes/default/user: request-text/title "SMTP User Name:"
        system/schemes/default/pass: request-text/title "SMTP Password:"
        system/user/email: to-email request-text/title "Your Email Addr:"
    ]
    a: field "user@website.com"
    s: field "Subject" 
    b: area
    btn "Send"[
        send/subject to-email a/text b/text s/text
        alert "Sent"
    ]
    h1 "Read:" 
    f: field "pop://user:pass@site.com"
    btn "Read" [editor read to-url f/text]
]

As you can see, REBOL is typically very easy to read and write.

4.1 Opening REBOL Directly to the Console

Before typing in or pasting any more code, adjust the following option in the REBOL interpreter: click the "User" menu in the graphic Viewtop that opens by default with REBOL, and uncheck "Open Desktop On Startup". That'll save you the trouble of clicking the "Console" button every time you start REBOL.

5. Some Perspective for Absolute Beginners

This tutorial moves at a pace quick enough to satisfy experienced developers, but because REBOL's learning curve is different from other programming languages, it can also be understood clearly by beginners. If you're reading this text as a novice programmer, it can be helpful to understand a few basic concepts that provide perspective about learning to program. First:

Essentially, all computers do is let users input, store, retrieve, organize, share/transfer, manipulate, alter, view and otherwise deal with data in useful ways.

So, everything you'll do when writing code basically involves manipulating text, numbers, and/or binary data (photos, music, etc.). The fundamental components used to deal with data haven't changed too dramatically in the past few decades. They've simply improved in speed, capacity, and interface. In the current state of modern computing, data is typically input, manipulated, and returned via graphical user interfaces such as program windows, web forms displayed in browsers, and other keyboard/mouse driven "GUI"s. Data is saved on local hard drives and storage devices (CDs, thumb drives, etc.) and on remote web servers, and is typically transferred via local networks and Internet connections. Images, sounds, video, and other types of multimedia data are contained in standardized file formats, and graphic data is displayed using standard mathematical techniques. Knowing how to control those familiar computing elements to allow users to manipulate data, is the goal of learning to program. It doesn't matter whether you're interested in writing business applications to work with inventory and scheduling (text and number data), programs to alter web pages (text and image data), programs to play/edit music (binary data), programs to broadcast video across the Internet (rapidly transferred sequential frames of binary data), programs to control robotic equipment, compute scientific equations, play games, etc... They all require learning to input, manipulate, and return data of some sort. You can do all those things with REBOL, and once you've done it in one language, it's easier to do with other programming tools.

REBOL allows programmers to quickly build graphic interfaces to input and return all common types of data. It can easily manipulate text, graphics, and sounds in useful ways, and it provides simple methods to save, retrieve, and share data across all types of hardware, networks, and the Internet. That makes it a great way to begin learning how to program. By learning REBOL, you'll learn about all the fundamental structures and concepts in programming: variables, functions, data types, conditional operations, loops, objects, etc. You'll also learn about important topics such as user interface design, algorithmic thinking, working with databases, the operating system API, CGI, and more. Those topics all share conceptual and technical similarities, regardless of language, and you'll need to learn to think in those terms to write computer programs, even in a language that's as easy to learn as REBOL. Despite its ease of use, REBOL is an extremely powerful tool. For years it has been used by professionals in enterprise level work around the world. You may never need to learn another programming language.

If you've never done any real "programming" before, the first part of this text may seem a bit technical. Don't be put off. There is no other language with a faster learning curve than REBOL - you'll begin to see the big picture within a few days. Working through this tutorial, you'll gradually build recognition of REBOL language idioms and practical code patterns, by example. The first part of the tutorial will be a whirlwind introduction to many of the fundamental language elements. Just take it all in, and if you really want to learn, be sure to type in, or at least copy/paste, each example into the REBOL interpreter. Reading through the code isn't enough.

6. A Quick Summary of the REBOL Language

6.1 Built-In Functions and Basic Syntax

As with any modern programming language, to use REBOL, you need to learn how to use "functions". Functions are words that perform actions. Function words are followed by data "parameters" (also called "arguments"). Paste these functions into the REBOL interpreter to see how they work:

alert "Alert is a function. THIS TEXT IS ITS PARAMETER."
request "Are you having fun yet?"
editor "Edit this text."
browse http://rebol.com

Some functions don't require any data parameters, but do produce "return" values. Try these functions in the interpreter. They each return a value selected by the user:

request-pass
request-date
request-color
request-file

The return values output by the above functions can be used in your programs to accomplish useful goals. The file name output by the "request-file" function, for example, could be used to determine which data gets opened and manipulated in your program. The data returned by the "request-pass" function can be used to control access to selected data.

Many functions have optional or limited parameters/return values. These options, called "refinements", are specified by the "/" symbol. Try these variations of the "request-pass" function to see how they each perform differently:

request-pass/only
request-pass/user "username"
request-pass/title "The 'title' refinement sets this header text."
request-pass/offset/title 10x100 "'offset' repositions the requester."

Some functions take multiple arguments. The "rejoin" function returns the joined ("concatenated") text arguments inside brackets. Concatenation is very important in all types of programming - you will see this function in use often:

rejoin ["Hello " "there" "!"]

6.1.1 Understanding Return Values and the Order of Evaluation

In REBOL, you can put as many functions as you want on one line, and they are all evaluated strictly from left to right. Functions are grouped together automatically with their required data parameter(s). The following line contains two alert functions:

alert "First function" alert "Second function"

Rebol knows to look for one parameter after the first alert function, so it uses the next piece of data on that line as the argument for that function. Next on the line, the interpreter comes across another alert function, and uses the following text as it's data parameter.

In the following line, the first function "request-pass/offset/title" requires two parameters, so REBOL uses the next two items on the line ("10x100" and "title") as its arguments. After that's complete, the interpreter comes across another "alert" function, and uses the following text, "Processing", as its argument:

request-pass/offset/title 10x100 "title" alert "Processing"

IMPORTANT: In REBOL, the return values (output) from one function can be used directly as the arguments (input) for other functions. Everything is simply evaluated from left to right. In the line below, the "alert" function takes the next thing on the line as it's input parameter, which in this case is not a piece of data, but a function which returns some data (the concatenated text returned by the "rejoin" function):

alert rejoin ["Hello " "there" "!"]

To say it another way, the value returned above by the "rejoin" function is passed to (used as a parameter by) the "alert" function. Parentheses can be used to clarify which expressions are evaluated and passed as parameters to other functions. The parenthesized line below is treated by the REBOL interpreter exactly the same as the line above - it just lets you see more clearly what data the "alert" function puts on screen:

alert ( rejoin ["Hello " "there" "!"] )

Perhaps the hardest part of getting started with REBOL is understanding the order in which functions are evaluated. The process can appear to work backwords at times. In the example below, the "editor" function takes the next thing on the line as it's input parameter, and edits that text. In order for the editor function to begin its editing operation, however, it needs a text value to be returned from the "request-text" function. The first thing the user sees when this line runs, therefore, is the text requester. That appears backwards, compared to the way it's written:

editor (request-text)

Always remember that lines of REBOL code are evaluated from left to right. If you use the return value of one function as the argument for another function, the execution of the whole line will be held up until the necessary return value is processed.

Any number of functions can be written on a single line, with return values cascaded from one function to the next:

alert ( rejoin ( ["You chose: " ( request "Choose one:" ) ] ) )

The line above is typical of common REBOL language syntax. There are three functions: "alert", "rejoin", and "request". In order for the first alert function to complete, it needs a return value from "rejoin", which in turn needs a return value from the "request" function. The first thing the user sees, therefore, is the request function. After the user responds to the request, the selected response is rejoined with the text "You chose: ", and the joined text is displayed as an alert message. Think of it as reading "display (the following text joined together ("you chose" (an answer selected by the user))). To complete the line, the user must first answer the question.

To learn REBOL, it's essential to first memorize and recognize REBOL's many built-in function words, along with the parameters they accept as input, and the values which they return as output. When you get used to reading lines of code as functions, arguments, and return values, read from left to right, the language will quickly begin to make sense.

It should be noted that in REBOL, math expressions are evaluated from left to right like all other functions. There is no "order of precedence", as in other languages (i.e., multiplication doesn't automatically get computed before addition). To force a specific order of evaluation, enclose the functions in parentheses:

print  (10  +  12) /  2      ;  22 / 2 = 11  (same as without parentheses)
print   10  + (12  /  2)     ;  10 + 6 = 16

REBOL's left to right evaluation is simple and consistent. Parentheses can be used to clarify the flow of code, if ever there's confusion.

6.1.2 White Space

Unlike other languages, REBOL does not require any line terminators between expressions (functions, parameters, etc.), and you can insert empty white space (tabs, spaces, newlines, etc.) as desired into code. Text after a semicolon and before a new line is treated as a comment (ignored entirely by the interpreter). The code below works exactly the same as the previous example. Notice that tabs are used to indent the block of code inside the square brackets and that the contents of the brackets are spread across multiple lines. This helps group the code together visually, but it's not required:

alert rejoin [
    "You chose: "               ; 1st piece of joined data
    (request "Choose one:")     ; 2nd piece of joined data
]

ONE CAVEAT: parameters for most functions should begin on the same line as the function word. The following example will not work properly because the rejoin arguments' opening brackets need to be on the same line as the rejoin function:

alert rejoin                    ; This does NOT work. 
[                               ; Put this bracket on the line above.
    "You chose: "
    (request "Choose one:")
]

If you want to comment out a large section of code, simply surround it with curly braces:

{
    This line doesn't do anything.
    This line also does nothing.
    This line is ignored too.
}

6.2 More Basics: Word Assignment, I/O, Files, Built-In Data Types and Native Protocols

In REBOL, the colon (":") symbol is used to assign word labels ("variables") to values:

person: "John"

Now, the word label "person" can be used anywhere (without the colon), to represent the text "John". Notice that the variable "person" has been rejoined with some other text below:

alert rejoin ["The person's name is " person]

Word labels are NOT case sensitive:

alert person
alert PERSON
alert PeRsOn

; to the REBOL interpreter, all three lines above are the same

In this next example, the word "filename" is assigned to the value returned by the request-file function (a file chosen by the user):

filename: request-file

Now, the label "filename" can be used to represent the file selected above:

alert rejoin ["You chose " filename]

REBOL is a bit different from other programming languages in that word labels can be assigned to anything: numbers, text strings, binary data, arrays, lists, hash tables, functions, and even executable blocks of code. At this point, just be aware that when you see the colon symbol, a word label is being assigned to some value.

The "ask" function is a simple way to get some text data from a user at the interpreter's command line (similar to "request-text", but without using a pop-up requester):

ask "What is your name? "

In the example below, the variable "name" is assigned to the text returned by the ask function (i.e., entered by the user). Again, the parentheses are not required - they're just there to clarify the grouping together of the 'ask' function with its text argument:

name: (ask "What is your name? ")

Now you can use the variable word "name" to represent whatever text the user typed in response to the above question.

The "print" function is a simple way to display text data at the interpreter's command line:

print rejoin ["Good to meet you " name]

The "prin" function prints consecutive text elements right next to each other (not on consecutive lines):

prin "All " prin "on " prin "one " print "line." print "On another."

Multi-line formatted text is enclosed in curly braces ("{}"), instead of quotes:

print {
    Line 1
    Line 2
    Line 3
}

Quotes and curly braces can be used interchangeably on a single line:

print {"text"}
print "{text}"

You can print a carriage return using the word "newline" or the characters ^/

print rejoin ["This text if followed by a carriage return." newline]
print "This text if followed by a carriage return.^/"

Clear the screen using "newpage":

prin newpage

The "write" function saves data to a file. It takes two parameters: a file name to write to, and some data to write to that file.

write %/C/YOURNAME.txt name

NOTE: in REBOL, the percent character ("%") is used to represent local files. Because REBOL can be used on many operating systems, and because those operating systems all use different syntax to refer to drives, paths, etc., REBOL uses the universal format: %/drive/path/path/.../file.ext . For example, "%/c/windows/notepad.exe" refers to "C:\Windows\Notepad.exe" in Windows. REBOL converts that syntax to the appropriate operating system format, so that your code can be written once and used on every operating system, without alteration. The following 2 functions convert REBOL file format to your operating system's format, and visa versa:

to-local-file %/C/YOURNAME.txt
to-rebol-file "C:\YOURNAME.txt"

You can write data to a web site (or any other connected protocol) using the exact same write syntax that is used to write to a file (be sure to use an appropriate username and password for your web site ftp account):

write ftp://user:pass@website.com/name.txt name

The "read" function reads data from a file:

print (read %/C/YOURNAME.txt)

REBOL has a built-in text editor that can also read, write, and manipulate text data:

editor %/c/YOURNAME.txt

You can read data straight from a web server, an ftp account, an email account, etc. using the same format. Many Internet protocols are built right into the REBOL interpreter. They're understood natively, and REBOL knows exactly how to connect to them without any preparation by the programmer:

editor http://rebol.com                      ; Reads the content of the
                                             ;   document at this URL.
editor pop://user:pass@website.com           ; Reads all emails in this
                                             ;   POP inbox.
editor clipboard://                          ; Reads data that has
                                             ;   been copied/pasted to
                                             ;   the OS clipboard.
print read dns://msn.com                     ; Displays the DNS info
                                             ;   for this address.
print read nntp://public.teranews.com        ; (Hit the [ESC] key to stop
                                             ;   this Usenet listing.)

; NOTE: The editor reads, AND allows you to SAVE EDITS back to the server:

editor ftp://user:pass@website.com/public_html/index.html

Transferring data between devices connected by any supported protocol is easy - just read and write:

; read data from a web site, and paste it into the local clipboard:

write clipboard:// (read http://rebol.com)   ; afterward, try pasting into
                                             ; your favorite text editor

; read a page from one web site, and write it to another:

write ftp://user:pass@website2.com (read http://website1.com) 

; again, notice that the "write" function takes TWO parameters

Sending email is just as easy, using a similar syntax:

send user@website.com "Hello"
send user@website.com (read %file.txt)       ; sends an email, with
                                             ; file.txt as the body

The "/binary" modifier is used to read or write binary (non-text) data. You'll use read/binary and write/binary to read and write images, sounds, videos and other non-text files:

write/binary %/c/bay.jpg read/binary http://rebol.com/view/bay.jpg

For clarification, remember that the write function takes two parameters. The first parameter above is "%/c/bay.jpg". The second parameter is the binary data read from http://rebol.com/view/bay.jpg:

write/binary (%/c/bay.jpg) (read/binary http://rebol.com/view/bay.jpg)

The "load" and "save" functions also read and write data, but in the process, automatically format certain data types for use in REBOL. Try this:

; assign the word "picture" to the image "load"ed from a given URL:

picture: load http://rebol.com/view/bay.jpg

; save the image to a given file name, and automatically convert it
; to .png format;

save/png %/c/picture.png picture

; show it in a GUI window (much more about this in the next section):

view layout [image load %/c/picture.png]

"Load" and "save" are used to conveniently manage certain types of data in formats directly usable by REBOL (images, sounds, DLLs, certain native data structures, etc. can be loaded and used immediately). You'll use "read" and "write" more commonly to store and retrieve typical types of data, exactly byte for byte, to/from a storage medium, when no conversion or formatting is necessary.

REBOL automatically knows how to perform appropriate computations on times, dates, IP addresses, coordinate values, and other common types of data:

print 3:30am + 00:07:19                   ; increment time values properly
print now                                 ; print current date and time
print now + 0:0:30                        ; print 30 seconds from now
print now - 10                            ; print 10 days ago
print 23x54 + 19x31                       ; easily add coordinate pairs
print 192.168.1.1 + 000.000.000.37        ; easily increment ip addresses
view layout [image picture effect [flip]] ; apply effects to image types

REBOL also natively understands how to use URLs, email addresses, files/directories, money values, tuples, hash tables, sounds, and other common values in expected ways, simply by the way the data is formatted. You don't need to declare, define, or otherwise prepare such types of data as in other languages - just use them.

To determine the type of any value, use the "type?" function:

some-text:  "This is a string of text"    ; strings of text go between
type? some-text                           ; "quotes" or {curly braces}

an-integer: 3874904                       ; integer values are just pos-
type? an-integer                          ; itive/negative whole numbers

a-decimal: 7348.39                        ; decimal numbers are recognized
type? a-decimal                           ; by the decimal point

web-site: http://musiclessonz.com         ; URLs are recognized by the
type? web-site                            ; http://, ftp://, etc.

email-address: user@website.com           ; email values are in the
type? email-address                       ; format user@somewebsite.domain

the-file: %/c/myfile.txt                  ; files are preceded by the %
type? the-file                            ; character

bill-amount: $343.56                      ; money is preceded by the $
type? bill-amount                         ; symbol

html-tag: <br>                            ; tags are places between <>
type? html-tag                            ; characters

binary-info:  #{ddeedd}                   ; binary data is put between
type? binary-info                         ; curly braces and preceded by
                                          ; the pound symbol

image: load http://rebol.com/view/bay.jpg ; REBOL can even automatically
type? image                               ; recognize the data type of
                                          ; most common image formats.

a-sound: load %/c/windows/media/tada.wav  ; And sounds too!
a-sound/type

Data types can be specifically "cast" (created, or assigned to different types) using "to-(type)" functions:

numbr: 4729                ; The label 'numbr now represents the integer
                           ;   4729.
strng: to-string numbr     ; The label 'strng now represents a piece of
                           ;   quoted text made up of the characters
                           ;   "4729".  Try adding strng + numbr, and
                           ;   you'll get an error.

; This example creates and adds two coordinate pairs.  The pairs are
; created from individual integer values, using the "to-pair" function:

x: 12  y: 33  q: 18  p: 7
pair1: to-pair rejoin [x "x" y]        ; 12x33
pair2: to-pair rejoin [q "x" p]        ; 18x7
print pair1 + pair2                    ; 12x33 + 18x7 = 30x40

; This example builds and manipulates a time value using the "to-time"
; function:

hour: 3
minute: 45
second: 00
the-time: to-time rejoin [hour ":" minute ":" second]    ; 3:45am
later-time: the-time + 3:00:15
print rejoin ["3 hours and 15 seconds after 3:45 is " later-time]

; This converts REBOL color values (tuples) to HTML colors and visa versa:

to-binary request-color
to-tuple #{00CD00}

REBOL has many built-in helper functions for dealing with common data types. Another way to create pair values is with the "as-pair" function. You'll see this sort of pair creation commonly in games which plot graphics at coordinate points on the screen:

x: 12  y: 33  q: 18  p: 7
print (as-pair x y) + (as-pair q p)    ; much simpler!

Built-in network protocols, native data types, and consistent language syntax for reading, writing, and manipulating data allow you to perform common coding chores easily and intuitively in REBOL. Remember to type or paste every example into the REBOL interpreter to see how each function and language construct operates.

6.3 GUIs (Program Windows)

Graphic user interfaces ("GUI"s) are easier to create in REBOL than in any other language. The functions "view" and "layout" are used together to display GUIs. The parameters passed to the layout function are enclosed in brackets. Those brackets can include identifiers for all types of GUI elements ("widgets"):

view layout [btn]  ; creates a GUI with a button

view layout [field]  ; creates a GUI with a text input field

view layout [text "REBOL is really pretty easy to program"]

view layout [text-list]  ; a selection list

view layout [
    button
    field
    text "REBOL is really pretty easy to program."
    text-list
    check
]

In REBOL, widgets are called "styles", and the entire GUI dialect is called "VID". You can adjust the visual characteristics of any style in VID by following it with appropriate modifiers:

view layout [
    button red "Click Me"
    field "Enter some text here"
    text font-size 16 "REBOL is really pretty easy to program." purple
    text-list 400x300 "line 1" "line 2" "another line"
    check yellow
]

The size of your program window can be specified by either of these two formats:

view layout [size 400x300]
view layout/size [] 400x300

; both these lines do exactly the same thing

A variety of functions are available to control the alignment, spacing, and size of elements in a GUI layout:

view layout [
    size 500x350
    across
    btn "side" btn "by" btn "side"
    return
    btn "on the next line"
    tab 
    btn "over a bit"
    tab
    btn "over more"
    below
    btn 160 "underneath" btn 160 "one" btn 160 "another"
    at 359x256 
    btn "at 359x256"
]

VERY IMPORTANT: You can have widgets perform functions when clicked, or when otherwise activated. Just put the functions inside another set of brackets after the widget. This is how you get your GUIs to 'do something' (using the fundamentals introduced in the previous section):

view layout [button "click me" [alert "You clicked the button."]]

view layout [btn "Display Rebol.com HTML" [editor read http://rebol.com]]

view layout [btn "Write current time to HD" [write %time.txt now/time]]

; The word "value" refers to data contained in a currently activated
; widget:

view layout [
    text "Some action examples.  Try using each widget:"
    button red "Click Me" [alert "You clicked the red button."]
    field 400 "Type some text here, then press [Enter] on your keyboard" [
        alert value
    ]
    text-list 400x300 "Select this line" "Then this line" "Now this one" [
        alert value
    ]
    check yellow [alert "You clicked the yellow check box."]
    button "Quit" [quit]    
]

To react to right-button mouse clicks on a widget, put the functions to be performed inside a second set of brackets after the widget:

view layout [
    btn "Right Click Me" [alert "left click"][alert "right click"]
]

You can assign keyboard shortcuts (keystrokes) to any widget, so that pressing the key reacts the same way as activating the GUI widget:

view layout [
    btn "Click me or press the 'A' key on your keyboard" #"a" [
        alert "You just clicked the button OR pressed the 'A' key"
    ]
]

You can assign a word label to any widget, and refer to data and properties of that widget by its label. The "text" property is especially useful:

view layout [
    page-to-read: field "http://rebol.com"
    ; page-to-read/text now refers to the text contained in that field
    btn "Display HTML" [editor read (to-url page-to-read/text)]
]

You can also set various properties of a widget using its assigned label. When the "Edit HTML Page" button is clicked below, the text of the multi-line area widget is set to contain the text read from the given URL. The "show" function in the example below is very important. It must be used to update the GUI display any time a widget property is changed (if you ever create a GUI that doesn't seem to respond properly, the first thing to check is that you've used a "show" function to properly update any changes on screen):

view layout [
    page-to-read: field "http://rebol.com"
    the-html: area 600x440
    btn "Download HTML Page" [
        the-html/text: read (to-url page-to-read/text)
        ; try commenting out the following line to see what happens:
        show the-html
    ]
]

Below are two more examples of the above code pattern (getting and setting a widget's text property) - it's a very important idiom in REBOL GUIs. In the first example, the variable "f" is assigned to the field widget, then the variable "t" is assigned to the text contained in that field. In the second example, "t" is assigned the text contained in the f1 field, then the text in f2 is set to "t" - again, all using the colon symbol. Note the use of the "show" function to update the display:

view layout [
    f: field
    btn "Display Variable" [

    ; When the button is pressed, set the variable
    ; "t" to hold the text currently in the field
    ; above, then alert the contents of that variable:

        t: f/text
        alert t    
    ]
]

view layout [
    f1: field
    btn "Display Variable" [

        ; Set the variable "t" to the text contained
        ; in the f1 field above:

        t: f1/text

        ; Now CHANGE the text in the f2 below to
        ; to equal the text stored in variable "t":

        f2/text: t
        show f2    
    ]
    f2: field
]

; You GET the text from a widget by assigning a VALUE to equal the
; widget's text property.  You SET/CHANGE the text of a widget by
; assigning THE TEXT PROPERTY of that widget to equal a value.

The "offset" of a widget holds its coordinate position. It's another useful property, especially for GUIs which involve movement:

view layout [
    size 600x440
    jumper: button "click me" [
        jumper/offset: random 580x420
        ; The "random" function creates a random value within
        ; a specified range.  In this example, it creates a
        ; random coordinate pair within the range 580x420,
        ; every time the button is clicked.  That random value
        ; is assigned to the position of the button.
    ]
]

The "style" function is very powerful. It allows you to assign a specific widget definition, including all its properties and actions, to any word label you choose. Any instance of that word label is thereafter treated as a replication of the entire widget definition:

view layout [
    size 600x440 
    style my-btn btn green "click me" [
        face/offset: random 580x420
    ]
    ; "my-btn" now refers to all the above code
    at 254x84 my-btn
    at 19x273 my-btn
    at 85x348 my-btn
    at 498x12 my-btn
    at 341x385 my-btn
]

REBOL is great at dealing with all types of common data - not just text. You can easily display photos and other graphics in your GUIs, play sounds, display web pages, etc. Here's some code that downloads an image from a web server and displays it in a GUI - notice the "view layout" functions again:

view layout [image (load http://rebol.com/view/bay.jpg)]

The "image" widget inside the brackets displays a picture (.bmp, .jpg, .gif., .png) in the GUI. The "load" function downloads the image to be displayed.

REBOL can apply many built-in effects to images:

view layout [image (load http://rebol.com/view/bay.jpg) effect [Emboss]]

view layout [image (load http://rebol.com/view/bay.jpg) effect [Flip 1x1]]

; The parentheses are not required:

view layout [image load http://rebol.com/view/bay.jpg effect [Grayscale]]

; There are MANY more built-in effects.

You can impose images onto most types of widgets:

view layout [area load http://rebol.com/view/bay.jpg]

; Use the "fit" effect to stretch or shrink the size of the image to that
; of the widget:

view layout [area load http://rebol.com/view/bay.jpg effect [Fit]]
view layout [button load http://rebol.com/view/bay.jpg effect [Fit]]
view layout [field load http://rebol.com/view/bay.jpg effect [Fit Emboss]]

; You can still type into the field and area widgets, as usual.

You can apply colors directly to images, just like any other widget. Notice that you can perform calculations directly on color values:

view layout [image load http://rebol.com/view/bay.jpg yellow]
view layout [image load http://rebol.com/view/bay.jpg (yellow / 2)]
view layout [image load http://rebol.com/view/bay.jpg (yellow + 0.0.132)]

Color gradients (fades from one color to another) are also simple to apply to any widget:

view layout [area effect [gradient red blue]]
view layout [
    size 500x400
    backdrop effect [gradient 1x1 tan brown]
    box effect [gradient 123.23.56 254.0.12]
    box effect [gradient blue gold/2]
]

You can assign a word label to any layout of GUI widgets, and then display those widgets simply by using the assigned word:

gui-layout1: [button field text-list]
view layout gui-layout1

You can save any GUI layout as an image, using the "to-image" function. This enables a built in screen shot mechanism, and also allows you to easily create/save/manipulate new images using any of the graphic capabilities in REBOL:

; assign the label "picture" to an image of a layout:

picture: to-image layout [
    page-to-read: field "http://rebol.com"
    btn "Display HTML"
]

; save it to the hard drive as a .png file:

save/png %/c/layout.png picture

Here are some other GUI elements used in REBOL's "VID" layout language:

view layout [
    backcolor white
    h1 "More GUI Examples:"
    box red 500x2
    bar: progress
    slider 200x16 [bar/data: value show bar]
    area "Type here"
    drop-down
    across 
    toggle "Click" "Here" [print value]
    rotary "Click" "Again" "And Again" [print value]
    choice "Choose" "Item 1" "Item 2" "Item 3" [print value]
    radio radio radio
    led
    arrow
    return
    text "Normal"
    text "Bold" bold
    text "Italic" italic
    text "Underline" underline
    text "Bold italic underline" bold italic underline
    text "Serif style text" font-name font-serif
    text "Spaced text" font [space: 5x0]
    return
    h1 "Heading 1"
    h2 "Heading 2"
    h3 "Heading 3"
    h4 "Heading 4"
    tt "Typewriter text"
    code "Code text"
    below
    text "Big" font-size 32
    title "Centered title" 200
    across
    vtext "Normal"
    vtext "Bold" bold
    vtext "Italic" italic
    vtext "Underline" underline
    vtext "Bold italic underline" bold italic underline
    vtext "Serif style text" font-name font-serif
    vtext "Spaced text" font [space: 5x0]
    return
    vh1 "Video Heading 1"
    vh2 "Video Heading 2"
    vh3 "Video Heading 3"
    vh4 "Video Heading 3"
    label "Label"
    below
    vtext "Big" font-size 32
    banner "Banner" 200
]

Here's a list of all the built in widgets (remember, in REBOL's VID language, widgets are called "styles"):

probe extract svv/vid-styles 2

Here's a list of the changeable attributes ("facets") available to all widgets:

probe remove-each i copy svv/facet-words [function? :i]

And here's a list of available layout words:

probe svv/vid-words

NOTE: By default, all REBOL GUIs contain the text "REBOL - " in the window title bar. In Windows, you can eliminate that text with the following code. Just set the "tt" variable to hold the title text you want displayed:

tt: "Your Title"

user32.dll: load/library %user32.dll
gf: make routine![return:[int]]user32.dll"GetFocus"
sc: make routine![hw[int]a[string!]return:[int]]user32.dll"SetWindowTextA"
so: :show show: func[face][so[face]hw: gf sc hw tt]

That's just the tip of the iceberg. With REBOL, even absolute beginners can create nice looking, powerful graphic interfaces in minutes. See http://rebol.com/docs/easy-vid.html and http://rebol.com/docs/view-guide.html for more information. Here's a little game that demonstrates common GUI techniques (this whole program was presented earlier in the demo app, in a more compact format without any comments. It's tiny.):

REBOL []

; Here's the code presented above, to alter the Windows title bar:

tt: "Tile Game"
user32.dll: load/library %user32.dll
gf: make routine![return:[int]]user32.dll"GetFocus"
sc: make routine![hw[int]a[string!]return:[int]]user32.dll"SetWindowTextA"
so: :show show: func[face][so[face]hw: gf sc hw tt]

; Create a GUI that's centered on the user's screen:

view center-face layout [

    ; Define some basic layout parameters.  "origin 0x0" 
    ; starts the layout in the upper left corner of the
    ; GUI window.  "space 0x0" dictates that there's no
    ; space between adjacent widgets, and "across" lays
    ; out consecutive widgets next to each other:

    origin 0x0 space 0x0 across 

    ; The section below creates a newly defined button
    ; style called "piece", with an action block that 
    ; swaps the current button's position with that of 
    ; the adjacent empty space.  That action is run
    ; whenever one of the buttons is clicked:

    style piece button 60x60 [

        ; The line below checks to see if the clicked button
        ; is adjacent to the empty space.  The "offset" 
        ; refinement contains the position of the given 
        ; widget.  The word "face" is used to refer to the
        ; currently clicked widget.  The "empty" button is
        ; defined later (at the end of the GUI layout).
        ; It's ok that the empty button is not yet defined,
        ; because this code is not evaluated until the
        ; the entire layout is built and "view"ed:

        if not find [0x60 60x0 0x-60 -60x0
            ] (face/offset - empty/offset) [exit]

        ; In English, that reads 'subtract the position of
        ; the empty space from the position of the clicked
        ; button (the positions are in the form of 
        ; Horizontal x Vertical coordinate pairs).  If that 
        ; difference isn't 60 pixels on one of the 4 sides,
        ; then don't do anything.' (60 pixels is the size of
        ; the "piece" button defined above.)

        ; The next three lines swap the positions of the 
        ; clicked button with the empty button.

        ; First, create a variable to hold the current
        ; position of the clicked button:

        temp: face/offset  

        ; Next, move the button's position to that of the 
        ; current empty space:

        face/offset: empty/offset 

        ; Last, move the empty space (button), to the old
        ; position occupied by the clicked button:

        empty/offset: temp
    ]

    ; The lines below draw the "piece" style buttons onto 
    ; the GUI display.  Each of these buttons contains all
    ; of the action code defined for the piece style above:

    piece "1"   piece "2"   piece "3"   piece "4" return
    piece "5"   piece "6"   piece "7"   piece "8" return
    piece "9"   piece "10"  piece "11"  piece "12" return
    piece "13"  piece "14"  piece "15"

    ; Here's the empty space.  Its beveled edge is removed
    ; to make it look less like a movable piece, and more
    ; like an empty space:

    empty: piece 200.200.200 edge [size: 0]
]

Advanced users may be interested in understanding why the two words "view" and "layout" are used to create GUIs. Those functions represent two complete and separate language dialects in REBOL. The "view" function is a front end to the lower level graphic compositing engine and user interface system built into REBOL. "Layout" is a higher level function that simply assembles view functions required to draw and manipulate common GUI elements. Understanding how the two operate under the hood is helpful in understanding just how deep, compact, and powerful the REBOL language and dialecting design is. For more information, see http://rebol.com/docs/view-system.html.

6.4 Blocks, Series, and Strings

In REBOL, all multiple pieces of grouped data items are stored in "blocks". Blocks are delineated by starting and ending brackets:

[ ]

Data items in blocks are separated by white space. Here's a block of text items:

["John" "Bill" "Tom" "Mike"]

Blocks were snuck in earlier as multiple text arguments passed to the "rejoin" function, and as brackets used to delineate GUI code passed to the 'view layout' functions:

rejoin ["Hello " "there!"]
view layout [button "Click Me" [alert "Hello there!"]]

Blocks are actually the fundamental structure used to organize REBOL code. You'll find brackets throughout the language syntax to delineate functions, parameters, and other items. In the next section of this tutorial, you'll see more about functions and control structures that use brackets to separate grouped items of code. This section will cover how data can be grouped into blocks.

The key concept to understand with blocks is that they are used to hold multiple pieces of data. Like any other variable data, blocks can be assigned word labels:

some-names: ["John" "Bill" "Tom" "Mike"]

; "some-names" now refers to all 4 of those text items

print some-names

Blocks of text data (lists) can be displayed in GUIs, using the "text-list" widget:

view layout [text-list data (some-names)]

The "append" function is used to add items to a block:

append some-names "Lee"
print some-names

append gui-layout1 [text "This text was appended to the GUI block."]
view layout gui-layout1

The "foreach" function is used to do something to/with each item in a block:

foreach item some-names [alert item]

The "remove-each" function can be used to remove items from a block that match a certain criteria:

remove-each name some-names [find name "i"]

; removes all names containing the letter "i" - returns ["John" "Tom"]

Empty data blocks are created with the "copy" function. "Copy" assures that blocks are erased and defined without any previous content. You'll use "copy" whenever you need to create an empty block:

; Create a new empty block like this:

empty-block: copy []

; NOT like this:

empty-block: []

Here's a very typical example that uses a block to save text entered into the fields of a GUI. When the "Save" button is pressed, the text in each of the fields is appended to a new empty block, then that whole block is saved to a text file. To later retrieve the saved values, the block is loaded from the text file, and its items assigned back to the appropriate fields in the GUI:

view gui: layout [

    ; label some text fields:

    field1: field 
    field2: field 
    field3: field

    ; add a button:

    btn "Save" [

        ; when the button is clicked, create a new empty block:

        save-block: copy []

        ; add the text contained in each field to the block:

        append save-block field1/text 
        append save-block field2/text 
        append save-block field3/text

        ; save the block to a file:

        save %save.txt save-block
        alert {SAVED -- Now try running this script again, and load
            the data back into the fields.}
    ]

    ; another button:

    btn "Load" [

        ; load the saved block:

        save-block: load %save.txt

        ; set the text in each field to the contents of the block:

        field1/text: save-block/1
        field2/text: save-block/2
        field3/text: save-block/3

        ; update the GUI display:

        show gui
    ]
]

After running the script above, open the save.txt file with a text editor, and you'll see it contains the text from the fields in the GUI. You can edit the save.txt file with your text editor, then click the "Load" button, and the edited values will appear back in the GUI. You'll use blocks regularly to store and retrieve multiple pieces of data in this way, using text files.

6.4.1 Series Functions

In REBOL, blocks can be automatically treated as lists of data, called "series", and manipulated using built-in functions that enable searching, sorting, and otherwise organizing the blocked data:

some-names: ["John" "Bill" "Tom" "Mike"]

sortednames: sort some-names       ; sort alphabetically/ordinally

print first sortednames            ; displays the first item ("Bill")

print sortednames/1                ; ALSO displays the first item ("Bill")
                                   ; (just an alternate syntax)

print pick sortednames 1           ; ALSO displays the first item ("Bill")
                                   ; (another alternate syntax)

find some-names "John"             ; SEARCH for "John" in the block, 
                                   ;  set a position marker after that
                                   ;  item - a very important function

find/last some-names "John"        ; search for "John" backwards from
                                   ;  the end of the block

select some-names "John"           ; search for "John" in the block
                                   ;  and return the Next item.

reverse sortednames                ; reverse the order of items in the
                                   ;  block

length? sortednames                ; COUNT items in the block - important

head sortednames                   ; set a position marker at the 
                                   ;  beginning of the block

next sortednames                   ; set a position marker at the next
                                   ;  item in the block

back sortednames                   ; set a position marker at the 
                                   ;  previous item in the block

last sortednames                   ; set a position marker at the last
                                   ;  item in the block

tail sortednames                   ; set a position marker after the
                                   ;  last item in the block

at sortednames x                   ; set a position marker at the x
                                   ;  numbered item in the block

skip sortednames x                 ; set a position marker x items
                                   ;  forward or backward in the block

extract sortednames 3              ; collect every third item from the
                                   ;  block

index? sortednames                 ; retrieves position number of the
                                   ;  currently marked item in the block

insert sortednames "Lee"           ; add the name "Lee" at the current
                                   ;  position in the block

append sortednames "George"        ; add "George" to the tail of the block
                                   ;  and set position marker to the head

remove sortednames                 ; remove the item at the currently
                                   ;  marked position in the block

remove find sortednames "Mike"     ; ... find the "Mike" item in the
                                   ;  block and remove it

change sortednames "Phil"          ; change the item at the currently
                                   ;  marked position to "Phil"

change third sortednames "Phil"    ; change the third item to "Phil"

poke sortednames 3 "Phil"          ; another way to change the third item
                                   ;  to "Phil"

copy/part sortednames 2            ; get the first 2 items in the block

clear sortednames                  ; remove all items in the block after
                                   ;  the currently marked position

replace/all sortednames "Lee" "Al" ; replace all occurrences of "Lee" in
                                   ;  the block with "Al"

both: join some-names sortednames  ; concatenate both blocks together

intersect sortednames some-names   ; returns the items found in both
                                   ;  blocks

difference sortednames some-names  ; returns the items that are NOT
                                   ;  found in BOTH blocks

exclude sortednames some-names     ; returns the items in sortednames that
                                   ;  are NOT also in some-names

union sortednames some-names       ; returns the items found in both
                                   ;  blocks, ignoring duplicates

unique sortednames                 ; returns all items in the block,
                                   ;  with duplicates removed

empty? sortednames                 ; returns true if the block is empty

write %/c/names.txt some-names     ; write the block to the hard drive
                                   ;  as raw text data

save %/c/namess.txt some-names     ; write the block to the hard drive
                                   ;  as native REBOL formatted code

Learning to use series functions is absolutely fundamental to using REBOL. They will be covered by example throughout this text. See http://www.rebol.com/docs/dictionary.html for a list of additional series functions. For more information and examples, be sure to read sections 6 and 7 from the REBOL/Core Users Guide by Carl Sassenrath.

6.4.2 REBOL Strings

In REBOL, a "string" is simply a series of characters. If you have experience with other programming languages, this can be one of the sticking points in learning REBOL. REBOL's solution is actually a very powerful, easy to learn and consistent with the way other operations work in the language. Proper string management simply requires a good understanding of series. Take a look at the following examples to see how to do a few common operations:

the-string: "abcdefghijklmnopqrstuvwxyz"

; Left String:  (get the left 7 characters of the string):

copy/part the-string 7

; Right String:  (Get the right 7 characters of the string):

copy at tail the-string -7

; Mid String 1:  (get 7 characters from the middle of the string,
; starting with the 12th character):

copy/part (at the-string 12) 7

; Mid String 2:  (get 7 characters from the middle of the string,
; starting 7 characters back from the letter "m"):

copy/part (find the-string "m") -7

; Mid String 3:  (get 7 characters from the middle of the string,
; starting 12 characters back from the letter "t"):

copy/part (skip (find the-string "t") -12) 7

; 3 different ways to get just the 7th character:

the-string/7 
pick the-string 7
seventh the-string

; Change "cde" to "123"

replace the-string "cde" "123"

; Several ways to change the 7th character to "7"

change (at the-string 7) "7"
poke the-string 7 #"7"  ; the pound symbol refers to a single character
poke the-string 7 (to-char "7")  ; another way to use single characters
print the-string

; Remove 15 characters, starting at the 3rd position:

remove/part (at the-string 3) 15
print the-string

; Insert 15 characters, starting at the 3rd position:

insert (at the-string 3) "cdefghijklmnopq"
print the-string

; Insert 3 instances of "-+" at the beginning of the string:

insert/dup head the-string "-+ " 3
print the-string

; Replace every instance of "-+ " with " ":

replace/all the-string "-+ "  " "
print the-string

; Remove spaces from a string (type "? trim" to see all its refinements!):

trim the-string
print the-string

; Get every third character from the string:

extract the-string 3

; Get the ASCII value for "c" (ASCII 99):

to-integer third the-string

; Get the character for ASCII 99 ("c"):

to-char 99

; Convert the above character value to a string value:

to-string to-char 99

; Convert any value to a string:

to-string now
to-string $2344.44
to-string to-char 99
to-string system/locale/months

; An even better way to convert values to strings:

form now
form $2344.44
form to-char 99
form system/locale/months  ; convert blocks to nicely formed strings

; Covert strings to a block of characters:

the-block: copy []
foreach item the-string [append the-block item]
probe the-block

REBOL's series functions are very versatile. Often, you can devise several ways to do the same thing:

; Remove the last part of a URL:

the-url: "http://website.com/path"
clear at the-url (index? find/last the-url "/")
print the-url

; Another way to do it:

the-url: "http://website.com/path"
print copy/part the-url (length? the-url)-(length? find/last the-url "/")

(Of course, REBOL has a built-in helper function to accomplish the above goal, directly with URLs):

the-url: http://website.com/path
print first split-path the-url

There are a number of additional functions that can be used to work specifically with string series. Run the following script for an introduction:

string-funcs: [
    build-tag checksum clean-path compress debase decode-cgi decompress
    dehex detab dirize enbase entab import-email lowercase mold parse-xml
    reform rejoin remold split-path suffix? uppercase
]    
echo %string-help.txt  ; "echo" saves console activity to a file
foreach word string-funcs [
    print "___________________________________________________________^/"
    print rejoin ["word:  " uppercase to-string word]  print "" 
    do compose [help (to-word word)]
]
echo off
editor at read %string-help.txt 4

See http://www.rebol.com/docs/dictionary.html and http://rebol.com/docs/core23/rebolcore-8.html for more information about the above functions.

6.4.3 Indentation

Blocks often contain other blocks. Such compound blocks are typically indented with consecutive tab stops. Starting and ending brackets are normally placed at the same indentation level. This is conventional in most programming languages, because it makes complex code easier to read, by grouping things visually. For example, the compound block below:

big-block: [[may june july] [[1 2 3] [[yes no] [monday tuesday friday]]]]

can be written as follows to show the beginnings and endings of blocks more clearly:

big-block: [
    [may june july] 
    [ 
        [1 2 3] 
        [
            [yes no]
            [monday tuesday friday]
        ]
    ]
]

probe first big-block
probe second big-block
probe first second big-block
probe second second big-block
probe first second second big-block
probe second second second big-block

Indentation is not required, but it's very helpful.

6.4.4 More About Why/How Blocks are Useful

IMPORTANT: In REBOL, blocks can contain mixed data of ANY type (text and binary items, embedded lists of items (other blocks), variables, etc.):

some-items: ["item1" "item2" "item3" "item4"]
an-image: load http://rebol.com/view/bay.jpg
append some-items an-image

; "some-items" now contains 4 text strings, and an image!

; You can save that entire block of data, INCUDING THE BINARY
; IMAGE data, to your hard drive as a SIMPLE TEXT FILE: 

save/all %some-items.txt some-items

; to load it back and use it later:

some-items: load %some-items.txt
view layout [image fifth some-items]

Take a moment to examine the example above. REBOL's block structure works in a way that is dramatically easy to use compared to other languages and data management solutions (much more simply than most database systems). It's is a very flexible, simple, and powerful way to store data in code! The fact that blocks can hold all types of data using one simple syntactic structure is a fundamental reason it's easier to use than other programming languages and computing tools. You can save/load block code to the hard drive as a simple text file, send it in an email, display it in a GUI, compress it and transfer it to a web server to be downloaded by others, transfer it directly over a point-to-point network connection, or even convert it to XML, encrypt, and store parts of it in a secure multiuser database to be accessed by other programming languages, etc...

Remember, all programming, and computing in general, is essentially about storing, organizing, manipulating, and transferring data of some sort. REBOL makes working with all types of data very easy - just put any number of pieces of data, of any type, in between two brackets, and that data is automatically searchable, sortable, storable, transferable, and otherwise usable in your programs.

6.4.5 Evaluating Variables in Blocks: Compose, Reduce, Pick and More

You will often find that you want to refer to an item in a block by its index (position number), as in the earlier 'some-items' example:

view layout [image some-items/5]

You may not, however, always know the specific index number of the data item you want to access. For example, as you insert data items into a block, the index position of the last item changes (it increases). You can obtain the index number of the last item in a block simply by determining the number of items in the block (the position number of the last item in a block is always the same as the total number of items in the block). In the example below, that index number is assigned the variable word "last-item":

last-item: length? some-items

Now you can use that variable to pick out the last item:

view layout [image (pick some-items last-item)]

; In our earlier example, with 5 items in the block, the
; line above evaluates the same as:

view layout [image (pick some-items 5)]

You can refer to other items by adding and subtracting index numbers:

alert pick some-items (last-item - 4)

There are several other ways to do the exact same thing in REBOL. The "compose" function allows variables in parentheses to be evaluated and inserted as if they'd been typed explicitly into a code block:

view layout compose [image some-items/(last-item)]

; The line above appears to the interpreter as if the following
; had been typed:

view layout [image some-items/5]

The "compose" function is very useful whenever you want to refer to data at variable index positions within a block. The "reduce" function can also be used to produce the same type of evaluation. Function words in a reduced block should begin with the tick (') symbol:

view layout reduce ['image some-items/(last-item)]

Another way to use variable values explicitly is with the ":" format below. This code evaluates the same as the previous two examples:

view layout [image some-items/:last-item]

Think of the colon format above as the opposite of setting a variable. As you've seen, the colon symbol placed after a variable word sets the word to equal some value. A colon symbol placed before a variable word gets the value assigned to the variable, and inserts that value into the code as if it had been typed explicitly.

You can use the "index?" and "find" functions to determine the index position(s) of any data you're searching for in a block:

index-num: index? (find some-items "item4")

Any of the previous 4 formats can be used to select the data at the determined variable position:

print pick some-items index-num
print compose [some-items/(index-num)]
print reduce [some-items/(index-num)]  
; no function words are used in the block above, so no ticks are required
print some-items/:index-num

Here's an example that displays variable image data contained in a block, using a foreach loop. The "compose" function is used to include dynamically changeable data (image representations), as if that data had been typed directly into the code:

photo1: load http://rebol.com/view/bay.jpg
photo2: load http://rebol.com/view/demos/palms.jpg

; The REBOL interpreter sees the following line as if all the code
; representing the above images had been typed directly in the block:

photo-block: compose [(photo1) (photo2)]

foreach photo photo-block [view layout [image photo]]

Block concepts may seem a bit vague at this point. The practical application of block structures will be presented much more thoroughly, by example, throughout this tutorial, and clarification about the usefulness of blocks will come from seeing them in working code. For additional detailed information about using blocks and series functions see http://www.rebol.com/docs/core23/rebolcore-6.html.

6.5 Conditions

6.5.1 If

Conditions are used to manage program flow. The most basic conditional evaluation is "if":

if (this expression is true) [do this block of code] 
; parentheses are not required

Math operators are typically used to perform conditional evaluations: = < > <> (equal, less-than, greater-than, not-equal):

if now/time > 12:00 [alert "It's after noon."]

Here's an example that gets a username and password from the user, tests that data using an "if" evaluation, and alerts the user if the response is correct:

userpass: request-pass/title "Type 'username' and 'password'"
if (userpass = ["username" "password"]) [alert "Welcome back!"]

6.5.2 Either

"Either" is an if/then/else evaluation that chooses between two blocks to evaluate, based on whether the given condition is true or false. Its syntax is:

either (condition) [
    block to perform if the condition is true
][
    block to perform if the condition is false
]

For example:

either now/time > 8:00am [
    alert "It's time to get up!"
][
    alert "You can keep on sleeping."
] 

userpass: request-pass
either userpass = ["username" "password"] [
    alert "Welcome back!"
][
    alert "Incorrect user/password combination!"
]

6.5.3 Switch

The "switch" evaluation chooses between numerous functions to perform, based on multiple evaluations. Its syntax is:

switch/default (main value) [
    (value 1) [block to execute if value 1 = main value
    (value 2) [block to execute if value 2 = main value]
    (value 3) [block to execute if value 3 = main value]
    ; etc...
] [default block of code to execute if none of the values match]

You can compare as many values as you want against the main value, and run a block of code for each matching value:

favorite-day:  request-text/title "What's your favorite day of the week?"

switch/default favorite-day [
    "Monday"    [alert "Monday is the worst!  The work week begins..."]
    "Tuesday"   [alert "Tuesdays and Thursdays are both ok, I guess..."]
    "Wednesday" [alert "The hump day - the week is halfway over!"]
    "Thursday"  [alert "Tuesdays and Thursdays are both ok, I guess..."]
    "Friday"    [alert "Yay!  TGIF!"]
    "Saturday"  [alert "Of course, the weekend!"]
    "Sunday"    [alert "Of course, the weekend!"]
] [alert "You didn't type in the name of a day!"]

6.5.4 Case

You can choose between multiple evaluations of any complexity using the "case" structure. If none of the cases evaluate to true, you can use any true value to trigger a default evaluation:

name: "john"
case [
    find name "a" [print {Your name contains the letter "a"}]
    find name "e" [print {Your name contains the letter "e"}]
    find name "i" [print {Your name contains the letter "i"}]
    find name "o" [print {Your name contains the letter "o"}]
    find name "u" [print {Your name contains the letter "u"}]
    true [print {Your name doesn't contain any vowels!}]
]

for i 1 100 1 [
    case [
        (0 = modulo i 3) and (0 = modulo i 5) [print "fizzbuzz"]
        0 = modulo i 3 [print "fizz"]
        0 = modulo i 5 [print "buzz"]
        true [print i]
    ]
]

By default, the case evaluation automatically exits once a true evaluation is found (i.e., in the name example above, if the name contains more than one vowel, only the first vowel will be printed). To check all possible cases before ending the evaluation, use the /all refinement:

name: "brian" 
found: false
case/all [
    find name "a" [print {Your name contains the letter "a"} found: true]
    find name "e" [print {Your name contains the letter "e"} found: true]
    find name "i" [print {Your name contains the letter "i"} found: true]
    find name "o" [print {Your name contains the letter "o"} found: true]
    find name "u" [print {Your name contains the letter "u"} found: true]
    found = false [print {Your name doesn't contain any vowels!}]
]

6.5.5 Multiple Conditions: "and", "or", "all", "any"

You can check for more than one condition to be true, using the "and", "or", "all", and "any" words:

; first set some initial values all to be true:

value1: value2: value3: true

; then set some additional values all to be false:

value4: value5: value6: false

; The following prints "both true", because both the first
; condition AND the second condition are true:

either ( (value1 = true) and (value2 = true) ) [
    print "both true"
] [
    print "not both true"
]

; The following prints "both not true", because the second 
; condition is false:

either ( (value1 = true) and (value4 = true) ) [
    print "both true"
] [
    print "not both true"
]

; The following prints "either one OR the other is true"
; because the first condition is true:

either ( (value1 = true) or (value4 = true) ) [
    print "either one OR the other is true"
] [
    print "neither is true"
]

; The following prints "either one OR the other is true"
; because the second condition is true:

either ( (value4 = true) or (value1 = true) ) [
    print "either one OR the other is true"
] [
    print "neither is true"
]

; The following prints "either one OR the other is true"
; because both conditions are true:

either ( (value1 = true) or (value4 = true) ) [
    print "either one OR the other is true"
] [
    print "neither is true"
]

; The following prints "neither is true":

either ( (value4 = true) or (value5 = true) ) [
    print "either one OR the other is true"
] [
    print "neither is true"
]

For comparisons involving more items, you can use "any" and "all":

; The following lines both print "yes", because ALL comparisons are true.
; "All" is just shorthand for the multiple "and" evaluations:

if ((value1 = true) and (value2 = true) and (value3 = true)) [
    print "yes"
]

if all [value1 = true  value2 = true  value3 = true] [
     print "yes"
]

; The following lines both print "yes" because ANY ONE of the comparisons
; is true. "Any" is just shorthand for the multiple "or" evaluations:

if ((value1 = true) or (value4 = true) or (value5 = true)) [
    print "yes"
]

if any [value1 = true  value4 = true  value5 = true] [
     print "yes"
]

6.6 Loops

6.6.1 Forever

"Loop" structures provide programmatic ways to methodically repeat actions, manage program flow, and automate lengthy data processing activities. The "forever" function creates a simple repeating loop. Its syntax is:

forever [block of actions to repeat]

The following code uses a forever loop to continually check the time. It alerts the user when 60 seconds has passed. Notice the "break" function, used to stop the loop:

alarm-time: now/time + :00:60
forever [if now/time = alarm-time [alert "1 minute has passed" break]]

Here's a more interactive version using some info provided by the user. Notice how the forever loop, if evaluation, and alert arguments are indented to clarify the grouping of related parameters:

event-name: request-text/title "What do you want to be reminded of?"
seconds: to-integer request-text/title "Seconds to wait?"
alert rejoin [
    "It's now " now/time ", and you'll be alerted in " 
    seconds " seconds."
]
alarm-time: now/time + seconds
forever [
    if now/time = alarm-time [
        alert rejoin [
            "It's now "alarm-time ", and " seconds 
            " seconds have passed.  It's time for: " event-name
        ] 
        break
    ]
]

Here's a forever loop that displays/updates the current time in a GUI:

view layout [
    timer: field
    button "Start" [
        forever [
            set-face timer now/time 
            wait 1
        ]
    ]
]

6.6.2 Loop

The "loop" function allows you to repeatedly evaluate a block of code, a specified number of times:

loop 50 [print "REBOL is great!"]

6.6.3 Repeat

Like "loop", the "repeat" function allows you to repeatedly evaluate a block of code, a specified number of times. It additionally allows you to specify a counter variable which is automatically incremented each time through the loop:

repeat count 50 [print rejoin ["This is loop #: " count]]

The above code does the same thing as:

count: 0
loop 50 [
    count: count + 1
    print rejoin ["This is loop #: " count]
]

6.6.4 For

"For" loops allow you to control repetition patterns that involve consecutively changing values. You specify a start value, end value, incremental value, and a variable name to hold the current value during the loop. Here's the "for" loop syntax:

for {variable word to hold current value} {starting value} {ending value} {incremental value} [block of code to perform, which can use the current variable value]

For example:

for counter 1 10 1 [print counter] 
; starts on 1 and counts to 10 by increments of 1 

for counter 10 1 -1 [print counter] 
; starts on 10 and counts backwards to 1 by increments of -1

for counter 10 100 10 [print counter] 
; starts on 10 and counts to 100 by increments of 10   

for counter 1 5 .5 [print counter] 
; starts on 1 and counts to 5 by increments of .5    

for timer 8:00 9:00 0:05 [print timer] 
; starts at 8:00am and counts to 9:00am by increments of 5 minutes

for dimes $0.00 $1.00 $0.10 [print dimes] 
; starts at 0 cents and counts to 1 dollar by increments of a dime

for date 1-dec-2005 25-jan-2006 8 [print date] 
; starts at December 12, 2005 and counts to January 25, 2006 
; and by increments of 8 days   

for alphabet #"a" #"z" 1 [prin alphabet] 
; starts at the character a and counts to z by increments of 1 letter

Notice that REBOL properly increments dates, money, time, etc.

This "for" loop displays the first 5 file names in the current folder on your hard drive:

files: read %.  
for count 1 5 1 compose [print files/(count)]

Notice the "compose" word used in the for loop. "files/1" represents the first item in the file list, "files/2" represents the second, and so on. The first time though the loop, the code reads as if [print files/1] had been typed in manually, etc.

The following "for" loop displays all the files in the current folder:

files: read %.
filecount: length? files
for count 1 filecount 1 compose [print files/(count)]

6.6.5 Foreach (very important!)

The "foreach" function lets you easily loop through a block of data. Its syntax is:

foreach {variable name referring to each consecutive item in the given block} [given block] [block of functions to be executed upon each item in the given block, using the variable name to refer to each successive item]

This example prints the name of every file in the current directory on your hard drive:

folder: read %. 
foreach file folder [print file]

This line reads and prints each successive message in a user's email box:

foreach mail (read pop://user:pass@website.com) [print mail]

Here's a slightly more complex foreach example:

; define a block of text items:

some-names: ["John" "Bill" "Tom" "Mike"]

; define a variable used to count items in the block:

count: 0

; go through each item in the block:

foreach name some-names [

    ; increase the counter variable by 1, for each item:

    count: count + 1

    ; print the count number, and the associated text item:

    print rejoin ["Item " count ": " name]
]

Here's an example in which an empty block is created and data is appended using a foreach loop. The data is then converted to a text string and displayed in a GUI:

; define a block of text items:

some-names: ["John" "Bill" "Tom" "Mike"]

; create another new, empty block:

data-block: copy []

; define a variable used to count items in the block:

count: 0

; go through each item in the block:

foreach name some-names [

    ; increase the counter variable by 1, for each item:

    count: count + 1

    ; for each item, add some rejoined text to the originally empty block:

    append data-block rejoin ["Item " count ": " name newline]
]

; convert the newly created block to a string, and show it in a
; GUI text area widget:

view layout [area (to-string data-block)]

You can select multiple values from a block during each iteration of a foreach loop, using the following format:

users:  [
    "John Smith" "123 Tomline Lane Forest Hills, NJ" "555-1234"
    "Paul Thompson" "234 Georgetown Pl. Peanut Grove, AL" "555-2345"
    "Jim Persee" "345 Pickles Pike Orange Grove, FL" "555-3456"
    "George Jones" "456 Topforge Court Mountain Creek, CO" ""
    "Tim Paulson" "" "555-5678"
]

; Use the following format to get 3 consecutive values from the above
; block, each time through the loop:

foreach [name address phone] users [
    print rejoin [
        "^/Name:     " name       ; gets 1 value from the block
        "^/Address:  " address    ; gets the next value from the block
        "^/Phone:    " phone      ; gets a third value from the block
    ]
]

You will use the foreach function very often in REBOL code. It will be demonstrated many times, by example, throughout this tutorial.

6.6.6 Forall and Forskip

"Forall" loops through a block, incrementing the marked index number of the series as it loops through:

some-names: ["John" "Bill" "Tom" "Mike"]

foreach name some-names [print index? some-names]  ; index doesn't change
forall some-names [print index? some-names]  ; index changes

foreach name some-names [print name]
forall some-names [print first some-names] ; same effect as line above

"Forskip" works like forall, but skips through the block, jumping a periodic number of elements on each loop:

some-names: ["John" "Bill" "Tom" "Mike"]
forskip some-names 2  [print first some-names]

6.6.7 While and Until

The "while" function repeatedly evaluates a block of code while the given condition is true. While loops are formatted as follows:

while [condition] [
    block of functions to be executed while the condition is true
]

This example counts to 5:

x: 1  ; create an initial counter value 
while [x <= 5] [
    alert to-string x 
    x: x + 1
]

In English, that code reads:

"x" initially equals 1. 
While x is less than or equal to 5, display the value of x,
then add 1 to the value of x and repeat.

Some additional "while" loop examples:

while [not request "End the program now?"] [
    alert "Select YES to end the program."
] 
; "not" reverses the value of data received from
; the user (i.e., yes becomes no and visa versa)

alert "Please select today's date" 
while [request-date <> now/date] [
    alert rejoin ["Please select TODAY's date.  It's " now/date]
]

while [request-pass <> ["username" "password"]] [
    alert "The username is 'username' and the password is 'password'"
]

"Until" loops are similar to "while" loops. They do everything in a given block, repeatedly, until the last expression in the block evaluates to true:

x: 10
until [
    print rejoin ["Counting down: " x]
    x: x - 1
    x = 0
]

The example below uses several loops to alert the user to feed the cat, every 6 hours between 8am and 8pm. It uses a for loop to increment the times to be alerted, a while loop to continually compare the incremented times with the current time, and a forever loop to do the same thing every day, continuously. Notice the indentation:

forever [
    for timer 8:00am 8:00pm 6:00 [
        while [now/time <= timer] [wait :00:01] 
        alert rejoin ["It's now " now/time ".  Time to feed the cat."]
    ]
]

6.6.8 Loops in a GUI

The following code template can be used to accomplish forever loops in a GUI. This technique is described in more detail later in the tutorial. For now, just know that you can insert the following "invisible box" widget template (the entire indented code for the box widget inside the view layout block), and it will perform as a continuously repeating forever loop, without interfering with the main event loop of the GUI (without holding up any other operations in the GUI - the events in this loop will run simultaneously along with other happenings in the GUI):

view layout [
    button [print "button pressed"]
    at 0x0 box 0x0 rate 0 feel [engage: func [f a e] [if a = 'time [
        print "looping"  ; your repeating loop code goes here
    ]]]
]

6.7 User Defined Functions and Imported Code

REBOL's built-in functions satisfy many fundamental needs. To achieve more complex or specific computations, you can create your own function definitions.

Data and function words contained in blocks can be evaluated (their actions performed and their data values assigned) using the "do" word. Because of this, any block of code can essentially be treated as a function. That's a powerful key element of the REBOL language design:

some-actions: [
    alert "Here is one action." 
    print "Here's a second action."
    write %/c/anotheraction.txt "Here's a third action."
]

do some-actions

New function words can also be defined using the "does" and "func" words. "Does" is included directly after a word label definition, and forces a block to be evaluated every time the word is encountered:

more-actions: does [
    alert "4" 
    alert "5"
    alert "6"
]

; now to use that function, just type the word label:

more-actions

Here's a useful function to clear the command line screen in the REBOL interpreter.

cls: does [prin "^(1B)[J"]

cls

The "func" word creates an executable block in the same way as "does", but additionally allows you to pass your own specified parameters to the newly defined function word. The first block in a func definition contains the name(s) of the variable(s) to be passed. The second block contains the actions to be taken. Here's the "func" syntax:

func [names of variable(s) to be passed] [
    actions to be taken with those variables
]

This function definition:

sqr-add-var: func [num1 num2] [print square-root (num1 + num2)]

Can be used as follows. Notice that no brackets, braces, or parentheses are required to contain the data arguments. Data parameters simply follow the function word, on the same line of code:

sqr-add-var 12 4      ; prints "4",  the square root of 12 + 4  (16)
sqr-add-var 96 48     ; prints "12", the square root of 96 + 48 (144)

Here's a simple function to display images:

display: func [filename] [view layout [image load to-file filename]]

display (request-file)

By default, the last value evaluated by a function is returned when the function is complete:

concatenate: func [string1 string2] [join string1 string2]

string3: concatenate "Hello " "there."
print string3

By default, values used inside functions are treated as global, which means that if any variables are changed inside a function, they will be changed throughout the rest of your program:

x: 10

change-x-globally: func [y z] [x: y + z]

change-x-globally 10 20
print x

You can change this default behavior, and specify that any value be treated as local to the function (not changed throughout the rest of your program), by using the "/local" refinement:

x: 10

change-x-locally: func [y z /local x] [x: y + z]

change-x-locally 10 20     ; inside the function, x is now 30
print x                    ; outside the function, x is still 10

You can specify refinements to the way a function operates, simply by preceding optional operation arguments with a forward slash ("/"):

compute: func [x y /multiply /divide /subtract] [
    if multiply [return x * y]
    if divide   [return x / y]
    if subtract [return x - y]
    return x + y
]

compute/multiply 10 20
compute/divide 10 20
compute/subtract 10 20
compute 10 20

The "help" function provides usage information for any function, including user defined functions:

help for
help compute

You can include documentation for any user defined function by including a text string as the first item in it's argument list. This text is included in the description displayed by the help function:

doc-demo: func ["This function demonstrates doc strings"] [help doc-demo]
doc-demo

Acceptable data types for any parameter can be listed in a block, and doc strings can also be included immediately after any parameter:

concatenate-string-or-num: func [
    "This function will only concatenate strings or integers."
    val1 [string! integer!] "First string or integer"
    val2 [string! integer!] "Second string or integer"
] [
    join val1 val2
]

help concatenate-string-or-num
concatenate-string-or-num "Hello " "there."  ; this works correctly
concatenate-string-or-num 10 20              ; this works correctly
concatenate-string-or-num 10.1 20.3          ; this creates an error

6.7.1 Importing Code

You can "do" a module of code contained in any text file, as long as it contains the minimum header "REBOL [ ]" (this includes HTML files and any other files that can be read via REBOL's built-in protocols). For example, if you save the previous functions in a text file called "myfunctions.r":

REBOL []  ; THIS HEADER TEXT MUST BE INCLUDED AT THE TOP OF ANY REBOL FILE

sqr-add-var: func [num1 num2] [print square-root (num1 + num2)]
display: func [filename] [view layout [image load filename]]
cls: does [prin "^(1B)[J"]

You can import and use them in your current code, as follows:

do %myfunctions.r

; now you can use those functions just as you would any other
; native function:

sqr-add-var
display
cls

Here's an example function that plays a .wave sound file. Save this code as C:\play_sound.r:

REBOL [title: "play-sound"]  ; you can add a title to the header

play-sound: func [sound-file] [
    wait 0
    ring: load sound-file
    sound-port: open sound://
    insert sound-port ring
    wait sound-port
    close sound-port
]

Then run the code below to import the function and play selected .wav files:

do %/c/play_sound.r

play-sound %/C/WINDOWS/Media/chimes.wav
play-sound to-file request-file/file %/C/WINDOWS/Media/tada.wav

Imported files can contain data definitions and any other executable code, including that which is contained in additional nested source files imported with the "do" function. Any code or data contained in a source file is evaluated when the file is "do"ne.

6.8 Quick Review and Synopsis

The list below summarizes some key characteristics of the REBOL language. Knowing how to put these elements to use constitutes a fundamental understanding of how REBOL works:

  1. To start off, REBOL has hundreds of built-in function words that perform common tasks. As in other languages, function words are typically followed by passed parameters. Unlike other languages, passed parameters are placed immediately after the function word and are not necessarily enclosed in parenthesis. To accomplish a desired goal, functions are arranged in succession, one after another. The value(s) returned by one function are often used as the argument(s) input to another function. Line terminators are not required at any point, and all expressions are evaluated in left to right order, then vertically down through the code. Empty white space (spaces, tabs, newlines, etc.) can be inserted as desired to make code more readable. Text after a semicolon and before a new line is treated as a comment. You can complete significant work by simply knowing the predefined functions in the language, and organizing them into a useful order.
  2. REBOL contains a rich set of conditional and looping structures, which can be used to manage program flow and data processing activities. If, switch, while, for, foreach, and other typical structures are supported.
  3. Because many common types of data values are automatically recognized and handled natively by REBOL, calculating, looping, and making conditional decisions based upon data content is straightforward and natural to perform, without any external modules or toolkits. Numbers, text strings, money values, times, tuples, URLs, binary representations of images, sounds, etc. are all automatically handled. REBOL can increment, compare, and perform proper computations on most common types of data (i.e., the interpreter automatically knows that 5:32am + 00:35:15 = 6:07:15am, and it can automatically apply visual effects to raw binary image data, etc.). Network resources and Internet protocols (http documents, ftp directories, email accounts, dns services, etc.) can also be accessed natively, just as easily as local files. Data of any type can be written to and read from virtually any connected device or resource (i.e., "write %file.txt data" works just as easily as "write ftp://user:pass@website.com data", using the same common syntax). The percent symbol ("%") and the syntax "%(/drive)/path/path/.../file.ext" are used cross-platform to refer to local file values on any operating system.
  4. Any data or code can be assigned a word label. The colon character (":") is used to assign word labels to constants, variable values, evaluated expressions, functions, and data/action blocks of any type. Once assigned, variable words can be used to represent all of the data and/or actions contained in the given expression, block, etc. Just put a colon at the end of a word, and thereafter it represents all the following actions and/or data. That forms a significant part of the REBOL language structure, and is the basis for it's flexible natural language dialecting abilities.
  5. Multiple pieces of data are stored in "blocks", which are delineated by starting and ending brackets ("[]"). Blocks can contain data of any type: groups of text strings, arrays of binary data, collections of actions (functions), other enclosed blocks, etc. Data items contained in blocks are separated by white space. Blocks can be automatically treated as lists of data, called "series", and manipulated using built-in functions that enable searching, sorting, ordering, and otherwise organizing the blocked data. Data and function words contained in blocks can be evaluated (their actions performed and their data values assigned) using the "do" word. New function words can also be defined using the "does" and "func" words. "Does" forces a block to be evaluated every time its word label is encountered. The "func" word creates an executable block in the same way as "does", but additionally allows you to pass your own specified parameters to the newly defined function word. You can "do" a module of code contained in a text file, as long as it contains the minimum header "rebol[]". Blocks are also used to delineate most of the syntactic structures in REBOL (i.e., in conditional evaluations, function definitions, etc.).
  6. The syntax "view layout [block]" is used to create basic GUI layouts. You can add graphic widgets to the layout simply by adding widget identifier words to the enclosed block: "button", "field", "text-list", etc. Color, position, spacing, and other facet words can be added after each widget identifier. Action blocks added immediately after any widget will perform the enclosed functions whenever the widget is activated (i.e., when the widget is clicked with a mouse, when the enter key pressed, etc.). Path refinements can be used to refer to items in the GUI layout (i.e., "face/offset" refers to the position of the selected widget face). Those simple guidelines can be used to create useful GUIs for data input and output, in a way that's native (doesn't require any external toolkits) and much easier than any other language.

6.9 A Telling Comparison

To provide a quick idea of how much easier REBOL is than other languages, here's a short example. The following code to create a basic program window with REBOL was presented earlier:

view layout [size 400x300]

It works on every type of computer, in exactly the same way.

Code for the same simple example is presented below in the popular programming language "C++". It does the exact same thing as the REBOL one-liner above, except it only works in Microsoft Windows. If you want to do the same thing on a Macintosh computer, you need to memorize a completely different page of C++ code. The same is true for Unix, Linux, Beos, or any other operating system. You have to learn enormous chunks of code to do very simple things, and those chunks of code are different for every type of computer. Furthermore, you typically need to spend a semester's worth of time learning very basic things about coding format and fundamentals about how a computer 'thinks' before you even begin to tackle useful basics like the code below:

#include <windows.h>

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
char szClassName[ ] = "C_Example";

int WINAPI
WinMain (HINSTANCE hThisInstance,
         HINSTANCE hPrevInstance,
         LPSTR lpszArgument,
         int nFunsterStil)

{
    HWND hwnd;               
    /* This is the handle for our window */
    MSG messages;            
    /* Here messages to the application are saved */
    WNDCLASSEX wincl;        
    /* Data structure for the windowclass */

    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      
    /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 
    /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 
    /* No menu */
    wincl.cbClsExtra = 0;                      
    /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      
    /* structure or the window instance */
    /* Use Windows's default color as window background */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register window class. If it fails quit the program */
    if (!RegisterClassEx (&wincl))
        return 0;

    /* The class is registered, let's create the program*/
    hwnd = CreateWindowEx (
           0,                   
            /* Extended possibilites for variation */
           szClassName,         
            /* Classname */
           "C_Example",       
            /* Title Text */
           WS_OVERLAPPEDWINDOW, 
            /* default window */
           CW_USEDEFAULT,       
            /* Windows decides the position */
           CW_USEDEFAULT,       
            /* where the window ends up on the screen */
           400,                 
            /* The programs width */
           300,                 
            /* and height in pixels */
           HWND_DESKTOP,        
            /* The window is a child-window to desktop */
           NULL,                
            /* No menu */
           hThisInstance,       
            /* Program Instance handler */
           NULL                
            /* No Window Creation data */
           );

    /* Make the window visible on the screen */
    ShowWindow (hwnd, nFunsterStil);

    /* Run the message loop. 
        It will run until GetMessage() returns 0 */
    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages 
            into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }

    /* The program return-value is 0 - 
        The value that PostQuitMessage() gave */
    return messages.wParam;
}

/*  This function is called by the Windows 
        function DispatchMessage()  */

LRESULT CALLBACK
WindowProcedure (HWND hwnd, UINT message, 
    WPARAM wParam, LPARAM lParam)
{
    switch (message)                  
    /* handle the messages */
    {
        case WM_DESTROY:
            PostQuitMessage (0);       
                /* send a WM_QUIT to the message queue */
            break;
        default:                      
            /* for messages that we don't deal with */
            return DefWindowProc (hwnd, message, 
                wParam, lParam);
    }

    return 0;
}

Yuck. Back to REBOL...

7. More Essential Topics

7.1 Built-In Help and Online Resources

The "help" function displays required syntax for any REBOL function:

help print

"?" is a synonym for "help":

? print

The "what" function lists all built-in words:

what

Together, those two words provide a built-in reference guide for the entire core REBOL language. Here's a script that saves all the above documentation to a file. Give it a few seconds to run:

echo %words.txt what echo off   ; "echo" saves console activity to a file
echo %help.txt
foreach line read/lines %words.txt [
    word: first to-block line
    print "___________________________________________________________^/"
    print rejoin ["word:  " uppercase to-string word]  print "" 
    do compose [help (to-word word)]
]
echo off
editor at read %help.txt 4

You can use help to search for defined words and values, when you can't remember the exact spelling of the word. Just type a portion of the word (hitting the tab key will also show a list of words for automatic word completion):

? to-         ; shows a list of all built-in type conversions
? reques      ; shows a list of built-in requester functions
? "load"      ; shows all words containing the characters "load"
? "?"         ; shows all words containing the character "?"

Here are some more examples of ways to search for useful info using help:

? datatype!   ; shows a list of built-in data types
? function!   ; shows a list of built-in functions
? native!     ; shows a list of native (compiled C code) functions
? char!       ; shows a list of built-in control characters
? tuple!      ; shows a list of built-in colors (RGB tuples)
? .gif        ; shows a list of built-in .gif images

You can view the source code for built-in "mezzanine" (non-native) functions with the "source" function. There is a huge volume of REBOL code accessible right in the interpreter, and all of the mezzanine functions were created by the language's designer, Carl Sassenrath. Studying mezzanine source is a great way to learn more about advanced REBOL code patterns:

source help
source request-text
source view
source layout
source ctx-viewtop  ; try this:  view layout [image load ctx-viewtop/13]

7.1.1 The REBOL System Object, and Help with GUI Widgets

"Help system" displays the contents of the REBOL system object, which contains many important settings and values. You can explore each level of the system object using path notation, like this:

? system/console/history        ; the current console session history
? system/options
? system/locale/months
? system/network/host-address

You can find info about all of REBOL's GUI components in "system/view/VID":

? system/view/VID

The system/view/VID block is so important, REBOL has a built-in short cut to refer to it:

? svv

You'll find a list of REBOL's GUI widgets in "svv/vid-styles". Use REBOL's "editor" function to view large system sections like this:

editor svv/vid-styles

Here's a script that neatly displays all the words in the above "svv/vid-styles" block:

foreach i svv/vid-styles [if (type? i) = word! [print i]]

Here's a more concise way to display the above widgets, using the "extract" function:

probe extract svv/vid-styles 2

This script lets you browse the object structure of each widget:

view layout [
    text-list data (extract svv/vid-styles 2) [
        a/text: select svv/vid-styles value
        show a focus a
    ]
    a: area 500x250 
]

REBOL's GUI layout words are available in "svv/vid-words":

? svv/vid-words

The following script displays all the images in the svv/image-stock block:

b: copy [] 
foreach i svv/image-stock [if (type? i) = image! [append b i]]
v: copy [] foreach i b [append v reduce ['image i]]
view layout v

The changeable attributes ("facets") available to all GUI widgets are listed in "svv/facet-words":

editor svv/facet-words

Here's a script that neatly displays all the above facet words:

b: copy [] 
foreach i svv/facet-words [if (not function? :i) [append b to-string i]]
view layout [text-list data b]

Some GUI widgets have additional facet words available. The following script displays all such functions, and their extra attributes:

foreach i (extract svv/vid-styles 2) [
    x: select svv/vid-styles i
    ; additional facets are held in a "words" block:
    if x/words [
        prin join i ": "
        foreach q x/words [
            if not (function? :q) [prin join q " "]
        ]
        print ""
    ]
]

To examine the function(s) that handle any of the additional facets for the widgets above, type the path to the widget's "words" block, i.e.:

svv/vid-styles/TEXT-LIST/words

For more information on system/view/VID, see http://www.mail-archive.com/rebol-bounce@rebol.com/msg01898.html and http://www.rebol.org/ml-display-message.r?m=rmlHJNC.

It's important to note that you can SET any system value. Just use a colon, like when assigning variable values:

system/user/email: user@website.com

Familiarity with the system object yields many useful tools.

7.1.2 Viewtop Resources

The REBOL desktop that appears by default when you run the view.exe interpreter can be used as a gateway into a world of "Rebsites" that developers use to share useful code. Surfing the public rebsites is a great way to explore the language more deeply. All of the code in the rebol.org archive, and much more, is available on the rebsites. When typing at the interpreter console, the "desktop" function brings up the REBOL desktop (also called the "Viewtop"):

desktop

Click the "REBOL" or "Public" folders to see hundreds of interesting demos and useful examples. Source code for every example is available by right-clicking individual program icons and selecting "edit". You don't need a web browser or any other software to view the contents of Rebsites - the Viewtop and all its features are part of the REBOL executable. You can learn volumes about the REBOL language using only the resources built directly into the 600k interpreter!

For detailed, categorized, and cross-referenced information about built-in functions, see the REBOL Dictionary rebsite, found in the REBOL desktop folder REBOL->Tools (an HTML version is also available at http://www.rebol.com/docs/dictionary.html).

7.1.3 Online Documentation, The Mailing List and The AltME Community Forum

If you can't find answers to your REBOL programming questions using built-in help and resources, the first place to look is http://rebol.com/docs.html. Googling online documentation also tends to provide quick results, since the word "REBOL" is uncommon.

To ask a question directly of other REBOL developers, you can join the community mailing list by sending an email to rebol-request@rebol.com , with the word "subscribe" in the subject line. Use your normal email program, or just paste the following code into your REBOL interpreter (be sure your email account settings are set up correctly in REBOL):

send rebol-request@rebol.com "subscribe"

You can also ask questions of numerous gurus and regular users in AltME, a messaging program which makes up the most active forum of REBOL users around the world. Rebol.org maintains a searchable history of several hundred thousand posts from both the mailing list and AltME, along with a rich script archive. The REBOL user community is friendly, knowledgeable and helpful, and you will typically find answers to just about any question already in the archives. Unlike other programming communities, REBOL does not have a popular web based support forum. AltME is the primary way that REBOL developers interact. If you want to speak with others, you must download the AltME program and set up a user account (it's fast and easy to do). Just follow the instructions at http://www.rebol.org/aga-join.r.

7.2 Saving and Running REBOL Scripts

So far in this tutorial, you've been typing or copying/pasting code snippets directly into the REBOL interpreter. As you begin to work with longer examples and full programs, you'll need to save your scripts for later execution. Whenever you save a REBOL program to a text file, the code must begin with the following bit of header text:

REBOL []

That header tells the REBOL interpreter that the file contains a valid REBOL program. You can optionally document any information about the program in the header block. The "title" variable in the header block is displayed in the title bar of GUI program windows:

REBOL [
    title:  "My Program"
    author: "Nick Antonaccio"
    date:   29-sep-2009
]
view layout [text 400 center "Look at the title bar."]

The code below is a web cam video viewer program. Type in or copy/paste the complete code source below into a text editor such as Windows Notepad or REBOL's built-in text editor (type "editor none" at the REBOL console prompt). Save the text as a file called "webcam.r" on your C:\ drive.

REBOL [title: "Webcam Viewer"]

; try http://www.webcam-index.com/USA/ for more webcam links.

temp-url: "http://209.165.153.2/axis-cgi/jpg/image.cgi"
while [true]  [
    webcam-url: to-url request-text/title/default "Web cam URL:" temp-url
    either attempt [webcam: load webcam-url] [
        break
    ] [
        either request [
            "That webcam is not currently available." "Try Again" "Quit"
        ] [
            temp-url: to-string webcam-url
        ] [
            quit
        ]
    ] 
] 
resize-screen: func [size] [
    webcam/size: to-pair size
    window/size: (to-pair size) + 40x72
    show window
]
window: layout [
    across 
    btn "Stop" [webcam/rate: none show webcam]
    btn "Start" [
        webcam/rate: 0 
        webcam/image: load webcam-url 
        show webcam
    ]
    rotary "320x240" "640x480" "160x120" [
        resize-screen to-pair value 
    ]
    btn "Exit" [quit] return
    webcam: image load webcam-url  320x240 
    with [
        rate: 0
        feel/engage: func [face action event][
            switch action [
                time [face/image: load webcam-url show face]
            ] 
        ] 
    ] 
]
view center-face window

Once you've saved the webcam.r program to C:\, you can run it in any one of the following ways:

  1. If you've already installed REBOL on your computer, just double-click your saved ".r" script file (find the C:\webcam.r file icon in your file explorer (click My Computer -> C: -> webcam.r)). By default, during REBOL's initial installation, all files with a ".r" extension are associated with the interpreter. They can be clicked and run as if they're executable programs, just like ".exe" files. The REBOL interpreter automatically opens and executes any selected ".r" text file. This is the most common way to run REBOL scripts, and it works the same way on all major graphic operating systems. If you want other people to be able to run your scripts, just have them download and install the tiny REBOL interpreter - it only takes a few seconds.
  2. Use the built-in editor in REBOL. Type "editor %/c/webcam.r" at the interpreter prompt, or type "editor none" and copy/paste the script into the editor. Pressing F5 in the editor will automatically save and run the script. This is a convenient way to work with scripts, and enables REBOL to be its own simple, self contained IDE.
  3. Type "do %/c/webcam.r" into the REBOL interpreter.
  4. Scripts can be run at the command line. In Windows, copy rebol.exe and webcam.r to the same folder (C:\), then click Start -> Run, and type "C:\rebol.exe C:\webcam.r" (or open a DOS box and type the same thing). Those commands will start the REBOL interpreter and do the webcam.r code. You can also create a text file called webcam.bat, containing the text "C:\rebol.exe C:\webcam.r" . Click on the webcam.bat file in Windows, and it'll run those commands. In Unix, you can also run scripts at scheduled times with Cron. Just enter the path to the script.
  5. Use a program such as XpackerX to package and distribute the program. XpackerX allows you to wrap the REBOL interpreter and webcam.r program into a single executable file that has a clickable icon, and automatically runs both files. That allows you to create a single file executable Windows program that can be distributed and run like any other application. Just click it and run... (this technique is covered in the next section).
  6. Buy the commercial "SDK" version of REBOL, which provides the most secure method of packaging REBOL applications.

VERY IMPORTANT: To turn off the default security requester that continually asks permission to read/write the hard drive, type "secure none" in the REBOL interpreter, and then run the program with "do {filename}". Running "C:\rebol.exe -s {filename}" does the same thing . The "-s" launches the REBOL interpreter without any security features turned on, making it behave like a typical Windows program.

7.3 "Compiling" REBOL Programs - Distributing Packaged .EXE Files

The REBOL.exe interpreter is tiny and does not require any installation to operate properly. By packaging it, your REBOL script(s), and any supporting data file(s) into a single executable with an icon of your choice, XpackerX works like a REBOL 'compiler' that produces regular Windows programs that look and act just like those created by other compiled languages. To do that, you'll need to create a text file in the following format (save it as "template.xml"):

<?xml version="1.0"?>
<xpackerdefinition>
    <general>
        <!--shown in taskbar -->
        <appname>your_program_name</appname>
        <exepath>your_program_name.exe</exepath>
        <showextractioninfo>false</showextractioninfo>
        <!-- <iconpath>c:\icon.ico</iconpath> -->
    </general>
    <files>
        <file>
            <source>your_rebol_script.r</source>
            <destination>your_rebol_script.r</destination>
        </file>
        <file>
            <source>C:\Program Files\rebol\view\Rebol.exe</source>
            <destination>rebol.exe</destination>
        </file>
        <!--put any other data files here -->
    </files>
    <!-- $FINDEXE, $TMPRUN, $WINDIR, $PROGRAMDIR, $WINSYSDIR -->
    <onrun>$TMPRUN\rebol.exe -si $TMPRUN\your_rebol_script.r</onrun>
</xpackerdefinition>

Just download the free XpackerX program and alter the above template so that it contains the filenames you've given to your script(s) and file(s), and the correct path to your REBOL interpreter. Run XpackerX, and it'll spit out a beautifully packaged .exe file that requires no installation. Your users do not need to have REBOL installed to run this type of executable. To them it appears and runs just like any other native compiled Windows program. What actually happens is that every time your packaged .exe file runs, the REBOL interpreter and your script(s)/data file(s) are unzipped into a temporary folder on your computer. When your script is done running, the temporary folder is deleted.

Most modern compression (zip) applications have an "sfx" feature that allows you to create .exe packages from zip files. You can create a packaged REBOL .exe in the same way as XpackerX using just about any sfx packaging application (there are dozens of freeware zip/compression applications that can do this - use the one you're most familiar with). There is an explanation of how to use the NSIS install creator to make REBOL .exe's here.

To create a self-extracting REBOL executable for Linux, first create a .tgz file containing all the files you want to distribute (the REBOL interpreter, your script(s), any external binary files, etc.). For the purposes of this example, name that bundle "rebol_files.tgz". Next, create a text file containing the following code, and save it as "sh_commands":

#!/bin/sh
SKIP=`awk '/^__REBOL_ARCHIVE__/ { print NR + 1; exit 0; }' $0`
tail +$SKIP $0 | tar xz    
exit 0
__REBOL_ARCHIVE__

Finally, use the following command to combine the above script file with the bundled .tgz file:

cat sh_commands rebol_files.tgz > rebol_program.sh

The above line will create a single executable file named "rebol_program.sh" that can be distributed and run by end users. The user will have to set the file permissions for rebol_program.sh to executable before running it ("chmod +x rebol_program.sh"), or execute it using the syntax "sh rebol_program.sh".

7.4 Embedding Binary Resources and Using REBOL's Built In Compression

The following program can be used to encode external files (images, sounds, DLLs, .exe files, etc.) so that they can be included within the text of your program code. Use "load (data)" to make use of any text data created by this program:

REBOL [Title: "Simple Binary Embedder"]

system/options/binary-base: 64
file: to-file request-file/only
data: read/binary file
editor data

The example below uses a text representation of the image at http://musiclessonz.com/test.png, encoded with the program above:

picture: load 64#{
iVBORw0KGgoAAAANSUhEUgAAAFUAAABkCAIAAAB4sesFAAAAE3RFWHRTb2Z0d2Fy
ZQBSRUJPTC9WaWV3j9kWeAAAAU1JREFUeJztlzEOgzAQBHkaT7s2ryZUUZoYRz4t
e9xsSzTjEXIktqP3trsPcPPo7z36e4/+3qO/9y76t/qjn3766V/oj4jBb86nUyZP
lM7kidKZPFE6kydq/Pjxq/nSElGv3qv50vj/o59++hNQM6Z93+P3zqefAw12Fyqh
v/ToX+4Pt0ubiNKZPFE6Ux5q/O/436lkh6affvrpp38ZRT/99Ov6+f4tPPqX+8Ps
/meidCZPlM7kidKZPFE6kydKZ/JE6UyeKJ3JE6UzeaJ0Jk+UzuSJ0pk8UTMmvn8L
j/7l/nC7tIkonekLdXm9dafSmeinn376D/rpp5/+vv1GqBkT37+FR/9yf7hd2kSU
zuSJ0pk8UTqTJ0pn8kTpTJ4onckTpTN5onQmT5TO5InSmTxROpMnasbE92/h0b/Q
//jR33v09x79vUd/73XvfwNmVzlr+eOLmgAAAABJRU5ErkJggg==
}
view layout [image picture]

The program below allows you to compress and embed files in your code. This program will be referred to many times throughout the tutorial. Save it to a .r file so that it can be run later:

REBOL [Title: "Binary Resource Embedder"]

system/options/binary-base: 64
file: to-file request-file/only
if not file [quit]
uncompressed: read/binary file
compressed: compress to-string uncompressed
editor compressed
alert rejoin ["Uncompressed size:  " length? uncompressed
    " bytes.  Compressed size: " length? compressed " bytes."]

To use the compressed version of data created by the program above, use the following code:

to-binary decompress {compressed data}

For example:

image-compressed: load to-binary decompress 64#{
eJzrDPBz5+WS4mJgYOD19HAJAtL/GRgYdTiYgKzm7Z9WACnhEteIkuD8tJLyxKJU
hiBXJ38f/bDM1PL+m2IVDAzsFz1dHEMq5ry9u3GijKcAy0Fh3kVzn/0XmRW5WXGV
sUF25EOmKwrSjrrF9v89o//u+cs/IS75763Tv7ZO/5qt//p63LX1e9fEV0fu/7ap
7m0qZRIJf+2DmGZoVER5MQiz+ntzJix6kKnJ6CNio6va0Nm0fCmLQeCHLVMY1Ljm
TRM64HLwMpGK/334Hf4n+vkn+1pr9md7jAVsYv+X8Z3Z+M/yscIX/j32H7sl/0j3
KK+of/CX8/X63sV1w51WqNj1763MjOS/xcccX8hzzFtXDwyXL9f/P19/f0vxz4f2
OucaHfmZDwID+P7Hso/5snw8m+qevH1030pG4kr8fhNC4f/34Z89ov+vHe4vAeut
SsdqX8T/OYUCv9iblr++f67R8pp9ukzLv8YHL39tL07o+3pekn1h/dDVBgzLU/d3
9te/Lki4cNgBmA6/lO+J/RPdzty8Rr5y94/tfOxsX6/r8xJK0/UW9vlH93/9oAzR
e09yKIUBVbT9/br/U/m7x6CU98VAAJS2ZPPF/197eEDhtfs9vX9rDzc6/v3qzUyo
nJA/dz76Y77tHw+w3gXlbEMpDKihza/+7/o/c3+DU54tDwsobR2/fXR/qYXBiV8T
t3eDEmpA/d9LDASK0y/tnz+H/Ynmt78E1vti7lAKA6pouxz/X7v+uR045ZFdRE6x
1q21pG7NiSzx1f5R40pvvdNn+oB1P4Onq5/LOqeEJgCemFy1KQgAAA==
}
view layout [image image-compressed]

7.5 Running Command Line Applications

The "call" function executes commands in your computer's operating system (i.e., DOS and Unix commands). The example below opens Windows' Notepad to edit the "C:\YOURNAME.txt" text file created earlier (leaving out the /show option runs the program in a hidden window):

call/show "notepad.exe c:\YOURNAME.txt"

This next example opens Windows' Paint program to edit an image we downloaded earlier in the tutorial:

call/show "mspaint.exe c:\bay.jpg"

Here's an example that embeds an executable program into the code, decompresses, and writes the program to the hard drive, and then runs it with the call function:

program: load to-binary decompress 64#{
eJztF11sU2X03K4VqJsrkZJp6OzchhFJsx8qDB9od1fHdIO6ds7AgJX2jttyey/p
vWUjJuNnmNhMibzwaCSLi+EBE1ziGIkBGh0BSYTwwAMme9Dk4kgkgSiKcj3nu7es
QrKFhMUQOcn5+c7fd875+vXe27FJAg4AbIiGAQwWIwZMEbqTcmODN5xRdmRi6aoy
Z83YogngLlaNtV+s6kV7q9KelHeu9LYqQTXt7e/v97UqLcLuqKJIvriShnAIoJ0r
gXvPn+StlDAF5dyzHLwAdlw4TZ1Mm7oQvWDu7jKLslsxBc4KQ30bb9bMHF3F/D5j
MFAHEIbHD+cwb88s9riSEIjvK7EKogZs//bxAvQmYlqM5JsOUwHPWFgEAYDTvqTp
eYdy1Fn5Sh/O96h9nLrrDcD4IpQm7UOkWL/nt6MlqMvxrkl+GVWS7xqWalzDzqGz
9rbyD5ehpmnl+ezt3M/RSPe7Q9/ajeh5+9Ztm3vKh9xoM7SaimLUR18C2JKf+Kg2
APoJwzDOuiAF+hHU/pHXryObdLyP+y2kEhx7UaLfo0gq/RJa60/n88Ndrpz7FmqG
u5bk3L8zwdWXc0+jdOYXkn4lnYfW++/qOPLyDz7BfH3jTXVnplx949inhPvnSgw/
8RSIHM7P8PdSUYtxlxSkONE+o/u7EkNElMbpcuRKUhTjmLH/iHbDQQ7DHqL77zbh
oQxeRa9duBQHkRj+HnIdr7y/e178AvmmnHt5VQAmaNo59/EZ8QSJAY7EURJvMu2x
KipYj2CaEToYve2eYYiwl4rWY6jN8RWF5XtsuWSyhO7aJG8XXQFkNdWYIqIHK8nH
8FOSFJMoteEfZfQEo1SNCPCW2/BTjWK1uXkp9dDDegjrDqpkAUtiJhNp4ma3qUrx
MG6dqkyFMQ2ExQmaxgU2c/07D2ZJsCz3Q68Xh76Cvac2pZwi8jCO8rIZd4jielmc
uHxmsEMe1vMBZJf0YY8Pda95yH5p+tWrI86XMZbTE5a1gVlXFKyryeowp0Cy4Wf+
hdSrWGp26N008hW4XnS6/OBS7MnUVHoK0osoTV+22qF56c95qKdtZBzB66J/imSc
/Rmsg/KDdHFbA9O3RrZWByD/qPf1KTCwze3y2KCbn9vnP4ExoItiwr11zvncqq6+
oXGV//XVa5qCzXxL6M3ZfBfMZyFPBvywgD3FGDjLnGVl83o4T+HJAZ/PFxWTqrcj
GxerHljRqyL9sWXxqU2/nkHki1H4HDkvJeM7vZooeLdnNU2R10K34G1XdgveTmE7
vmv7fNDcFY1u3ABpNa5J6rZd9MouqGpjw6z1GLXn6vDxV/s9o1cYvcroNUanGP2J
UZ3RG4zeZPQ2o3cY/YtRqCdqZ3Qho6WMuhitYHQZ0pr6mRr21Zvv03VFuuMoX0Gd
VqT7BlupKFoXw8eo/8yynUR+HvEa4g3EPxEXYuwSxOWIaxADiGHEBKKGeADxCOIx
a1wXkE81zH/ut0OdG0LtjQ2+hCSBzLUKWoeSyErC+pickIQgfAmhgaSG319xPEvo
ioQ6Ld9D0CL04ddZQuknaxA4W1hRtXeySa0DXWM7BHjDFhHkhLUKYs2cJTcrA0H4
mmtXYgk+m1GVTBBOsVVbXJGDsNTWKexIqpqQ4aWYqgbps4LPCDFNMPcLYXQpldrC
g0bcVHcKcQ220DqyB4PTHYKWScZVgCGsw/LBEgHWsjYLZR2zRTMxWZUwfaFwOAot
SXVXTIuLM9V/ZeuSMw/UxW/s4KOF6W2GNjmp8Uo6rci8ImsZRVLxG+1hZWhgrlv6
/4F/ABcSIgQAEAAA
}
write/binary %program.exe program
call/show %program.exe

The "call" function has many options that allow you to monitor, control, and make use of output from external command line applications. Type "help call" into the REBOL interpreter for an introduction. For more information, see http://rebol.com/docs/shell.html.

7.6 Responding to Special Events in a GUI - "Feel"

REBOL's simple GUI syntax makes it easy for widgets to respond to mouse clicks. As you've seen, you can simply put the block of code you want evaluated immediately after the widget that activates it:

view layout [btn "Click me" [alert "Thank you for the click :)"]]

But what if you want your GUI to respond to events other than a mouse click directly on a widget? What if, for example, you want the program to react whenever a user clicks anywhere on the GUI screen (in a paint program, for example), or if you want a widget to do something after a certain amount of time has passed, or if you want to capture clicks on the GUI close button so that the user can't accidentally shut down an important data screen. That's what the "feel" object and "insert-event-func" function are used for.

Here's an example of the basic feel syntax:

view layout [
    text "Click, right-click, and drag the mouse over this text." feel [
        engage: func [face action event] [
            print action
            print event/offset
        ]
    ]
]

The above code is often shortened using "f a e" to represent "face action event":

view layout [
    text "Mouse me." feel [
        engage: func [f a e] [
            print a
            print e/offset
        ]
    ]
]

You can respond to specific events as follows:

view layout [
    text "Mouse me." feel [
        engage: func [f a e] [
            if a = 'up [print "You just released the mouse."]
        ]
    ]
]

This example demonstrates how to combine full screen mouse detection with normal mouse clicks on widgets. To do this, an invisible box the same size as the screen, with a feel event attached, is used for full screen detection. Then, other widgets are simply placed on top of it, starting over at the window origin:

print "Click anywhere in the window, then click the text."
view center-face layout [
    size 400x200
    box 400x200 feel [
        engage: func [f a e] [
            print a
            print e/offset
        ]
    ]
    origin 
    text "Click me" [print "Text clicked"] [print "Text right-clicked"]
    box blue [print "Box clicked"]
]

You can also assign timer events to any widget, as follows:

view layout [
    text "This text has a timer event attached." rate 00:00:00.5 feel [
        engage: func [f a e] [
            if a = 'time [print "1/2 second has passed."]
        ]
    ]
]

Here's a button with a time event attached (a rate of "0" means don't wait at all). Every 0 seconds, when the timer event is detected, the offset (position) of the button is updated. This creates animation:

view layout/size [
    mover: btn rate 0 feel [
        engage: func [f a e] [
            if a = 'time [
                mover/offset: mover/offset + 5x5
                show mover
            ]
        ]
    ]
] 400x400

Here's a little shooting game that uses a timer event to automate the movement of GUI graphics around the screen, check for collisions, and control other game operations:

REBOL [title: "VID Shooter"]

score: 0   speed: 20   fire: false
do game: [
    view center-face layout [
        size 600x440
        at 270x0 text join "Score: " score
        at 280x440 x: box 2x20 yellow
        at (as-pair 0 (random 300) + 30) y: btn 50x20 red "Enemy"
        at 280x420 z: btn 50x20 blue "Player"
        box 0x0 #"l" [z/offset: z/offset + 10x0 show z]
        box 0x0 #"k" [z/offset: z/offset + -10x0 show z]
        box 0x0 #" " [
            if fire = false [
                fire: true 
                x/offset: as-pair z/offset/1 440
            ]
        ]
        box 0x0 rate speed feel [
            engage: func [f a e] [
                if a = 'time [
                    y/offset: y/offset + 5x0
                    if y/offset/1 > 600 [
                        y/offset: as-pair -10 ((random 300) + 30)
                    ]
                    show y
                    if fire = true [x/offset: x/offset + 0x-20]
                    if x/offset/2 < 0 [
                        x/offset/2: 440 
                        fire: false
                    ]
                    show x
                    if within? x/offset y/offset 50x25 [
                        alert "Kablammmm!!!"
                        score: score + 1
                        speed: speed + 5
                        fire: false
                        unview
                        do game
                    ]
                ]
            ]
        ]
    ]
]

By updating the offset of a widget every time it's clicked, you can enable drag-and-drop operations:

view layout/size [
    text "Click and drag this text" feel [
        ; remember f="face", a="action", e="event":
        engage: func [f a e] [
            ; first, record the coordinate at which the mouse is
            ; initially clicked:
            if a = 'down [initial-position: e/offset]
            ; if the mouse is moved while holding down the button, 
            ; move the position of the clicked widget the same amount
            ; (the difference between the initial clicked coordinate
            ; recorded above, and the new current coordinate determined
            ; whenever a mouse move event occurs):
            if find [over away] a [
                f/offset: f/offset + (e/offset - initial-position)
            ]
            show f
        ]
    ]
] 600X440

Feel objects and event functions can be included right inside a style definition. The definition below allows you to easily create multiple GUI widgets that can be dragged around the screen. "movestyle" is defined as a block of code that's later passed to a widget's "feel" object, and is therefore included in the overall style definition (the remove and append functions have been added here to place the moved widget on top of other widgets in the GUI (i.e., to bring the dragged widget to the visual foreground)). You can add this "feel movestyle" code to any GUI widget to make it drag-able:

movestyle: [
    engage: func [f a e] [
        if a = 'down [
            initial-position: e/offset
            remove find f/parent-face/pane f
            append f/parent-face/pane f
        ]
        if find [over away] a [
            f/offset: f/offset + (e/offset - initial-position)
        ]
        show f
    ]
]

view layout/size [
    style moveable-object box 20x20 feel movestyle
    ; "random 255.255.255" represents a different random
    ;  color for each piece:
    at random 600x400 moveable-object (random 255.255.255)
    at random 600x400 moveable-object (random 255.255.255)
    at random 600x400 moveable-object (random 255.255.255)
    at random 600x400 moveable-object (random 255.255.255)
    at random 600x400 moveable-object (random 255.255.255)
    text "This text and all the boxes are movable" feel movestyle
] 600x440

The "detect" function inside a feel block is useful for constantly checking events. The following program constantly checks for mouse movements, and if the mouse is ever positioned over the button, the button is moved to a random position. This technique can be useful, for example, in video games controlled by mouse movement:

view center-face layout [
    size 600x440
    at 270x209 b: btn "Click Me!" feel [
        detect: func [f e] [
            ; The following line checks for any mouse movement:
            if e/type = 'move [
                ; This line checks if the mouse position is within the
                ; coordinates of the button (i.e., touching the button):
                if (within? e/offset b/offset 59x22) [
                    ; If so, move the button to a random position:
                    b/offset: b/offset + ((random 50x50) - (random 50x50))
                    ; Check if the button has been moved off screen:
                    if not within? b/offset -59x-22 659x462 [
                        ; If so, move back to the center of the window:
                        b/offset: 270x209
                    ]
                    ; Update the screen:
                    show b
                ]
            ]
            ; When using the detect function, always return the event:
            e
        ]
    ]
]

To handle global events in a GUI such as resizing and closing, "insert-event-func" is useful. The following example checks for resize events:

insert-event-func [
    either event/type = 'resize [
        alert "I've been resized"
        none   ; return this value when you don't want to
               ; do anything else with the event.
    ][
        event  ; return this value if the specified event
               ; is not found
    ]
]

view/options layout [text "Resize this window."] [resize]

You can use that technique to adjust the window layout, and specifically reposition widgets when a screen is resized:

insert-event-func [
    either event/type = 'resize [
        stay-here/offset:
            stay-here/parent-face/size - stay-here/size - 20x20
        show stay-here
        none   ; return this value when you don't want to
               ; do anything else with the event.
    ][
        event  ; return this value if the specified event
               ; is not found
    ]
]

view/options layout [
    stay-here: text "Resize this window."
] [resize]

To remove an installed event handler, use "remove-event-func". The following example captures three consecutive close events, and then removes the event handler, allowing you to close the GUI on the 4th try:

count: 1
evtfunc: insert-event-func [
    either event/type = 'close [
        if count = 3 [remove-event-func :evtfunc]
        count: count + 1
        none
    ][
        event
    ]
]

view layout [text "Try to close this window 4 times."]

For more information about handling events see http://www.rebol.com/how-to/feel.html, http://www.codeconscious.com/rebol/view-notes.html, and http://www.rebol.com/docs/view-system.html.

7.7 Common REBOL Errors, and How to Fix Them

Listed below are solutions to a variety of common errors you'll run into when first experimenting with REBOL:

1) "** Syntax Error: Script is missing a REBOL header" - Whenever you "do" a script that's saved as a file, it must contain at least a minimum required header at the top of the code. Just include the following text at the beginning of the script:

REBOL []

2) "** Syntax Error: Missing ] at end-of-script" - You'll get this error if you don't put a closing bracket at the end of a block. You'll see a similar error for unclosed parentheses and strings. The code below will give you an error, because it's missing a "]" at the end of the block:

fruits: ["apple" "orange" "pear" "grape"
print fruits

Instead it should be:

fruits: ["apple" "orange" "pear" "grape"]
print fruits

Indenting blocks helps to find and eliminate these kinds of errors.

3) "** Script Error: request expected str argument of type: string block object none" - This type of error occurs when you try to pass the wrong type of value to a function. The code below will give you an error, because REBOL automatically interprets the website variable as a URL, and the "alert" function requires a string value:

website: http://rebol.com
alert website

The code below solves the problem by converting the URL value to a string before passing it to the alert function:

website: to-string http://rebol.com
alert website

Whenever you see an error of the type "expected _____ argument of type: ___ ____ ___ ...", you need to convert your data to the appropriate type, using one of the "to-(type)" functions. Type "? to-" in the REBOL interpreter to get a list of all those functions.

4) "** Script Error: word has no value" - Miss-spellings will elicit this type of error. You'll run into it any time you try to use a word that isn't defined (either natively in the REBOL interpreter, or by you, in previous code):

wrod: "Hello world"
print word

5) If an error occurs in a "view layout" block, and the GUI becomes unresponsive, type "unview" at the interpreter command line and the broken GUI will be closed. To restart a stopped GUI, type "do-events". To break out of any endless loop, or to otherwise stop the execution of any errant code, just hit the [Esc] key on your keyboard.

6) "** User Error: Server error: tcp 550 Access denied - Invalid HELO name (See RFC2821 4.1.1.1)" and "** User Error: Server error: tcp -ERR Login failed.", among others, are errors that you'll see when trying to send and receive emails. To fix these errors, your mail server info needs to be set up in REBOL's user settings. The most common way to do that is to edit your mail account info in the graphic Viewtop or by using the "set-net" function (http://www.rebol.com/docs/words/wset-net.html). You can also set everything manually - this is how to adjust all the individual settings:

system/schemes/default/host: your.smtp.address
system/schemes/default/user: username
system/schemes/default/pass: password
system/schemes/pop/host: your.pop.address
system/user/email: your.email@site.com

7) Here's a quirk of REBOL that doesn't elicit an error, but which can cause confusing results, especially if you're familiar with other languages:

unexpected: [
    empty-variable: ""
    append empty-variable "*"
    print empty-variable
]

do unexpected
do unexpected
do unexpected

The line:

empty-variable: ""

doesn't re-initialize the variable to an empty state. Instead, every time the block is run, "empty-variable" contains the previous value. In order to set the variable back to empty, as intended, use the word "copy" as follows:

expected: [
    empty-variable: copy ""
    append empty-variable "*"
    print empty-variable
]

do expected
do expected
do expected

8) Load/Save, Read/Write, Mold, Reform, etc. - another point of confusion you may run into initially with REBOL has to do with various words that read, write, and format data. When saving data to a file on your hard drive, for example, you can use either of the words "save" or "write". "Save" is used to store data in a format more directly usable by REBOL. "Write" saves data in a raw, 'unREBOLized' form. "Load" and "read" share a comparable relationship. "Load" reads data in a way that is more automatically understood and put to use in REBOL code. "Read" opens data in exactly the format it's saved, byte for byte. Generally, data that is "save"d should also be "load"ed, and data that's "write"ed should be "read". For more information, see the following REBOL dictionary entries:

http://rebol.com/docs/words/wload.html

http://rebol.com/docs/words/wsave.html

http://rebol.com/docs/words/wread.html

http://rebol.com/docs/words/wwrite.html

Other built-in words such as "mold" and "reform" help you deal with text in ways that are either more human-readable or more natively readable by the REBOL interpreter. For a helpful explanation, see http://www.rebol.net/cookbook/recipes/0015.html.

9) Order of precedence - REBOL expressions are always evaluated from left to right, regardless of the operations involved. If you want specific mathematical operators to be evaluated first, they should either be enclosed in parenthesis or put first in the expression. For example, to the REBOL interpreter:

2 + 4 * 6

is the same as:

(2 + 4) * 6  ; the left side is evaluated first

== 6 * 6

== 36

This is contrary to other familiar evaluation rules. In many languages, for example, multiplication is typically handled before addition. So, the same expression:

2 + 4 * 6

is treated as:

2 + (4 * 6)  ; the multiplication operator is evaluated first

== 2 + 24

== 26

Just remember, evaluation is always left to right, without exception.

10) You may run into problems when copying/pasting interactive console scripts directly into the REBOL interpreter, especially when the code contains functions such as "ask", which require a response from the user before the remainder of the script is evaluated (each line of the script simply runs, as the pasting operation completes, without any response from the user, leaving necessary variables unassigned). To fix such interactivity problems when copying/pasting console code into the interpreter, simply wrap the entire script in square brackets and then "do" that block: do [...your full script code...]. This will force the entire script to be loaded before any of the code is evaluated. If you want to run the code several times, simply assign it a word label, and then run the word label as many times as needed: do x: [...your full script code...] do x do x do x .... This saves you from having to paste the code more than once. Another effective option, especially with large scripts, is to run the code from the clipboard using "do read clipboard://". This performs much faster than watching large amounts of text paste into the console.

7.7.1 Trapping Errors

There are several simple ways to keep your program from crashing when an error occurs. The words "error?" and "try" together provide a way to check for and handle expected error situations. For example, if no Internet connection is available, the code below will crash abruptly with an error:

html: read http://rebol.com

The adjusted code below will handle the error more gracefully:

if error? try [html: read http://rebol.com] [
    alert "Unavailable."
]

The word "attempt" is an alternative to the "error? try" routine. It returns the evaluated contents of a given block if it succeeds. Otherwise it returns "none":

if not attempt [html: read http://rebol.com] [
    alert "Unavailable."
]

To clarify, "error? try [block]" evaluates to true if the block produces an error, and "attempt [block]" evaluates to false if the block produces an error.

For a complete explanation of REBOL error codes, see: http://www.rebol.com/docs/core23/rebolcore-17.html.

8. EXAMPLE PROGRAMS - Learning How All The Pieces Fit Together

The examples in this section demonstrate how REBOL code is put together to create complete programs. The code is heavily commented to provide line-by-line explanations of how each element works. The recommended way to run the examples is to install REBOL on your computer, paste the code for each program into a text editor, save the code file as "(program_name).r" and then double click the icon for the text file you've created. With REBOL installed, any file with a ".r" extension will automatically run as if it's an .exe program. Downloadable Windows executables and screen shots of these examples are available at:

http://musiclessonz.com/rebol_tutorial/examples

Be sure to check out the hundreds of additional code examples available directly from rebsites on the desktop of the REBOL interpreter!

8.1 Little Email Client

The first example is a complete graphical email client that can be used to read and send messages:

; Every program requires a minimum header:

REBOL [Title: "Little Email Client"] 

; The line below creates the program's GUI window:

view layout [

    ; This line adds a text label to the GUI:

    h1 "Send Email:"

    ; This line adds a button to the GUI:

    btn "Server settings" [

        ; When the button is clicked, the following lines are run.
        ; These lines set all the email user account information
        ; required to send and receive email.  The settings are gotten
        ; from the user with the "request-text" function, and assigned
        ; to their appropriate locations in REBOL's system object:

        system/schemes/default/host: request-text/title "SMTP Server:"
        system/schemes/pop/host:     request-text/title "POP Server:"
        system/schemes/default/user: request-text/title "SMTP User Name:"
        system/schemes/default/pass: request-text/title "SMTP Password:"
        system/user/email: to-email request-text/title "Your Email Addr:"

    ]

    ; This line creates a text entry field, containing the default text
    ; "recipient@website.com".  The variable word "address" is assigned to
    ; this widget:

    address: field "recipient@website.com"

    ; Heres another text entry field, for the email subject line:

    subject: field "Subject" 

    ; This line creates a larger, multi-line text entry area for the body
    ; text of the email:

    body: area "Body"

    ; Here's a button displaying the word "send".  The functions inside
    ; its action block are executed whenever the button is clicked:

    btn "Send" [

        ; This line does most of the work.  It uses the REBOL "send" 
        ; function to send the email.  The send function, with its
        ; "/subject" refinement accepts three parameters.  It's passed the
        ; current text contained in each field labeled above (referred to
        ; as "address/text" "body/text" and "subject/text").  The
        ; "to-email" function ensures that the address text is treated as
        ; an email data value:

        send/subject (to-email address/text) body/text subject/text

        ; This line alerts the user when the previous line is complete:

        alert "Message Sent."

    ]

    ; Here's another text label:

    h1 "Read Email:"

    ; Here's another text entry field.  The user's email account info is
    ; entered here.

    mailbox: field "pop://user:pass@site.com"

    ; This last button has an action block that reads messages from a
    ; specified mailbox.  It only takes one line of code:

    btn "Read" [

        ; The "to-url" function ensures that the text in the mailbox field
        ; is treated as a URL.  The contents of the mailbox are read and
        ; displayed using REBOL's built-in text editor:

        editor read to-url mailbox/text
    ]
]

Here's the same code, without comments - it's very simple. Try pasting it directly into the REBOL interpreter:

REBOL [Title: "Little Email Client"]
view layout [
    h1 "Send Email:"
    btn "Server settings" [
        system/schemes/default/host: request-text/title "SMTP Server:"
        system/schemes/pop/host:     request-text/title "POP Server:"
        system/schemes/default/user: request-text/title "SMTP User Name:"
        system/schemes/default/pass: request-text/title "SMTP Password:"
        system/user/email: to-email request-text/title "Your Email Addr:"
    ]
    address: field "recipient@website.com"
    subject: field "Subject"
    body: area "Body"
    btn "Send" [
        send/subject to-email address/text body/text subject/text
        alert "Message Sent."
    ]
    h1 "Read Email:"
    mailbox: field "pop://user:pass@site.com"
    btn "Read" [
        editor read to-url mailbox/text
    ]
]

8.2 Simple Web Page Editor

The following program can be used to load, edit, and save HTML files (or any other text file) directly to/from a live web server or to/from a drive on your local computer. It requires 14 lines of code:

REBOL [Title: "Web Page Editor"]  ; required header

; Create a GUI window:

view layout [

    ; Here's a text entry field containing a generic URL address for
    ; the page to be edited.  The label "page-to-read" is assigned to
    ; this widget:

    page-to-read: field 600 "ftp://user:pass@website.com/path/page.html"

    ; Here's a multi-line text field to hold and edit the HTML
    ; downloaded from the above URL.  The label "the-html" is assigned
    ; to it:

    the-html: area 600x440

    ; The "across" words lays out the next buttons on the same line:

    across

    ; Here's a button to download and display the HTML at the URL given
    ; above:

    btn "Download HTML Page" [

        ; When the button is clicked, read the HTML at the URL above,
        ; insert it into the multi-line text area (by setting the text
        ; property of that field to the downloaded text), and update the
        ; display:

        the-html/text: read (to-url page-to-read/text)
        show the-html

    ]

    ; Here's another button to read and display text from a local file:

    btn "Load Local HTML File" [

        ; When the button is clicked, read the HTML from a file selected
        ; by the user, insert it into the multi-line text area, and update
        ; the display:

        the-html/text: read (to-file request-file)
        show the-html
    ]

    ; Here's another button.  When clicked, the edited contents of the
    ; multi-line text area are saved back to the URL:

    btn "Save Changes to Web Site" [
        write (to-url page-to-read/text) the-html/text
    ]

    ; Here's another button to write the edited contents of the multi-
    ; line text area to a local file selected by the user:

    btn "Save Changes to Local File" [
        write (to-file request-file/save) the-html/text
    ]
]

8.3 Card File

This is the quintessential simple text field storage application. It can be used as shown here, to save contact information, but by adjusting just a few lines of code and text labels, it could be easily adapted to store recipes, home inventory information, or any other type of related pages of data.

REBOL [title: "Card File"]

; The line below writes a new empty data file to the hard drive, if it
; doesn't already exist.  If the file DOES already exist, then this
; function simply writes an empty string to it (i.e., leaves the file
; alone):

write/append %data.txt ""

; This line loads all saved records from the database file:

database: load %data.txt


; Here's the GUI window:

view center-face gui: layout [

    ; Here's a text label to instruct the user:

    text "Load an existing record:"

    ; This text list displays an alphabetically sorted list of the
    ; names found in the database (every forth item).  The number
    ; pair indicates the widget's pixel size:

    name-list: text-list blue 400x100 data (sort extract database 4) [

        ; The following line is included to avoid potential errors.
        ; When an item in the text list is clicked, we first check that
        ; the selected data (represented by the word "value") is NOT
        ; equal to nothing.  If so, exit the widget's action block 
        ; (the "return" word quits the text-list's action routine):

        if value = none [return]

        ; The following code finds the selected name in the loaded
        ; database.  The display fields in the GUI are then set
        ; to show the found name, and each of the 3 items after
        ; it in the database (name = field 1, address = field 2,
        ; phone = field 3, notes = field 4):

        marker: index? find database value
        n/text: pick database marker
        a/text: pick database (marker + 1)
        p/text: pick database (marker + 2)
        o/text: pick database (marker + 3)

        ; Update the display to show the changed text fields (notice
        ; the "gui" label defined above - it refers to the entire GUI
        ; layout):

        show gui
    ]

    ; Here are the text display fields, and some text labels to show
    ; what should be typed into each field:

    text "Name:"       n: field 400
    text "Address:"    a: field 400
    text "Phone:"      p: field 400
    text "Notes:"      o: area  400x100

    ; The "across" word adds widgets to the GUI next to one another,
    ; instead of beneath one another, which is the default behavior
    ; (the following 3 buttons will appear next to each other):

    across

    ; Here's a GUI button to let the user save the contents of the
    ; text fields to the database:

    btn "Save" [

        ; When this button is clicked, make sure the required field
        ; contains some text.  If not, notify the user, and then exit
        ; this button's routine (the "return" word quits the save 
        ; button's action block):

        if n/text = "" [alert "You must enter a name." return]

        ; Now run through every forth item in the database to check if
        ; the name already exists.  If so, give the user the option to
        ; overwrite that record.  If they respond yes, delete the old
        ; record from the database ("remove/part" deletes 4 items at
        ; the location where the selected name is found).  If the user
        ; responds no, escape out of the save button's routine:

        if find (extract database 4) n/text [
            either true = request "Overwrite existing record?" [
               remove/part (find database n/text) 4
            ] [
               return
            ]
        ]

        ; Now update the database with the new data, and write it to
        ; the hard drive.  The "repend" function appends the evaluated
        ; variables inside the brackets (in this case a block of 4
        ; separate text strings contained in the GUI fields) to the
        ; database:

        save %data.txt repend database [n/text a/text p/text o/text]

        ; Update the text-list to show the added record:

        name-list/data: sort (extract copy database 4)
        show name-list
    ]

    ; This button allows the user to clear the screen and enter a
    ; new record:

    btn "Delete" [

        ; When this button is clicked, the code below gives the user
        ; the option to delete the selected record.  If the user
        ; selects "yes", the "remove/part" function deletes 4 items
        ; from the database, at the location where the selected name
        ; is found.  The database is saved, and the text fields are
        ; cleared ("do-face" runs the action block of the 
        ; "clear-button" widget above, to clear the GUI fields), then
        ; the name list is updated:

        if true = request rejoin ["Delete " n/text "?"] [
            remove/part (find database n/text) 4
            save %data.txt database
            do-face clear-button 1
            name-list/data: sort (extract copy database 4)
            show name-list
        ]
    ]

    clear-button: btn "New" [

        ; When this button is clicked, set the text of each field to an
        ; empty string:

        n/text: copy  ""
        a/text: copy  ""
        p/text: copy  ""
        o/text: copy  "" 

        ; As always, when any on data in the GUI is changed, the
        ; screen must be updated: 

        show gui
    ]
]

Here's the whole program, without comments:

REBOL [title: "Card File"]

write/append %data.txt ""
database: load %data.txt

view center-face gui: layout [
    text "Load an existing record:"
    name-list: text-list blue 400x100 data sort (extract database 4) [
        if value = none [return]
        marker: index? find database value
        n/text: pick database marker
        a/text: pick database (marker + 1)
        p/text: pick database (marker + 2)
        o/text: pick database (marker + 3)
        show gui
    ]
    text "Name:"       n: field 400
    text "Address:"    a: field 400
    text "Phone:"      p: field 400
    text "Notes:"      o: area  400x100
    across
    btn "Save" [
        if n/text = "" [alert "You must enter a name." return]
        if find (extract database 4) n/text [
            either true = request "Overwrite existing record?" [
               remove/part (find database n/text) 4
            ] [
               return
            ]
        ]
        save %data.txt repend database [n/text a/text p/text o/text]
        name-list/data: sort (extract copy database 4)
        show name-list
    ]
    btn "Delete" [
        if true = request rejoin ["Delete " n/text "?"] [
            remove/part (find database n/text) 4
            save %data.txt database
            do-face clear-button 1
            name-list/data: sort (extract copy database 4)
            show name-list
        ]
    ]
    clear-button: btn "New" [
        n/text: copy  ""
        a/text: copy  ""
        p/text: copy  ""
        o/text: copy  ""
        show gui
    ]
]

8.4 Little Menu Example

A module that produces full blown menus with all the bells and whistles is available at http://www.rebol.org/library/scripts/menu-system.r (covered later in this tutorial). Here's a simpler homemade example that can be included in your programs to provide basic menu functionality. It's constructed using only raw, native REBOL GUI components:

REBOL [Title: "Simple Menu Example"]

; "center-face" centers the GUI window:

view center-face gui: layout [

    size 400x300
    at 100x100 H3 "You selected:"
    display: field

    ; Here's the menu.  Make sure it goes AFTER other GUI code.
    ; If you put it before other code, the menu will appear be-
    ; hind other widgets in the GUI.  The menu is basically just
    ; a text-list widget, which is initially hidden off-screen
    ; at position -200x-200.  When an item in the list is 
    ; clicked upon, the action block for the text-list runs
    ; through a conditional switch structure, to decide what to
    ; do for the chosen item.  The code for each option first 
    ; re-hides the menu by repositioning it off screen (at 
    ; -200x-200 again).  For use in your own programs, you can 
    ; put as many items as you want in the list, and the action 
    ; block for each item can perform any actions you want.
    ; Here, each option just updates the text in the "display"
    ; text entry field, created above.  Change, add to, or 
    ; delete the "item1" "item2" and "quit" elements to suit 
    ; your needs:

    origin 2x2 space 5x5 across
    at -200x-200 file-menu: text-list "item1" "item2" "quit" [
        switch value [
            "item1" [
                face/offset: -200x-200
                show file-menu
                ; PUT YOUR CODE HERE:
                set-face display "File / item1"
            ]
            "item2" [
                face/offset: -200x-200
                show file-menu
                ; PUT YOUR CODE HERE:
                set-face display "File / item2"
            ]
            "quit" [quit]
        ]
    ]

    ; The menu initially just appears as some text choices at 
    ; the top of the GUI.  When the "File" menu is clicked,
    ; the action block of that text widget repositions the 
    ; text-list above, so that it appears directly underneath
    ; the File menu ("face/offset" is the location of the 
    ; currently selected text widget).  It disappears when
    ; clicked again - the code checks to see if the text-list
    ; is positioned beneath the menu.  If so, it repositions
    ; it out of sight.

    at 2x2
    text bold "File" [
        either (face/offset + 0x22) = file-menu/offset [
            file-menu/offset: -200x-200
            show file-menu
        ][
            file-menu/offset: (face/offset + 0x22)
            show file-menu
        ]
    ]

    ; Here's an additional top level menu option.  It provides
    ; just a single choice.  Instead of opening a text-list 
    ; widget with multiple options, it simply ensures that the
    ; other menu is closed (re-hidden), and then runs some code.

    text bold "Help" [
        file-menu/offset: -200x-200
        show file-menu
        ; PUT YOUR CODE HERE:
        set-face display "Help"
    ]
]

8.5 Loops and Conditions - A Simple Data Storage App

One of the most important applications of loop structures is to step through lists of data. By stepping through elements in a block, loops can be used to process and perform actions on each item in a given data series. This technique is used in all types of programming, and it's a cornerstone of the way programmers think about working with tables of data (such as those found in databases). Because many programs work with lists of data, you'll very often come across situations that require the use of loops. Thinking about how to put looping structures to use is a fundamental part of learning to write code in any language. The example below demonstrates several ways in which you'll see loops commonly put to use.

REBOL [title: "Loops and Conditions - a Simple Data Storage App"]

; First, a small user database is defined.  It's organized
; into a block structure:  the "users" block contains 5 
; blocks, which each contain 5 items of information for
; each user.  Blank items are represented with empty quotes.  

users: [
    ["John" "Smith" "123 Toleen Lane" "Forest Hills, NJ" "555-1234"]
    ["Paul" "Thompson" "234 Georgetown Pl." "Peanut Grove, AL" "555-2345"]
    ["Jim" "Persee" "345 Portman Pike" "Orange Grove, FL" "555-3456"]
    ["George" "Jones" "456 Topforge Court" "Mountain Creek, CO" ""]
    ["Tim" "Paulson" "" "" "555-5678"]
]

; This program does not have a GUI.  Instead, it's a text
; based "console" program.  Since there's no GUI, we need
; to format the output so that it's got a nice layout on the
; screen.  Here's a little function that uses a loop to draw
; a line.  It prints 65 dashes next to each other, and then
; a carriage return.  We'll use those lines to help print 
; nicely formatted output:

draw-line: does [loop 65 [prin "-"] print ""]

; Note that this is not the most efficient way to draw a line
; of characters, because the program needs to run through
; the loop every time a line is drawn.  You'll see some 
; flicker on the screen every time this happens, because
; the computer has to run through the "prin" function 65 
; times for each line.  Although it only takes a fraction of
; a second on a modern computer, it's still quite noticeable.
; It would be faster, instead, to build a block of characters
; once, and then print that block, as follows:
;
;        a-line: copy []
;        loop 65 [append a-line "-"]
;        ; remove the spaces and turn it
;        ; into a string of characters:
;        a-line: trim to-string a-line
;        ; now you can print "a-line"
;        ; anywhere you need it:
;        print a-line
;
; The inefficient code above is left in this example to 
; demonstrate a point about how the coding thought process
; can dramatically effect the performance of programs you
; create.  That's especially true for programs that perform
; complex loops on large lists of data.  The more efficient
; line printing function is implemented in another example
; following this one, to demonstrate the difference in its
; effectiveness.

; Next is a small function that prints out all of the data
; in the database.  It uses a foreach loop to cycle through
; each block of user data, and then it prints a line
; displaying each element in the block (items numbered 1-5 
; in each block).  This creates a nicely formatted display:

print-all: does [
    foreach user users [
        draw-line
        print rejoin ["User:     " user/1 " " user/2]
        draw-line
        print rejoin ["Address:  " user/3 "  " user/4]
        print rejoin ["Phone:    " user/5]
        print newline
    ]
]

; The following code uses a forever loop to continually
; request a choice from the user.  It uses several foreach
; loops to pull information from the data block, and a 
; conditional "switch" structure to decide how to respond
; to the user's request.  The "switch" inside a forever 
; loop is a common design in command line programs:

forever [

    ; First, print some nice formatting and display info:

    prin "^(1B)[J"  ; this code clears the screen.

    print "Here are the current users in the database:^/"
    ; The "^/" at the end of the line above prints a newline.

    draw-line  ; run the function defined above

    ; Now print the list of user names.  A foreach loop is 
    ; used to get the first and last name of each user in the
    ; database.  The first name is item 1 in each block, and
    ; the last name is item 2 in each block.  So for each 
    ; block in the database, "user/1" and "user/2" are 
    ; printed:

    foreach user users [prin rejoin [user/1 " " user/2 "  "]]
    print "" 
    draw-line

    ; print some instructions:

    prin "Type the name of a user below "
    print "(part of a name will perform search):^/"
    print "Type 'all' for a complete database listing."
    print "Press [Enter] to quit.^/"

    ; Now ask the user for a choice:

    answer: ask {What person would you like info about?  }
    print newline

    ; Decide what to do with the user's response:

    switch/default answer [

        ; If they typed "all", execute the "print-all"
        ; function defined earlier:

        "all" [print-all]

        ; If they typed the [Enter] key alone (""), print a
        ; goodbye message, and end the program.  Note that
        ; "ask" is used to display the message, instead of
        ; "print".  This allows the program to wait for the 
        ; user to press a key before ending the program:

        "" [ask "Goodbye!  Press [Enter] to end." quit]

        ; If neither of the choices above were selected, the
        ; default block below is executed (this is the last
        ; part of the switch structure):

        ][

        ; This section starts by creating a "flag" variable,
        ; which is used to track whether or not the user's 
        ; choice has been found in the database - the word 
        ; "found" is initially set to false to indicate that
        ; the user name has not yet been found:

        found: false

        ; Next, a foreach loop steps through each user block
        ; in the database:

        foreach user users [

            ; If the entered user name is found in the
            ; database (either the first or last name), the
            ; info for that user is printed out in a nicely
            ; formatted display, and the "found" flag is set
            ; to true.  The "rejoin" action is used to join
            ; the first name and last name, and is used in
            ; conjunction with the "find" action to check
            ; whether the user's answer matches any part of
            ; the names in the database (when you run this
            ; code, try entering single characters, or a 
            ; part of a name, to see what happens).

            if find rejoin [user/1 " " user/2] answer [
                draw-line
                print rejoin ["User:     " user/1 " " user/2]
                draw-line
                print rejoin ["Address:  " user/3 " " user/4]
                print rejoin ["Phone:    " user/5]
                print newline
                found: true
            ]
        ]

        ; If the "found" variable is still false after
        ; looping through the entire user database, then the
        ; user name was not found in the database.  Print a
        ; message to that effect:

        if found <> true [   ; "<>" means "not equal to"
            print "That user is not in the database!^/"]
    ]

    ; Wait for a user response, and then continue again at 
    ; the beginning of the forever loop:

    ask "Press [ENTER] to continue"
]

Here's the entire program without the comments. Try to follow the program flow on your own. NOTE: In this version, the inefficient "draw-line" function is replaced by the suggested "print a-line" routine above. As a result, you'll see a dramatic reduction in screen flicker:

Rebol []
users: [
    ["John" "Smith" "123 Tomline Lane" "Forest Hills, NJ" "555-1234"]
    ["Paul" "Thompson" "234 Georgetown Pl." "Peanut Grove, AL" "555-2345"]
    ["Jim" "Persee" "345 Pickles Pike" "Orange Grove, FL" "555-3456"]
    ["George" "Jones" "456 Topforge Court" "Mountain Creek, CO" ""]
    ["Tim" "Paulson" "" "" "555-5678"]
]
a-line: copy [] loop 65 [append a-line "-"]
a-line: trim to-string a-line
print-all: does [
    foreach user users [
        print a-line
        print rejoin ["User:     " user/1 " " user/2]
        print a-line
        print rejoin ["Address:  " user/3 "  " user/4]
        print rejoin ["Phone:    " user/5]
        print newline
    ]
]    
forever [
    prin "^(1B)[J"
    print "Here are the current users in the database:^/"
    print a-line
    foreach user users [prin rejoin [user/1 " " user/2 "  "]]
    print "" print a-line
    prin "Type the name of a user below "
    print "(part of a name will perform search):^/"
    print "Type 'all' for a complete database listing."
    print "Press [Enter] to quit.^/"
    answer: ask {What person would you like info about?  }
    print newline
    switch/default answer [
        "all"     [print-all]
        ""         [ask "Goodbye!  Press any key to end." quit]
        ][
        found: false
        foreach user users [
            if find rejoin [user/1 " " user/2] answer [
                print a-line
                print rejoin ["User:     " user/1 " " user/2]
                print a-line
                print rejoin ["Address:  " user/3 " " user/4]
                print rejoin ["Phone:    " user/5]
                print newline
                found: true
            ]
        ]
        if found <> true [
            print "That user is not in the database!^/"
        ]
    ]
    ask "Press [ENTER] to continue"
]

For some perspective, here's a GUI version of the same program that demonstrates how GUI and command line programming styles differ. Notice how much of the data handling is managed by the built-in GUI tools in the language, rather than by homemade loops:

REBOL [title: "Loops and Conditions - Data Storage App - GUI Example"]

users: [
    ["John" "Smith" "123 Tomline Lane" "Forest Hills, NJ" "555-1234"]
    ["Paul" "Thompson" "234 Georgetown Pl." "Peanut Grove, AL" "555-2345"]
    ["Jim" "Persee" "345 Pickles Pike" "Orange Grove, FL" "555-3456"]
    ["George" "Jones" "456 Topforge Court" "Mountain Creek, CO" ""]
    ["Tim" "Paulson" "" "" "555-5678"]
]
user-list: copy []
foreach user users [append user-list user/1]
user-list: sort user-list

view display-gui: layout [
    h2 "Click a user name to display their information:"
    across
    list-users: text-list 200x400 data user-list [
        current-info: []
        foreach user users [
            if find user/1 value [
                current-info: rejoin [
                    "FIRST NAME:  " user/1 newline newline
                    "LAST NAME:   " user/2 newline newline
                    "ADDRESS:     " user/3 newline newline
                    "CITY/STATE:  " user/4 newline newline
                    "PHONE:       " user/5
                ]
            ]
        ]
        display/text: current-info
        show display show list-users
    ]
    display: area "" 300x400 wrap
]

8.6 FTP Chat Room

This example is a simple chat application that lets users send instant text messages back and forth across the Internet. It includes password protected access for administrators to erase chat contents. It also allows users to pause activity momentarily, and requires a username/password to continue ["secret" "password"]. The chat "rooms" are created by dynamically creating, reading, appending, and saving text files via ftp (to use the program, you'll need access to an available ftp server: ftp address, username, and password. Nothing else needs to be configured on the server).

REBOL [title: "FTP Chat Room"]

; The following line gets the URL of a text file on the user's web server
; to use for the chat.  The ftp username, password, domain, and filename
; must be entered in the format shown:

webserver: to-url request-text/title/default {
    URL of text file on your server:} "ftp://user:pass@site.com/chat.txt"

; The following line gets the user's name:

name: request-text/title "Enter your name:"

; In the following line, the word "cls" is assigned to a function
; definition which clears the screen:

cls: does [prin "^(1B)[J"]

; The following line writes some text to the webserver file (obtained 
; above), indicating that the user has entered the chat.  The "/append"
; refinement adds to the existing text in the webserver file (as opposed
; to erasing what's already there).  Using "rejoin", the text written to
; the webserver is the combined value of the user's name, some static
; text, the current date and time, and a carriage return:

write/append webserver rejoin [now ": " name " has entered the room.^/"]

; Now the program uses a "forever" loop to continually wait for user
; input, and to do appropriate things with that input:

forever [

    ; First, read the messages that are currently in the "webserver" text
    ; file, and assign the variable word "current-chat" to that text:

    current-chat: read webserver 

    ; Clear the screen using the function word defined above:

    cls

    ; Display a greeting and some instructions:

    print rejoin [ 
        "--------------------------------------------------"
        newline {You are logged in as: } name newline 
        {Type "room" to switch chat rooms.} newline
        {Type "lock" to pause/lock your chat.} newline
        {Type "quit" to end your chat.} newline 
        {Type "clear" to erase the current chat.} newline 
        {Press [ENTER] to periodically update the display.} newline 
        "--------------------------------------------------" newline
    ]

    print rejoin ["Here's the current chat text at: " webserver newline]
    print current-chat

    ; In the line below, the "ask" function is used to get some text from
    ; the user.  The returned text (the text entered by the user) is
    ; assigned the label "entered-text", and concatenated with the user's
    ; name and the text " says: ".  This prepares it to be added to the
    ; webserver file and displayed in the chat.  Notice that the user
    ; must first respond to the "ask" function, before the rejoin
    ; evaluation can occur:

    sent-message: copy rejoin [
        name " says: "
        entered-text: ask "You say:  "
    ]

    ; The "switch" structure below is used to check for commands in the
    ; text entered by the user.  If the user enters "quit", "clear", 
    ; "room", or "lock", appropriate actions occur:

    switch/default entered-text [

        ; If the user typed "quit", stop the forever loop (exit the
        ; program):

        "quit" [break] 

        ; If the user typed "clear", erase the current text chat.  But
        ; first, ask user for the administrator username/password (using
        ; the "request-pass" function):

        "clear" [

            ; "if/else" does the same thing as "either" (deprecated):

            if/else request-pass = ["secret" "password"] [
                write webserver ""
            ] [
                alert {
                    You must know the administrator password to clear
                    the room!
                }
            ]
        ]

        ; If the user typed "room", request a new FTP address, and run
        ; some code that was presented earlier in the program, using the
        ; newly entered "webserver" variable, to effectively change chat
        ; "rooms":

        "room" [

            ; Add a message the chat file, indicating that the user has
            ; left the chat:

            write/append webserver rejoin [
                now ": " name " has left the room." newline
            ]

            ; Get the URL of a new chat text file (the new room address).
            ; Use the old address as the default displayed URL:

            webserver: to-url request-text/title/default {
                New Web Server Address:} to-string webserver

            ; Display a message in the newly chosen chat text file,
            ; showing that the user has entered the chat:

            write/append webserver rejoin [
                now ": " name " has entered the room." newline
            ]

        ]

        "lock" [

            ; Display a message to the user that the program will be
            ; paused:

            alert {The program will now pause for 5 seconds. 
                You'll need the correct username and password 
                to continue.
            }

            ; Assign a variable to the time 5 seconds from now:

            pause-time: now/time + 5  

            ; Don't go on until the user gets the password right:

            forever [

                ; First, wait 5 seconds:

                if now/time = pause-time [

                    ; The while loop below continually asks the user for
                    ; a password, until correct:

                    while [
                        request-pass <> ["secret" "password"]
                    ][
                        alert "Incorrect password - look in the source!"
                    ]

                    ; After the user has entered the correct username and
                    ; password, exit the forever loop and continue with
                    ; the program:

                    break
                ]
            ]
        ]
    ][

        ; The following line is the default case for the switch structure:
        ; as long as the entered message is not blank ([Enter]), write the
        ; entered message to the web server (append it to the current chat
        ; text):

        if entered-text <> "" [
            write/append webserver rejoin [sent-message newline]
        ]
    ]
]

; When the "forever" loop is exited, do the following:

cls print "Goodbye!" 
write/append webserver rejoin [now ": " name " has closed chat." newline]
wait 1

The bulk of this program runs within the "forever" loop, and uses the conditional "switch" structure to decide how to respond to user input (as in the "Loops and Conditions - A Simple Data Storage App" example). This is a classic outline that can be adjusted to match a variety of generalized situations in which the computer repeatedly waits for and responds to user interaction at the command prompt.

8.7 Image Effector

The next application creates a GUI interface, downloads and displays an image from the Internet, allows you to apply effects to it, and lets you save the effected image to the hard drive. In the mix, there are several routines which get data, and alert the user with text information.

; A header is still required, even if a title isn't included: 

REBOL [] 

; The following line creates a short list of image effects that are built
; into REBOL, and assigns the variable word "effect-types" to the block:

effect-types: [
    "Invert" "Grayscale" "Emboss" "Blur" "Sharpen" "Flip 1x1" "Rotate 90"
    "Tint 83" "Contrast 66" "Luma 150" "None"
]

; The code below imports the simple "play-sound" function created earlier
; in the tutorial.  For this to work correctly, the play_sound.r file
; should be saved to C:\.  The either condition checks to see if the file
; exists.  If so, it runs the code and sets a variable that we'll use
; later to decide whether or not to play a sound.  If the file doesn't
; exist, the variable is simply set to false:

either exists? %/c/play_sound.r [
    do %/c/play_sound.r
    sound-available: true
][
    sound-available: false
]

; The line below asks user for the URL of a new image (with a default
; location), and assigns that address to the word "image-url":

image-url: to-url request-text/title/default {
    Enter the URL of an image to use:} trim {
    http://rebol.com/view/demos/palms.jpg}

; Now a GUI block will be constructed, to be display later using
; "view layout":

gui: [

    ; This first line horizontally aligns all the following GUI widgets,
    ; so they appear next to each other in the layout (the default
    ; behavior in REBOL is to align elements vertically):

    across 

    ; This line changes the spacing of consecutive widgets so they're on
    ; top of each other:

    space -1 

    ; The following code displays the program menu, using a "choice"
    ; button widget (a menu-select type of button built into REBOL).
    ; The button is 160 pixels across, and is placed at the uppermost,
    ; leftmost pixel in the GUI (0x0) using the built-in word "at".

    at 20x2 choice 160 tan trim {
        Save Image} "View Saved Image" "Download New Image" trim {
            -------------} "Exit" [

        ; This is the action block for the choice selector.  It contains
        ; various functions to be performed, based on the choice selected
        ; by the user.  Conditional "if" evaluations are used to determine
        ; which actions to perform.  This could have been done with less
        ; code, using a "switch" structure.  "If" was used, however, to
        ; demonstrate that there are always alternate ways to express
        ; yourself in code - just like in spoken language:

        if value = "Save Image" [ 

            ; Request a filename to save the image as (defaults to
            ; "c:\effectedimage.png"):

            filename: to-file request-file/title/file/save trim {
                Save file as:} "Save" %/c/effectedimage.png

            ; Save the image to hard drive:

            save/png filename to-image picture 

        ]

        if value = "View Saved Image" [

            ; Request a file name from the user (defaults to
            ; "c:\effectedimage.png"):

            view-filename: to-file request-file/title/file {
                View file:} "" %/c/effectedimage.png

            ; Read the selected image from the hard drive and
            ; display it in a new GUI window:

            view/new center-face layout [image load view-filename]

        ]

        if value = "Download New Image" [

            ; Ask for the location of a new image, and assign the entered
            ; URL the word label "new-image":

            new-image: load to-url request-text/title/default trim {
                Enter a new image URL} trim {
                http://www.rebol.com/view/bay.jpg}

            ; Replace the old image with the new one:

            picture/image: new-image

            ; Update the GUI display:

            show picture

        ]

        if value = "-------------" []  ; don't do anything

        if value = "Exit" [

            ; If the variable we set earlier indicates that sound is
            ; available, play a little closing sound:

            if sound-available = true [
                play-sound %/c/windows/media/tada.wav
            ]

            ; Exit the program:

            quit

        ]         
    ]

    ; Here's another choice button which simply displays a little "about"
    ; message:

    choice tan "Info" "About" [
        alert "Image Effector - Copyright 2005, Nick Antonaccio"
    ] 

    ; The following line vertically aligns all successive GUI widgets -
    ; the opposite of "across":

    below 

    ; Spread out the following widgets by 5 pixels:

    space 5 

    ; Put 2 pixels of blank space before the next widget:

    pad 2 

    ; This box widget draws a line 550 pixels wide, 1 pixel tall (just a
    ; cosmetic separator):

    box 550x1 white

    ; Put some more space before the next widget:

    pad 10

    ; Here's a big text header for the GUI:

    vh1 "Double click each effect in the list on the right:"

    ; Advance to the next row in the GUI, and then begin arranging
    ; successive widgets across the screen again:

    return across

    ; Load the image entered at the beginning of the program, and give it
    ; a label:

    picture: image load image-url

    ; The code below creates a text-list widget and assigns a block of
    ; actions to it, to be run whenever the user clicks on an item in the
    ; list.  The first line assigns the word "current-effect" to the value
    ; which the user has selected from the list.  The second line applies
    ; that effect to the image (the words "to-block" and "form" are
    ; required for the way effects are applied syntactically.  The third
    ; line displays the newly effected image.  The "show" word is _very_
    ; important.  It needs to be used whenever a GUI element is updated:

    text-list data effect-types [
        current-effect: value 
        picture/effect: to-block form current-effect 
        show picture
    ]

]

; The following line displays the gui block above.  "/options [no title]"
; displays the window without a title bar (so it can't be moved around), 
; and "center-face" centers the window on the screen:

view/options center-face layout gui [no-title]

8.8 Guitar Chord Diagram Maker

This program creates, saves, and prints collections of guitar chord fretboard diagrams. It demonstrates some common and useful file, data, and GUI manipulation techniques, including the drag-and-drop "feel" technique, used here to slide the pieces around the screen. It also demonstrates the very important technique of printing output to HTML, and then previewing in a browser (to be printed on paper, uploaded to a web site, etc.). This is a useful cross-platform technique that can be used to view and print formatted hard copies of REBOL data:

REBOL [Title: "Guitar Chord Diagram Maker"]

; Load some image files which have been embedded using the "binary 
; resource embedder" script from earlier in the tutorial:

fretboard: load 64#{
iVBORw0KGgoAAAANSUhEUgAAAFUAAABkCAIAAAB4sesFAAAACXBIWXMAAAsTAAAL
EwEAmpwYAAAA2UlEQVR4nO3YQQqDQBAF0XTIwXtuNjfrLITs0rowGqbqbRWxEEL+
RFU9wJ53v8DN7Gezn81+NvvZXv3liLjmPX6n/4NL//72s9l/QGbWd5m53dbc8/kR
uv5RJ/QvzH42+9nsZ7OfzX62nfOPzZzzyNUxxh8+qhfVHo94/rM49y+b/Wz2s9nP
Zj+b/WzuX/cvmfuXzX42+9nsZ7OfzX4296/7l8z9y2Y/m/1s9rPZz2Y/m/vX/Uvm
/mWzn81+NvvZ7Gezn8396/4l2/n+y6N/f/vZ7Gezn81+tjenRWXD3TC8nAAAAABJ
RU5ErkJggg==
}

barimage: load 64#{
iVBORw0KGgoAAAANSUhEUgAAAEoAAAAFCAIAAABtvO2fAAAACXBIWXMAAAsTAAAL
EwEAmpwYAAAAHElEQVR4nGNsaGhgGL6AaaAdQFsw6r2hDIa59wCf/AGKgzU3RwAA
AABJRU5ErkJggg==
}

dot: load 64#{
iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAACXBIWXMAAAsTAAAL
EwEAmpwYAAAAFElEQVR4nGNsaGhgwA2Y8MiNYGkA22EBlPG3fjQAAAAASUVORK5C
YII=
}

; The following lines define the GUI design. The routine below was
; defined in the section about "feel":

movestyle: [
    engage: func [f a e] [
        if a = 'down [
            initial-position: e/offset
            remove find f/parent-face/pane f
            append f/parent-face/pane f
        ]
        if find [over away] a [
            f/offset: f/offset + (e/offset - initial-position)
        ]
        show f
    ]
]

; With that defined, adding "feel movestyle" to any widget makes it
; movable within the GUI.  It's very useful for all sorts of graphic
; applications.  If you want to pursue building graphic layouts that
; respond to user events, learning all about how "feel" works in REBOL
; is very important.  See the URL above for more info.

gui: [

    ; Make the GUI background white:

    backdrop white

    ; Show the fretboard image, and resize it (the saved image is
    ; actually only 85x100 pixels):

    currentfretboard: image fretboard 255x300

    ; Show the bar image, resize it, and make it movable.  Notice the
    ; "feel movestyle".  Thats' what enables the dragging:

    currentbar: image barimage 240x15 feel movestyle

    ; Some text instructions:

    text "INSTRUCTIONS:" underline
    text "Drag dots and other widgets onto the fretboard."
    across    
    text "Resize the fretboard:"

    ; "tab" aligns the next GUI element with a predefined column spacer:

    tab 

    ; The rotary button below lets you select a size for the fretboard.
    ; In the action block, the fretboard image is resized, and then the
    ; bar image is also resized, according to the value chosen.  This
    ; keeps the bar size proportioned correctly to the fretboard image.
    ; After each resize, the GUI is updated to actually display the
    ; changed image.  The word "show" updates the GUI display.  This
    ; needs to be done whenever a widget is changed within a GUI.  Be
    ; aware of this - not "show"ing a changed GUI element is an easily
    ; overlooked source of errors:

    rotary "255x300" "170x200" "85x100" [
        currentfretboard/size: to-pair value show currentfretboard
        switch value [
            "255x300" [currentbar/size: 240x15 show currentbar]
            "170x200" [currentbar/size: 160x10 show currentbar]
            "85x100" [currentbar/size: 80x5 show currentbar]
        ]
    ]

    return

    ; The action block of the button below requests a filename from the
    ; user, and then saves the current fretboard image to that filename:

    button "Save Diagram" [
        filename: to-file request-file/save/file "1.png"
        save/png filename to-image currentfretboard
    ]

    tab

    ; The action block of the button below prints out a user-
    ; selected set of images to an HTML page, where they can be
    ; viewed together, uploaded the Internet, sent to a printer,
    ; etc.    

    button "Print" [

        ; Get a list of files to print:

        filelist: sort request-file/title "Select image(s) to print:"

        ; Start creating a block to hold the HTML layout to be printed, 
        ; and give it the label "html":

        html: copy "<html><body>"

        ; This foreach loop builds an HTML layout that displays each of
        ; the selected images:

        foreach file filelist [
            append html rejoin [
                {<img src="file:///} to-local-file file {">}
            ]
        ]

        ; The following line finishes the HTML layout:  

        append html [</body></html>]

        ; Now the variable "html" contains a complete HTML document that
        ; can be written to the hard drive and opened in the default
        ; browser.  The code below accomplishes that:

        write %chords.html trim/auto html
        browse %chords.html 

    ]
]

; Each of the following loops puts 50 movable dots onto the GUI, all at
; the same locations.  This creates three stacks of dots that the user
; can move around the screen and put onto the fretboard.  There are three
; sizes to accommodate the resizing feature of the fretboard image.
; Notice the "feel movestyle" code at the end of each line.  Again,
; that's what makes the each of the dots dragable: 

loop 50 [append gui [at 275x50 image dot 30x30 feel movestyle]]
loop 50 [append gui [at 275x100 image dot 20x20 feel movestyle]]
loop 50 [append gui [at 275x140 image dot 10x10 feel movestyle]]

; The following loops add some additional dragable widgets to the GUI:

loop 6 [append gui [at 273x165 text "X" bold feel movestyle]]
loop 6 [append gui [at 273x185 text "O" bold feel movestyle]]

view layout gui

8.9 Shoot-Em-Up Video Game

This is a very simple graphic shoot-em-up which demonstrates important concepts required to make many types of 2D video games:

REBOL [title: "VID Shooter"]

; First, we'll set some initial values for variables that will be used
; throughout the game.  The "random/seed now/time" function ensures that
; random generated numbers will be truly random:

score: 0   speed: 10   lives: 5   fire: false   random/seed now/time

; This line provides the user with some instructions:

alert "[SPACE BAR: fire] | [K: move left] | [L: move right]"

; When certain events occur, we'll want to reload a completely fresh
; game screen.  A simple way to do that is to label the entire section
; of GUI code, unview the existing GUI, and then run that entire section
; of code again.  Here, we'll label the section "game", and run it
; initially with the "do" function.  You could also use this technique
; to implement a "play again" feature, for example.  To do that, you'd
; Simply wrap the entire program in a block, label it, and "do" it at
; the very beginning of the code.  To play again, just do the label
; again:

do game: [

    ; Here's the game window:

    view center-face layout [

        ; Set some layout properties:

        size 600x440
        backdrop black

        ; Display a simple text scoreboard:

        at 246x0 info: text tan rejoin ["Score: " score " Lives: " lives]

        ; For this game we'll use some generic buttons and boxes for
        ; graphics, but we could just as easily use images of any type
        ; (simply embed the images in code using the binary embedder
        ; program, label each image, and then use the "image" word to
        ; display them).  In the code below, the yellow box labeled "x" 
        ; is the missile, the orange button labeled "y" is the moving
        ; target being shot at, and the blue button labeled "z" is the
        ; player's graphic.  Notice that the target graphic is placed at
        ; an initial coordinate 50 pixels off to the left of the GUI,
        ; and at a random height between 30 and 330 pixels.  The
        ; "as-pair" function combines the two numbers into a coordinate:

        at 280x440 x: box 2x20 yellow
        at (as-pair -50 (random 300) + 30) y: btn 50x20 orange
        at 280x420 z: btn 50x20 blue

        ; The following boxes are invisible, as a result of their "0x0"
        ; size.  They exist here solely to perform actions when certain
        ; keys are pressed by the user (each box has a separate key
        ; assigned - their action blocks will execute whenever those
        ; keys are pressed by the user).  Notice that when the box
        ; assigned to the "l" key is activated, it moves the player
        ; graphic 10 pixels to the right.  The "k" key moves the player
        ; left, and the space bar is used to set some variables that
        ; will be used below to fire a missile:  

        box 0x0 #"l" [z/offset: z/offset + 10x0 show z]
        box 0x0 #"k" [z/offset: z/offset + -10x0 show z]
        box 0x0 #" " [

            ; The "fire" variable is used to track whether or not
            ; a missile is currently in the air.  When the space
            ; bar is pressed by the user, the following code is
            ; only run when a missile ISN'T currently moving:

            if fire = false [

                ; Set the missile currently firing flag to true:

                fire: true

                ; Set the missile position to be centered on the
                ; player graphic, at the bottom of the screen:

                x/offset: as-pair (z/offset/1 + 25) 440
            ]
        ]

        ; This box is also invisible.  It's only purpose is to enable a
        ; feel-engage-time routine.  The box has a rate set to our
        ; "speed" variable, and the feel-engage routine checks for a
        ; given amount of time to pass.  Every time that occurs (the
        ; number of times per second indicated by the "speed" variable),
        ; the enclosed block of code is run.  In effect, this works
        ; exactly like a forever loop, but WITHOUT stopping any of the
        ; other operations in the game:

        box 0x0 rate speed feel [
            engage: func [f a e] [
                if a = 'time [

                    ; If the "fire" variable is currently set to true,
                    ; move the missile up 30 pixels:

                    if fire = true [x/offset: x/offset + 0x-30]

                    ; If the missile reaches the top of the screen,
                    ; move it back down to the bottom (out of sight
                    ; below the bottom edge of the GUI), and set
                    ; the flag variable to false so that it stops
                    ; moving:

                    if x/offset/2 < 0 [x/offset/2: 440  fire: false]

                    ; Update the display:

                    show x

                    ; The code below moves the target piece.  The
                    ; randow X and Y portions of the coordinate move
                    ; the graphic generally from left to right, but
                    ; in a slightly unpredictable path that is a bit
                    ; harder to shoot at:

                    y/offset: y/offset + as-pair 
                        ((random 20) - 5) ((random 10) - 5)

                    ; Check if the target has made it all the way over
                    ; to the right side of the screen:

                    if y/offset/1 > 600 [

                        ; If so, decrease the number of lives by one:

                        lives: lives - 1

                        ; If no more lives are available, notify the user
                        ; and end the game:

                        if lives = 0 [
                            alert join "GAME OVER!!! Final Score: " score
                            quit
                        ]

                        ; Otherwise, notify the user and refresh the
                        ; screen (this automatically updates the score
                        ; board and resets the graphics to new starting
                        ; positions):

                        alert "-1 Life!"   unview   do game
                    ]

                    ; Update the display:

                    show y

                    ; Now that the graphics have been moved, check for
                    ; a collision between the missile and the target
                    ; (if the target and missile starting points are
                    ; within the specified distance):

                    if within? x/offset (y/offset - 5x5) 60x30 [

                        ; If a collision has occured, notify the user,
                        ; adjust the score and speed, set the fire
                        ; flag to false, and refresh the game screen:

                        alert "Ka-blammmm!!!"
                        score: score + 1   speed: speed + 5  fire: false
                        unview   do game
                    ]
                ]
            ]
        ]
    ]
]

Here's an even simpler game which demonstrates many of the same techniques. The point of the game is to catch the falling blocks. Move the player's piece left/right using the "O" and "P" keys. One point is added for each caught block. One point is subtracted for each missed block. Play as long as you'd like, and try to get the highest score. Speed can be adjusted manually using the "W" and "Q" keys:

REBOL [title: "Catch"]

alert "Arrow keys move left/right (up: faster, down: slower)"
random/seed now/time   speed: 11   score: 0
view center-face layout [
    size 600x440   backdrop white   across
    at 270x0 text "Score:"  t: text bold 100 (form score)
    at 280x20  y: btn 50x20 orange
    at 280x420 z: btn 50x20 blue
    key keycode [left] [z/offset: z/offset - 10x0  show z]
    key keycode [right]  [z/offset: z/offset + 10x0  show z]
    key keycode [up]  [speed: speed + 1]
    key keycode [down]  [if speed > 1 [speed: speed - 1]]
    box 0x0 rate 0 feel [engage: func [f a e] [if a = 'time [
        y/offset: y/offset + (as-pair 0 speed)  show y
        if y/offset/2 > 440 [
            y/offset: as-pair (random 550) 20   show y
            score: score - 1
        ]
        if within? z/offset (y/offset - 50x0) 100x20 [
            y/offset: as-pair (random 550) 20   show y
            score: score + 1
        ]
        t/text: (form score)  show t
    ]]]
]

8.10 Blogger

This program allows users to create and add entries to an online blog page. The GUI has text fields which allow the user to enter a title, link, and blog text, as well as a button to select an image file which will be uploaded and included in the blog entry. When the "Upload" button is clicked, an HTML file is created and uploaded to the user's web server, along with the image. Be sure to edit the ftp-url and html-url variables to represent actual user account information.

REBOL [title: "Blogger"]

; Store the blog file name in a variable:

page: "blog.html"

; Store the FTP account address, with username and password:

ftp-url: ftp://user:pass@site.com/public_html/folder/

; Store the blog's web page URL in a variable: 

html-url: join http://site.com/folder/ page

; Create a 1 pixel blank image to upload, for when the user
; doesn't want to upload any image for a given blog entry:

save/png %dot.png to-image layout/tight [box white 1x1]

; Here's the GUI:

view center-face gui: layout [

    ; Display the blog URL in the GUI (the "form" function converts
    ; the URL data type created above, to a string data type required
    ; by the h2 widget):

    h2 (form html-url)

    ; Here's a descriptive header and a text entry field where the user
    ; can enter a title for the blog.  The field is labeled by the
    ; variable word "t":

    text "Title:"       t: field 400

    ; Header and entry field for a URL link to be included in the blog:

    text "Link:"        l: field 400

    ; Header and selection button for an image to be included in the
    ; blog.  When the button is clicked, a file requestor pops up.  The
    ; button's text is set to display the selected file name:

    text "Image:"       i: btn 400 [i/text: request-file show i]

    ; Header and text entry field for the blog text:

    text "Text:"        x: area  400x100

    ; Layout all the following widgets across the screen:

    across

    ; Here's the GUI button which does most of the work:

    btn "Upload" [

        ; When the button is clicked, first try to read the existing
        ; blog text (HTML source).  If it's readable, assign that data
        ; the variable label "existing-text".  If it's not readable,
        ; create the folder and an empty blog file on the FTP server,
        ; then assign the "existing-text" variable an emptry string:

        if error? try [existing-text: read html-url] [
            make-dir ftp-url
            write (join ftp-url page) ""
            existing-text: copy ""
        ]

        ; Assign the variable word "picture" to the image file name
        ; chosen above ("last split-path" trims off the directory path
        ; portion of the file name (i.e., %/C/folder/myimage.jpg would
        ; be trimmed to %myimage.jpg)): 

        picture: last split-path to-file i/text

        ; Write the image to the FTP server:

        write/binary (join ftp-url picture) (read/binary to-file i/text)

        ; Write the following rejoined HTML to the blog file on the FTP
        ; server:

        write (join ftp-url page) rejoin [

            ; First add the title, enclosed in "h1" tags:

            {<h1>} t/text {</h1>}

            ; Now add a link to the uploaded picture, followed by 2
            ; HTML line break tags:

            {<img src="} picture {"><br><br>}

            ; Add the date and time, followed by 5 spaces, no line breaks:

            now/date { } now/time { &nbsp; &nbsp; }

            ; Add a link tag (because there were no line breaks above,
            ; this link appears on the same line as the date and time in
            ; the blog), followed by 2 line breaks:

            {<a href="} l/text {">} l/text {</a><br><br>}

            ; Put the blog text inside a table which takes up 80% of the
            ; screen.  Center the table on the screen (so it's indented).
            ; Use the "pre" tag to make sure that the text is formatted
            ; exactly the way it was typed in by the user (with carriage
            ; returns and spaces as entered into the area widget above),
            ; and use the "strong" tag to bold the text.  Follow that
            ; entire section of HTML with a line break and then a
            ; "horizontal rule" tag (a separator line):

            {<center><table width=80%><tr><td><pre><strong>}
                x/text 
            {</strong></pre></td></tr></table></center><br><hr>}

            ; Add the previously existing blog HTML below the new entry:

            existing-text

        ]

        ; After the blog has been updated, open the blog URL in the
        ; browser:

        browse html-url

    ]

    ; Add a GUI button to open the blog URL in the browser:

    btn "View" [browse html-url]

    ; This GUI button allows the user to edit the existing blog file
    ; (the HTML data created by this program), using REBOL's built in
    ; text editor.  Remember that REBOL's editor has the ability to
    ; read and SAVE data directly to/from FTP files, so this editor
    ; enables the user to actually edit and MAKE CHANGES to the blog
    ; file on the FTP server:

    btn "Edit" [editor (join ftp-url page)]

]

Here's the whole program, without comments:

REBOL [title: "Blogger"]

page: "blog.html"
ftp-url: ftp://user:pass@site.com/public_html/folder/
html-url: join http://site.com/folder/ page
save/png %dot.png to-image layout/tight [box white 1x1]  ; blank image

view center-face gui: layout [
    h2 (form html-url)
    text "Title:"       t: field 400
    text "Link:"        l: field 400
    text "Image:"       i: btn 400 [i/text: request-file show i]
    text "Text:"        x: area  400x100
    across
    btn "Upload" [
        if error? try [existing-text: read html-url] [
            make-dir ftp-url
            write (join ftp-url page) ""
            existing-text: copy ""
        ]
        picture: last split-path to-file i/text
        write/binary (join ftp-url picture) (read/binary to-file i/text)
        write (join ftp-url page) rejoin [
            {<h1>} t/text {</h1>}
            {<img src="} picture {"><br><br>}
            now/date { } now/time { &nbsp; &nbsp; }
            {<a href="} l/text {">} l/text {</a><br><br>}
            {<center><table width=80%><tr><td><pre><strong>}
                x/text 
            {</strong></pre></td></tr></table></center><br><hr>}
            existing-text
        ]
        browse html-url
    ]
    btn "View" [browse html-url]
    btn "Edit" [editor (join ftp-url page)]
]

8.11 Listview Multi Column Data Grid Example

This example uses the listview module found at http://www.hmkdesign.dk/rebol/list-view/list-view.r. The listview module handles all the main work of displaying, sorting, filtering, altering, and manipulating data, with a familiar user interface that's easy to program. Documentation is available at http://www.hmkdesign.dk/rebol/list-view/list-view.html.

Clicking on a column header in the example below sorts the data by the selected column, ascending or descending. Clicking the diamond in the upper right hand corner returns the data to its unsorted order. Selecting a row of data with the mouse allows each cell to be edited directly. Because inline editing is possible, no additional GUI widgets are required for data input/output. That makes the listview module a very powerful tool which is useful in a wide variety of situations.

REBOL [title: "Listview Data Grid"]

; The function below watches for the GUI close button, to keep
; the program from being shut down accidentally.  The code was
; adjusted from an example at: 
; http://www.rebolforces.com/view-faq.html

evt-close: func [face event] [
    either event/type = 'close [
        inform layout [
            across
            Button "Save Changes" [
                ; when the save button is clicked, a backup data
                ; file is automatically created:
                backup-file: to-file rejoin ["backup_" now/date]
                write backup-file read %database.db
                save %database.db theview/data quit
            ]
            Button "Lose Changes" [quit]
            Button "CANCEL" [hide-popup]
        ] none ] [ 
        event
    ]
]
insert-event-func :evt-close

; Download and import/run ("do") the list-view.r module:

if not exists? %list-view.r [write %list-view.r read
    http://www.hmkdesign.dk/rebol/list-view/list-view.r
]
do %list-view.r

; The following conditional evaluation checks to see if a
; database file exists.  If not, it creates a file with 
; some empty blocks:

if not exists? %database.db [write %database.db {[][]}]

; Now the stored data is read into a variable word:

database: load %database.db

; Here's the guts of the program.  Be sure to read the 
; list-view documentation to see how the widget works.

view center-face gui: layout [
    h3 {To enter data, double-click any row, and type directly 
        into the listview.  Click column headers to sort:}
    theview: list-view 775x200 with [
        data-columns: [Student Teacher Day Time Phone 
            Parent Age Payments Reschedule Notes]
        data: copy database
        tri-state-sort: false
        editable?: true
      ]
    across
    button "add row" [theview/insert-row]
    button "remove row" [
        if (to-string request-list "Are you sure?" 
                [yes no]) = "yes" [
            theview/remove-row
        ]
    ]
    button "filter data" [
        filter-text: request-text/title trim {
            Filter Text (leave blank to refresh all data):}
        if filter-text <> none [
            theview/filter-string: filter-text
            theview/update
        ]
    ]
    button "save db" [
        backup-file: to-file rejoin ["backup_" now/date]
        write backup-file read %database.db
        save %database.db theview/data
    ]
]

This example downloads the list-view module from the Internet, and then imports it from the hard drive. The following lines require that there is either an Internet connection available, or that the list-view.r module already exists on the user's hard drive:

if not exists? %list-view.r [write %list-view.r read
    http://www.hmkdesign.dk/rebol/list-view/list-view.r
]
do %list-view.r

If you want to use the list-view.r module in a script, without having to download it or include it a separate file (so that no external dependencies are required to run the script), you can replace the above lines with the following code. The following code is the list-view.r file, compressed using "compress read http://www.hmkdesign.dk/rebol/list-view/list-view.r":

do decompress #{
789CCD3D6B73E3C871DFF52BE6369592F67629905A9F73A6B2A7B2EFECD8E557
2AE738A96221551031142181040D8092B8A9FC97FCD474F7BCBA07038ADA3B3B
E6D56909CCABA7BBA75FD333FCB75FFEE28FBF538B33A5FE54F5B59EAB377FFE
CD77EA77BFF9FE4F933FFFE697FFA17E552CF51B28FD558585FF58575D3F79AC
F4D3650B2F7FBEEFD74D3B578B37BFD6DBB67A50BFAF1E0A5DABDFB6504D6F3B
BD7D9343B56F9BDDA1ADEED63DF47E359D7EF55EC1DF9FAA89FAF5EF7FABBED3
5D75B7C521BE6D75D1EB728EA55F4D665793AB9FC1DBEFE01DBDFAE9643A9B5C
CDE0D59F75DB55CD76AEA697D3CBABAFE1CDEFAA250E3757FF0D0F4AFDE2FBEF
D4C5D3D3D365B383D7CDBE5DEACBA6BDCB6A53ADCB6EBB72621F2E77EBDD5B6A
F5EF9D5645AF0E505F354F5B059378B88492FF81FFFF75DFEE1A1AE05FF456B7
45AD76E68D428C2042D453D5AFD5A6D81ED40AE6B16F75A7564DABF650A7DA2A
C0EA2576F487A6F760FE695D411D40AC827F8BC7A2AA8BDB1A619853F1BAEF77
F32CC379AC370F25E1E9B27CC85A7DDBD499A74426690218D39B4615DB52F5BA
EB9705CCF787765E428FAEF36609DDB59A7509B3DB140FBA6C965738E30DE0F0
F3A7D03FF79FD972DD6FEACF6B3A013AF44D7BF8FCC17D0F1608A4F4AFCDAB39
2D2EA5BED740F71FDC717E969F9D75FDA1AE3EE96C53C02A6BA97F6AB0AA740D
CBE7177FFC4FC38C66E00EAAC25279BE9AD2A32EEFE01109465FD5C294CF9E67
4AAF567A098BF4BCBAD58FB08C974D8DABFBEA27B0CACCFF3975317C4FAF57CD
161A6F9BADA6C75DD1167620FCAA164F6DB1BB99AB555177DA74B4AA8BBB0E10
4480ABBEB8BDD5A56A352C9EAD6AB693FD76D52CF71D30D86EDFDB165AD7B6D3
65FF3CE9F5739FE9B2EAD56D050C6F26ACA08BB22D9E60A8FD7609BD830853C5
B257B05E735745A96AA58ABA560B605FAA714ED3EAD46DDD2C1F6EE85D665EE5
2AB452AC60AE76D5F281D7343DFDF337808B65514FF0C9B7CCCFE4BFA5EE09DB
1E614A358FBA152FF4F6AE4072451301EA6C7B3695FA71AEEE748F2B713537F0
00CAA1CE24FEAECED9836FDF01BB2CD7D4359F69895290BF008080AF80E5F45F
F6456D70A4904DBFCCC284D562BFAD51DA1375F2A8036413A069B66D26DDBA79
A22EF2A886E9710980027A9AD5AAD3D05733A117664C4240668AA2C6BE57F69E
0F80388E60024ED836FDC44E8A8DFEF2E083D999BE6C276B40834145D7172D54
4EBE9EF321635CA841577A5B8A16F3410BF56AA4A5D12611C7BF3FE843CC18B0
0889E06C2CCF93C478C05DA0B463C41BA0B0BF8FEA1FDEFCD7E4CD00A382E506
70C3B2D7CFC089B8FC138CBFD5C349E1A78695D5AF6F469AC420E05250AB0C06
A9BAF584A69A980DA068A42148B663AD4E40B9141E3962D5C93F75DEE97A7566
CA480FE0DB97D48011D62448F1AB9FB2D50633608802D6BF91460A358EB6DF0B
6043B0BDCE6BBDEA9D2AB8AD8BE5C35900F015B29F8404BDE95B90726801BA8A
F4B62DB64102EE8AAA9D29FC7BA5B21A458E6AD50EDE5C0539B39BC1C8801F56
D7955C214CCF891298C2128C54B5707846CB0D2ACE32EC1BFECC184F60D901CB
AEB0EC0ACB2A3024617DF7601201404537C1CEA18383A79B6A197A56FBBAB654
62EA72ABD9235374F8D50F6F5404D307CD63EB50B10245404A4EFD78AA019628
8EC019BB7ECC108AC952FB59505F7E56AC2E6958D7A06D9EEC0AC065411A3B51
543FF22500E353E78139A2D579ADBE47D1D5374DADFA6A37B27AAEDD572B4D08
AEF056D124091898172C0754E1386C59F4C5B5AA9BED1DC8DD667FB70E6D72D1
DEB49D23136BFE7E25D06DBB9F87CE79DD6BB009EAE26015231FC9F58EEC11DB
11A336422FF9023919987355FEC8EC615029308116D404451108F5B0922D4780
83B440003FAA733230ECF7A2063F039E39F1A5FDB4906C01E3A04C4517E48678
6C5D959AC46CCC3F25C377C43DB05AE67EBD9EA64B18ED784F82B194F80029B2
6E5DAD62AD4BA800C0417E832538D934A5464C6CF6755FA991F7B852621BC154
45C64A16401FBA9C80A4DE6FA4EA19DA66D0C0CA5B12876047EF61C2D63C8C20
1AEA3A03F86258A01C8A13255285C31868DA7878BBC41CC6E76B3E40D3C1FB7C
685779741E8518A47B18EEE8349C49319842B2769A8992A0C6CF4EC1396265B3
F0F52AAE6B55DE297523E22FE2719D92EC1A5075963586988BA8899527F48E34
E66BAA5F0DAAC7101DD3D2BC0E703294B6465727C8C1D5B7C302D3E202AE6C7E
1892ECB845878A0298B2AB6EABBAEA0FA0AB9461695528E43F80B05096B76F01
BFBAEFABED9D0279026F9BB6ACB6A0F8B0D527DD36D09769F5D4ECC1695E178F
9A6A3E633B57E9BDEAF6E8D07560F0FD945AD8EEA3468774A30FCF5368D46378
CA34D075B5B15040ABAD06371DF1EA56E135859B6E8B4EDB89811EEF804F6A5D
1FC09167B3E8A82621B8734380BAC3316E01E5A0C43072A61E4D84EFF2F2122A
B560FA57184E6BF5535B010CCD8AC020610CCF6D417643DBEC016DB1A9FF1253
0FC58BF5EA8792084898CD86CD9BBA9C5844CC5F12C5B1F21F679A11971C3FC6
2D77961559FC43AB8ABF068B2A9EB6D3B6A11F7C33D2575C34E82F02DD982FE9
6234188CC2DFEFD4A2450F83D011A935F20CFBAC6CF6A0DE27CB1A293270DA53
96C6005B29854CE4307D53D77C76290FCE2162A40914BD20A89C99B42A85D992
1BF50FF81AD82D273A82F41759095C3BE32E2146A7DC1B442E9EAB5FFDFCDB5F
7277705D8238873F93DB7E6BBEAC2A30DFFDD3B269B7DA96D65D4F7F262B0C2F
764B68A94BEEBB18A7F1C314BD4B034659B5FD01FD38E8D41AC5F89A50C71A8E
0423A7CF53E759CE7E32BDB4FFC7F14933F96B0A21BC574D59BEB7D2E73D88A2
E5C31D08846D6986A5E01C389B2C64A9AEAEE0DF0FF0FFD554CDBE867FA730CA
D7533EA20877960D88A08535F3705E18B76C3BEB037760A417E1C1B4EC76C512
24F9C4F600D2AE2D6D0D2A47D181C47322C60AA9C42B60C06AA9DDD3B2814712
B27327D1B02B29AF4C90883FAF7551A2DFE69E3D19B0F15355F66B78E9FEDD3D
FB57A21E460BE09DFD2714451EA12FE80F3BCD2B3A478EFBF8655BDCF1E755D5
33A601866BEA1ABA2778E6C8E293B536FB49369A0153F2D0AE2786E9A69C04C4
533C9EE011ED31081A0698CF2B8181562013C03D300EB66B99C38F9C402B9A55
5C4FCC3C78BD60557828E815DAD748DAA25B824B087FB60DBEB735820D8E753A
985DADADED8D7641B06B0DB083568E1179479E0CBE5F7AB0668A2D78B4BEA7AF
87EB1B57406818E60012C01802AE529899E5830AC39FBD9E60012337185DDB3E
3069C096779E85C6F55F3CA64D1D74970DC5AB12D06B14B7571782DB802ACDD3
0D0301390CB51217713D182E456BF8DF8AACEED0F57A93B9A2CC44417EFC685A
B1240368821EBE67946AB323BC19B7DFB8B6CDED3DE0FC8BDC3976A6A67E3635
8D00B390D1046CFDC00F2BD68C948799C06351EFB5FD3E658BC58DDE7DCA9DE8
EE3EA9FD0EA071513EC26D2C3C0DC2A109AD0858CCADCE1764368096C9D9F085
E5A028463457914982032455B30B8E0F43B7731545652DB1A901ED019168BE71
B063549F74387ED19B1DA837921F14EE777B466635C05B9884D54EDBE2B1BA33
162A228BA4F699957250DBF0AE208D0B789B31409881890B4BA5459F60E1D97D
C6E2210497F351C3CA083ECB2CC4210DF2199D77AD7E044FE74E7350E8253C67
DD43B523CA077E60CD8C394B3CC06AD21F1B7B22D670ABE1D3DC4D2E5426BEC9
171EC2E45CFC4C8FE286194CE8F3CE5C733581A1830565D5368260FC4EE674BA
165B7D87DE4E34E724FEB6A0EFFE8E118181021B9948CCF39DC40C6B6807409F
F8E638AE60C94B039B0CA15091DBB1B2E2098460E025CDE0117AC4FCEC8834CE
CFA4B35F5A8C0CC085503C6C62FEFDA9241A92274D9131B40A1C48CF00DDF63E
E84A3133E79378960739B64293F9668C18E8AE582CF1419CA524442522E5C637
74C2D020011110C942B22C3986AC85967AF7E68D159F5444115CBB3B445B1F45
DF23CA7153268496785CA971E6B9EB5CA24720250CE2E722DA5AE3CDC7C5C40B
D0D467D7185AA76885896C204B6816F2C0221E1B6A566AB9060B887044639890
09E86B1B13DAB8584EB7D3CB6A552DCF184060FE16308752AF0A30032795AAD4
93B23C073A1BDEA9BB8961B8208E1273F17112CBC481A45110DDF536F76C0C64
DA156D87012BC7ED92F7597E83EFDF75A3BE5153B138AE0943B4606DD8E7B6EA
3168B5D9C36AB8B586EFAEAE2815E9F6A0BE668DCD8C9D0B65FE1502C8E3C95A
74A6EF2FD48587E79DFA1AD44778CE32F5F5DBB7AC9327675B9ACF7EDB5771C0
C186172DFE9F4419347F8241645CE909D0E0464C8A3FC4CB1AD882B2AF688582
58DB217291BD040676BAE8D5BDA74E620FC42DAE0D2C422ACFE6F7A2821FCB32
5E0992B332CB371ECF8FB8DD6F025117B80B43CBD070C05D36C772B76D5A61ED
28A4042D2AF5D1E16C9065E11C38CFE7D76A59EBA28546CB624F296FEA767FF7
45DC8E057A39D72B3961117A11159964B6EBC46B22F9C28A3C63E1C29C6F982F
61340FD9E4B45A2B34B0D9AE2D17246CFD71A33320049D7A87F42A22339FEE22
57D508AC8AC61F59F9CCED65A30201B878E5110BDEC2D77F990F39AC088EDF61
B01C99CD2B68EEDD767833B40622D43DF72D98F699A1309F8EF74D69E311FC6B
0E08D62033C180F121AD8C7DAD0CEC608DE22EAEAD3EA82B8FEF0017D79CC0DD
DA7182D0CDE958C3D0B33E3781015BEAF9CAC9F5A6D5C572AD7618D1B3992DB0
AE761939A038771B0004D599B9E01A59330EA1C695F3DD4596965D18C69F264E
76695D47795918440CF56E071577454DFC30693BE5AED4FAF061DAF9822FC7C0
AB2BD1E7C0D4A0CDFE68044669320DA6ECC549B60AB7DB22B2707EADB6A8E131
C6C22C9484C0712D6C7A99CB9E5B10FF2C5B698EF513670C58F73C9A550A7801
35233B6754D7AB60D5CFB0DC1203B0488065A5AECE07A66A6C63FB67261E1306
32DFB5065A9F47B2292E268BE27829C622BC07C2B229B8B91D7AA8A5B01C1AD6
BE2AF11ABA6C5D0D56C74C0DCA2ED0B18542E3BB7917E62DD4F60F9CF1FD4BF5
CF293770C0E7173878D4390BFFA4C761E2D7B063C213B125B19C5BB61925EBDE
B1E132EBBD8761596D13B2B5A222D40061163A584CD9EAC7C964EA8255655378
EB40A7D5C40260D65F212DD56AE061FD888934D269B983395525F33E141E0950
6BD5AACCA833B1EC2CD020F25ACA44B6CF6BDC47F05E86CC51764ACB7A23CE2D
C128412E144988B1D9B622BE27CC1EAB149652693371298D060B028B88A9055A
F630F7A706B733845AE425C6BB37BEC052A62E79E57E6EF7334838F019216ECD
EEA009AA50DA6E46CE13A2296B8B27836DCC1DED5C90A562C94D481943211548
83EDA1692457AA32BDCCC7177F3ED45B0407C70560DFBA15946492D35E3BBC34
FBCBC45A3030BCC817FC4D6A870FE506470771B3C2A473B4C9B7C5C687992AB5
020F9E6121E82E2C988B7C2CA9908D491645336C151C49088B39EDB954E5B303
E0251F0864E0C0CF21B51C99E88620B89EB9A5684CBE2A9B2FCDA2476A8477C3
DDEDDAD98DACC947DE8B410646FC4FDA755DFC75A7E7E7C418213929371356EF
B55389498BCD5934ABB2769E5B81F9629874189628EEF839A6C4EFE873F4FA4E
B75F2894015FE4E935FB43576BE40E792916BDE7ABD487851856088B769E045D
550A0C5A0CE14C6E949BDE88D783C539E1E0F8663D0E47C2D4DA39E0B3B6434B
874D84362CC54C705B1C98ADB8055F6B7F4B4E8E15B62673890285F495B58141
61C8A26D8B0328A7AAAF800005A6440D5ECD22638D32BF5CF6D884F1F8C0EB34
C30B0C3798028E683610D87E600D5E415738BC7F31A317720551CBC0EBAC7168
C63C2E33865732DC9F357FF7DBEA2F6006D02AF26143E7DC1A86054C7B12F91C
3026EBB0EA541A089B625BEDF6F56093E7FA69ADB78ACE7850ECAE51C56EA781
7188B2A815BAF783F01625B35486951FB6CDD3F6CC420EFA85897F162EC06966
A5D63B91836AB7A4C82B256EA56F4E397F2114987094A9DE95BACACD300C7B16
0B0E0AEC273FB222A8DC34C4D08C33508C056769E31345507C7187D26E72922D
C2359F053A48152F70722660F84B436C332D2F78CCAC5E237D52BA32B299B266
5B9BE88430503B3C1185275C089F0BB40878166FBC8FC71640E5E271F0850F51
F4CE584889AB53064D1C476384907E2292C3BE49C84A41282BD88241CC082199
96A68A35CC04F19B353CDDE4F095E5DE882BECAE38AD2561240A52A38F4CE61B
2333C321D9B7AFA5D57124F9436DCC4B37091461DF44B96DEE0446B949309CA8
C0AD014F4C8BCD084B5F07B1834F82E79C7F4CFD10883E7DF57D96A6776ADE33
BF0181F1FD0B7243003E60CA48A362266BA57C01E60847E39106626E73F58A45
3304C546390C3C0696FF5DF00A160EB93D6525E91A8B1202D1F0FCE974E99B5D
208EEA37BBD74BC401950C70B188526C41052B07C70FA20EC6B72A61C41A0B3C
1897041561973F55800E99A1E2332D7E54996769312EF3E88899957C562187DC
1C2FAEB1D28D211A530586299DE3EF2403F504FC69867E993F5FCDA77C4E824F
47E660D394D3E7426C6ADA8277EA4D2A3735675AC5873C38BF28E3418B7E5C7B
1749A0FAA1813D8B229A381B5676CDCE819C50DB198B2F56CD8758E5CE92B7DD
68B7E36FBC729927E5162FB7070377894805338209E6E15249AC6CBEB6DD58AC
7020D85919559FCDFD1439432797A544BAD3DDE9840E11CF922E1057C3C1BDB3
21AA45225665F3F386F915CC8BE3397C30A0B75D9CCD5236A28AD844BC568F55
B707C8305B2D913CE113C8BD7EB65C8A94B2AC22AD2F80CDC714831B50BAD3E1
D9811F2ABF007320034793D21BD155C90E6FD597917AF2F5CD56A2A5CEB8AB18
9DAD10994700076E56A177679289F91BE33E1063924CB299D150946012B266A0
35E74F244A2C6C16836C5FE34F4B2843BFE65E0A02C2C12642FCA307C04238CD
4891A6CEE3B15302C41F6DB44103494D966EECBC94B2A76305FCB02CDBE37D91
3A7E5222E627D57B847D778C34428A3A1727829920F5960C3757CD5405C924B0
3C1CE597875DB9D52772C1CFAC4752E1166AE9F6932830ADB4DFD190F9DE5671
82392C93E473B99C43028CF062995D938A4A0524CACCFEA32EA5B29D9BDAB380
B854969E9066C22774E17E178E4FE47F380F8345FB7DB0AC6F2656F5A8325F9C
FF61BFB9D56D8EAF29C45FE6A22711715C48F8C73D4F8FD95826C4E717068723
5C0F03B2C930B71F405662684874408E45FC7214027FC0A772795282BD727429
C4F18263F2C5C34B67278008E608050D7E82AA3A37572A78E7909F1C89B33B2C
EF8D4AE3646249A8C73D4F3B10CF32E1D62C71E1EA98DC7701EE792296B452C9
A18CC8853629EEB25FAE69DF05A551A7F63B9EB04467A0CC150B8538CB6A8E25
893CB5E8FA0CFC981B1B245AF3D0353B50951AC31E02E015B99149679FAEA697
F67F26C5DB08B2D13354B3A367A8D8112A09F628C429AC9849B83C92085B5702
5D43608E4D6AB53201EB10DCC413294F32B8ACB7E6100521109F9ED6783E74D7
D487BB66AB3E3C7FA5FEE979F613359B3D7F95AB555DA50C49B29EC879227E08
D728C834109F2ECBF2A1C82202E0D5479EB1131FABA75A0EEFCE619269480381
8C394994D9C5D99E58CD62062FEC8A7788E8A8D08BCDA691CC16FB3B1235DCED
C1E38011910677C888C32CEEC80B6344220FB3A246EE38210618BDD522CA2147
9D1BAE8148DE0171C269DAE86590586B963B45F45F0FB3A7D6E9E4293619BBD3
662E0A434C0A3E1AA49033C0792DC76E897A2E5B5F1CAA4A9E759747CC44963F
26C9B3D291E636E530D4CB1751C32102F00384B2E96A6EB91C3922363AF65866
9CFCA421102B3D7D2FD4E088821837003C02DDF124C3735CA06045C1431A4021
298EAEE3CF1A1F45C6622000C63016BF19EE47BB23E47FDDC5F312C5C74D5099
5278ECCE99780A8610A0EFB2E8CC71546F98DA673ED2F71F643A8AA9D9E4C853
3301D31355F20C88ADB248CC43E87D0B86BBFB6DECEE9FA3CA81DB0EFCD4F867
9810A1F9E75A129C978E98177F3BCD732A070EB8E35416F416BA4F0570A6BA5F
7B3380CE7EC7088A586F516F2F2DB6E309C8EE33B62CFEDF79946E327895C53F
9B82FD320D0C66E360AFE0A7976E6C93A41F879DB22729A849BD6058C408D373
7740DE2DC4936BD22D0E27A2C344E258524874619D2352946206D07B3FCC8769
B4BD65E03D2E6573BE89EE13F03EEB2ACA9D32EEB0BF92C0BF0F171760F254A2
D0A4BE7EA344BEEAF0AE04AC928CD971E20CA0727045E35A4F3631CA29033306
1043B9EB0D4400402C9191D06ADF4C4ABDAC36C06E33958DD44A98FCEC0208B2
3CA2595EAB6F8B7A89592FDA5CE3B36B9B9D6EEB43387333081E5852456E9585
CE15632C510A430F0914CD150B4985065FAA0B8A4C3FE359DF656BC2D4CF6F8F
2EAC231435DC98202815847B30CC55BFF0658C6AF69A8C74D4262294B95380DF
33C0AF2BCA8FB0AFA5D00849F9ED1E434AFA38893BE06F73A42FAE00A9249629
513A28798BDBB96D9039C3C4E4725B2AC83B3B82F4CDCC18F13D6AAE2B41BDB8
F400A545C977885C06B5BD5AC35D28E086CD17C2D4C5E1FDFE4614008B2383D3
7CE1A77AC8D5D9708E073F9DCC9D29E4B790940DB27F8D4A0A99071CB34D666E
88C753769EA3D59B45FE4699FFDE29275C07F3F2B340D9CE261140C0847D363F
D9A078AE00A4F367F9D64CC46FC8FABE9E5F85D735CBE12FAB478CDE8BA5C8DA
BD65180AB75BF20E180CD978378E73176BC72C74BD0B5394160FF3D4E4049A24
C2317ACB639F830D82F9405E244F1B470B3B0A05B3B80D5E0410401B179C815F
0601D3D4360648A58AFAEE3E01F6B8F81C9182A94E16AC5D1215DDA744A4173A
0B44C6432701F10B77330F7B3761B5638327488D49DDD85D9CBA41A3059EC174
EA76E2EC4DD8A484118A43B3EFB31E2F09F2E72EECA52E74EB187D93A7EB1B7F
B9D2867ED801CCF5B6E93ABA4148AB29D83BA5BA70FC3475170B6587B75CC6BC
669F132F27F27288A784C76D4018B9D19EA5552C2E2E6C02E8297B9CEE8AF613
E04C812B6CC7AE557A1B351733879A5359211D3B245630141F1CEAF68047F09F
0740DD70B68700DCC8607593E0EC24E5DDD551C35DED3CA1B96D7A496C100CE8
CC7B4BDC21BE6284FE26DC3E4E3684B938CC98110BFA472C549A5DD43BBA71FC
22226134A4AFDECB532BC5FD8E81B8A82C360C643023664EB33C5DEFC6297039
391866AB31AEB7A42D6912F56C5FC6A43A27F66ED59038CF0941C96E28129917
D7E682CB0298A279C0AD6AB44F2F2F2F55853BFF7A0336F11A89D753B3ED3970
5B4919327D43BC62B1115C9F607132859B2538289260FCDE24016130D68F5E12
42671812DBBDB2B615D78B5C1C334EB88F99BBE8C8E510ECC51E073FABCE12C5
EC3484E1ED4EE281898867F5A6FC24DEC849445256F66A6D33F4E0F021BD662F
A354D944C8E9874FEA07D969C61EB6C684E5D0F064EF1032470F112D9E7F317D
66ED7028BCE77006915BE6369BC25FD5FD6AF56674A6DBAC948A8D25D9BD4279
71C96D35725AFB845FA69189444E8C599E993ECFD2DE5A226BDB8C274639418B
B2081D3F283E32197F4011955F42FBE6128A1CAF47F7911017FFD62C91468F0B
EC452E486DE4452C5CBC0B228ED80E73F030C9F0661E3CC79642328B9C4B562B
F5C55D96B1D2C45B356FF8A1DA4EE3B593FEB64B71F7A5B703F91D98D28CBB56
DD7EB7031292D4A72546DCCFC0323CCE226546B2D958D9D25DD5A11E14B8E094
CE4363A121B8B037ED90187FB257148338DF14DBE24E2BF30B01F67783FCE599
78B930ABBBA4DF1FB387663BB56A9B0D9DD1F189BAAD06389650B969F15663BA
D5A7061FC2A30E80999FE4118CA47C053E3672F646258E69870D032C7A27CE88
F35F352247801F8B26D78588CD6FB35E3A7D11AD5B42F05C3DC8BB0F5E6FF98C
5AB9C376E3762E7422564454780F168C1328713BE62D5F18A4E0C1FA2FA1C0A5
324E822D1F3576D9825E8724F6706C9D207EEF990A20240EB7B42C6E0D0FBF53
F7CE1B7A0790A406F04702FD28C221B3275B1D77A1F598DC3FBDF7A6D4C0FA1F
D47E69F7EF5E0A9141FBA389E1AC9A4D114F148D5DDD6C1B0E5978ACA64D74FD
F88A3628553EAA9897CCE7B4DD539BB23E3EAF0430C7414D6C8D8674F4D12EED
4C928D6D8EFCAB710F7DFE60AAA4CE469F363A8DF31934B5908FD2153FE96D79
FCF09BF1E4EF0354AF8021DDFFA91CC54E2AFCBDD2ECF55CFD778C7190A2FCCA
6E7B4D8379F0E64896A92B3CF91F374F8A6D160F39A60A53E2DB0B5CFB0554D8
F4F2672F4869BC77AD2EEE68F01BB09C283534D13968F987C1F505665493E87C
0AC2EF333A6D3156683610EF33F6E357DE547A318DD97CA2E3E02966F309E90F
6A48CFD13EC7BA1B76319ADC556DC0D0BCB1F31C51BF6E83DB7A4A36AF31C9A1
836447FEE9613D74B4F3767171EF3C2833B2D9AD51C0947114CF7D08507561AA
A72B9D2A901C51937958496CDF67C90C8DF039F2334A7CCC6491BB5A3E59C842
5847BAB8E8FD5D4DF16F28DE7BF9E3EDB5D9576FD1C41E41B45BDDE11ECCD181
D3F61A4D96FD289E885F0C71028BABC1DB9B306376873F37190FACF026E40FEA
CDE5E5E59B14100B391CFBB13E51EDA8CC89B236AC91CD2FED272BD7DADBEA5D
D499F77F702D8B3B9D7C9B68574FB91F8CE5E1597E5AC216B3E89559E3E9081E
17E52C5469F60747829532403A7AC82058FDEC22753301EB52F1CB9AA36E5E19
92F1B93FE4F2BA5CBB74842699DB4BC4A02C908BE46F05BF9C5A2E458BDB90F3
FB08C6F989B6120C1B5F444C11F8D89D3B21A12D31342697AD128ECE77708259
95915E83E18053BE8882F212744A328B21E730F0C4F1D9689C2A0505CCBAAEEC
A55A3CCF7CC10B6437831D0409ACA1E345FAE7CC1C835AE039ACA3BC1D4F182D
45BA50BAEAFD36722EB614019F21203A9029B266483B381C9954AB6BF3FBF12E
B7342D1106EE7733769BDDF806D6F1A5E6CE738CAC37897C374498E2EB174E1E
4FC9F49998D25D7DD8213D8A5ADBCBFEE4DE3A0703E4E387A86151DEEFBB7E98
BF11FF480BDA1FE2AA2096397218141EC766480A8DED26F6D3994E86452AF214
E41DC926FD31456044F590E6324279D795E49DA459586CC12D04AD547438E1A8
707848067FE8C8FEEF8ECA446D2EA6CF138C91316E01A6E22C10DB8D17B3E7E9
EB1AE0B190573598BC6E88B1132DFEABE334BF5DE95E881A2E62166568BC2EC3
5D1E3C58477B73E62F9A16738CDBDA63ABE6ADB928D59FEB8661CDB924CBFA89
7B1DF88657E2AE3FEA810F3308FCBB94499F3EC92BF39B56E2B38FB875257E31
96D241F19761DCC656986F7E969FFD1F598CCF767B840000
}

8.12 Thumbnail Maker

This program resizes and arranges a list of image files into a single preview image. The screen shot image sheet at the beginning of this tutorial was created using this application.

REBOL [Title: "Thumbnail Maker"]

; Create a little GUI to allow the user to adjust image settings:

view center-face layout [

    text "Resize input images to this height:"
    height: field "200"

    text "Create output mosaic of this width:"
    width: field "600"

    text "Space between thumbnails:"
    padding-size: field "30"

    text "Color between thumbnails:"
    btn "Select color" [background-color: request-color/color white]

    text "Thumbnails will be displayed in this order:"
    the-images: area
    across
    btn "Select images" [

        ; Select some files:        
        some-images: request-file/title trim/lines {Hold
            down the [CTRL] key to select multiple images:} ""

        ; Error check:
        if some-images = none [return] 

        ; Show the selected files in the area widget above, with
        ; each file on a new line:
        foreach single-image some-images [
           append the-images/text single-image
           append the-images/text "^/"
        ]
        show the-images
    ]

    ; This button creates the output thumbnail mosaic:

    btn "Create Thumbnail Mosaic" [

        ; Set sizing variables to the values entered in the GUI:

        y-size: to-integer height/text
        mosaic-size: to-integer width/text
        padding: to-integer padding-size/text

        ; Set the background color (white if none selected):

        if error? try [background-color: to-tuple background-color][
            background-color: white
        ]

        ; The list of images that will be resized is stored in a block
        ; labeled "images".  The "parse" function is covered later in
        ; this tutorial.  The following code simply separates each line
        ; item in the text area above, and returns a block of all the
        ; items:

        images: copy parse/all the-images/text "^/"

        ; Error check:
        if empty? images [alert "No images selected." break]

        ; The output image will be created from a "view layout" GUI block.
        ; That block will be labeled "mosaic" and will contain all the
        ; resized image data and layout formatting needed to create the
        ; thumbnail image.  We'll start building that block by
        ; including the background color, spacing, and "across" words
        ; needed to layout the GUI.  Because the block contains some
        ; variables, we'll use the "compose" function to evaluate them
        ; (treat them as if they'd been typed in explicitly):

        mosaic: compose [
            backcolor (background-color) space (padding) across
        ]

        ; Next, we'll use a foreach loop to go through the list of images,
        ; read and resize each image, and add the resized image data to
        ; the mosaic block.  The variable "picture" will be used to refer
        ; to each image as the loop progresses through each item in the
        ; list:

        foreach picture images [

             ; Give the user some feedback with a litte message:

             flash rejoin ["Resizing " picture "..."]

             ; Read the image data, and assign it the variable label
             ; "original":

             original: load to-file picture

             ; After the data is done loading, erase the message above:

             unview

             ; We can refer to the size of the original image using the
             ; format "orginal/size".  That returns width and height
             ; values in the form of an XxY pair.  To refer to the height
             ; (Y) value only, we can use the format "original/size/2" 
             ; (the second element in the pair).  If the height of the
             ; original image is larger than the "y-size" variable set at
             ; the beginning of the program, we'll resize the image so
             ; it fits that height, and append the resized image data to
             ; the "mosaic" block.  Otherwise, we'll simply append the
             ; orginal image to the block.  We're also going to include
             ; the "image" word, because the "mosaic" block needs to
             ; include all the functions and data needed to create a view
             ; layout GUI window:

             ; If the original image is taller than the prescribed height:

             either original/size/2 > y-size [

                 ; Figure a percentage amount the width needs to be 
                 ; resized:

                 new-x-factor: y-size / original/size/2

                 ; Calculate the width of the new image size, and assign
                 ; that value to the variable "new-x-size":

                 new-x-size: round original/size/1 * new-x-factor

                 ; Create the resized image by using the "layout" function
                 ; (as in "view layout").  Specify a new size for the
                 ; image by rejoining the "new-x-size" variable above with
                 ; the "y-size" value specified earlier, and convert that
                 ; value to a pair.  Create a new image from that layout
                 ; using the "to-image" function, and assign it to the
                 ; variable "new-image":

                 new-image: to-image layout/tight [
                     image original as-pair new-x-size y-size
                 ]

                 ; Next, append the resized image data to the "mosaic"
                 ; block.  We'll compose the block because we want the
                 ; new-image data to be included as if it was typed in
                 ; explicitly.  The word "image" also needs to be included
                 ; because that's needed to show an image in a view layout
                 ; block:

                 append mosaic compose [image (new-image)]

             ][

                 ; Here's the second part of the "either" condition above.
                 ; If the height of the original is less than the "y-size"
                 ; variable, simply append the original image to the
                 ; "mosaic" block:

                 append mosaic compose [image (original)]
             ]

             ; As the current foreach loop stands, each resized image is
             ; simply added to the "mosaic" layout from left to right.  We
             ; need to check the size of the "mosaic" layout every time we
             ; add an image.  If the layout is wider than the width we set
             ; at the beginning of the program (the "mosaic-size" 
             ; variable), we need to insert a "return" word into the
             ; "mosaic" GUI layout block:

             ; Create a temporary layout of the "mosaic" block:

             current-layout: layout/tight mosaic

             ; If the width of the current layout is larger than the
             ; prescribed width, insert the "return" word BEFORE the
             ; current resized image.  A tick mark is put onto the 'return
             ; word so that the actual unevaluated text "return" is
             ; appended to the mosaic block.  "back back tail" puts the
             ; "return" word in the correct place in the layout block: 

             if current-layout/size/1 > mosaic-size [
                 insert back back tail mosaic 'return
             ]
        ]

        ; Prompt the user for a file name to save the final "mosaic"
        ; layout image:

        filename: to-file request-file/file/save "mosaic.png"

        ; Create an image from the final "mosaic" layout block, and save
        ; that image to the file name above:

        save/png filename (to-image layout mosaic)

        ; Show the user the saved image:

        view/new layout [image load filename]
    ]
]

You can use this program to quickly resize collections of photos for email, web sites, etc.

9. Additional Topics

9.1 Objects

Objects are code structures that allow you to encapsulate and replicate code. They can be thought of as code containers which are easily copied and modified to create multiple versions of similar code and/or duplicate data structures. They're also used to provide context and namespace management features (i.e., to avoid assigning the same variable words and/or function names to different pieces of code in large projects).

Object "prototypes" define a new object container. To create an original object prototype in REBOL, use the following syntax:

label: make object! [object definition]

The object definition can contain functions, values, and/or data of any type. Below is a blank user account object containing 6 variables which are all set to equal "none"):

account: make object! [
    first-name: last-name: address: phone: email-address: none
]

The account definition above simply wraps the 6 variables into a container, or context, called "account".

You can refer to data and functions within an object using refinement ("/path") notation:

object/word

In the account object, "account/phone" refers to the phone number data contained in the account. You can make changes to elements in an object as follows:

object/word: data

For example:

account/phone: "555-1234"
account/address: "4321 Street Place Cityville, USA 54321"

Once an object is created, you can view all its contents using the "help" function:

help object
? object  

; "?" is a synonym for "help"

If you've typed in all the account examples so far into the REBOL interpreter, then:

? account

displays the following info:

ACCOUNT is an object of value:
   first-name      none!     none
   last-name       none!     none
   address         string!   "4321 Street Place Cityville, USA 54321"
   phone           string!   "555-1234"
   email-address   none!     none

You can obtain a list of all the items in an object using the format "first (object label)":

first account

The above line returns [self first-name last-name address phone email-address]. The first item in the list is always "self", and for most operations, you'll want to remove that item. To do that, use the format "next first (object label)":

next first account

To iterate through every item in an object, you can use a foreach loop on the above values:

foreach item (next first account) [print item]

To get the values referred to by individual word labels in objects, use "get in":

get in account 'first-name
get in account 'address

; notice the tick mark

The following example demonstrates how to access and manipulate every value in an object:

count: 0
foreach item (next first account) [
    count: count + 1
    print rejoin ["Item " count ":   " item]
    print rejoin ["Value:    " (get in account item) newline]
]

Once you've created an object prototype, you can make a new object based on the original definition:

label: make existing-object [
    values to be changed from the original definition
]

This behaviour of copying values based on previous object definitions (called "inheritance") is one of the main reasons that objects are useful. The code below creates a new account object labeled "user1":

user1: make account [
    first-name: "John"
    last-name: "Smith"
    address: "1234 Street Place  Cityville, USA 12345"
    email-address: "john@hisdomain.com"
]

In this case, the phone number variable retains the default value of "none" established in the original account definition.

You can extend any existing object definition with new values:

label: make existing-object [new-values to be appended]

The definition below creates a new account object, redefines all the existing variables, and appends a new variable to hold the user's favorite color.

user2: make account [
    first-name: "Bob"
    last-name: "Jones"
    address: "4321 Street Place Cityville, USA 54321"
    phone:  "555-1234"
    email-address: "bob@mysite.net"
    favorite-color: "blue"
]

"user2/favorite-color" now refers to "blue".

The code below creates a duplicate of the user2 account, with only the name and email changed:

user2a: make user2 [
    first-name: "Paul"
    email-address: "paul@mysite.net"
]

"? user2a" provides the following info:

USER2A is an object of value:
   first-name      string!   "Paul"
   last-name       string!   "Jones"
   address         string!   "4321 Street Place Cityville, USA 54321"
   phone           string!   "555-1234"
   email-address   string!   "paul@mysite.net"
   favorite-color  string!   "blue"

You can include functions in your object definition:

complex-account: make object! [
    first-name: 
    last-name:
    address:
    phone:
    none
    email-address: does [
        return to-email rejoin [
            first-name "_" last-name "@website.com"
        ]
    ]
    display: does [
        print ""
        print rejoin ["Name:     " first-name " " last-name]
        print rejoin ["Address:  " address]
        print rejoin ["Phone:    " phone]
        print rejoin ["Email:    " email-address]
        print ""
    ]
]

Note that the variable "email-address" is initially assigned to the result of a function (which simply builds a default email address from the object's first and last name variables). You can override that definition by assigning a specified email address value. Once you've done that, the email-address function no longer exists in that particular object - it is overwritten by the specified email value.

Here are some implementations of the above object. Notice the email-address value in each object:

user1: make complex-account []

user2: make complex-account [
    first-name: "John"
    last-name: "Smith"
    phone:  "555-4321"
]

user3: make complex-account [
    first-name: "Bob"
    last-name: "Jones"
    address: "4321 Street Place Cityville, USA 54321"
    phone:  "555-1234"
    email-address: "bob@mysite.net"
]

To print out all the data contained in each object:

user1/display user2/display user3/display

The display function prints out data contained in each object, and in each object the same variables refer to different values (the first two emails are created by the email-address function, and the third is assigned).

Here's a small game in which multiple character objects are created from a duplicated object template. Each character can store, alter, and print its own separately calculated position value based on one object prototype definition:

REBOL []

hidden-prize: random 15x15
character: make object! [
    position: 0x0
    move: does [
        direction: ask "Move up, down, left, or right:  "
        switch/default direction [
            "up" [position: position + -1x0]
            "down" [position: position + 1x0]
            "left" [position: position + 0x-1]
            "right" [position: position + 0x1]
        ] [print newline print "THAT'S NOT A DIRECTION!"]
        if position = hidden-prize [
            print newline
            print "You found the hidden prize.  YOU WIN!"
            print newline
            halt
        ]
        print rejoin [
            newline
            "You moved character " movement " " direction
            ".  Character " movement " is now " 
            hidden-prize - position
            " spaces away from the hidden prize.  "
            newline
        ]
    ]
]
character1: make character[]
character2: make character[position: 3x3]
character3: make character[position: 6x6]
character4: make character[position: 9x9]
character5: make character[position: 12x12]
loop 20 [
    prin "^(1B)[J"
    movement: ask "Which character do you want to move (1-5)?  "
    if find ["1" "2" "3" "4" "5"] movement [
        do rejoin ["character" movement "/move"]
        print rejoin [
            newline
            "The position of each character is now:  "
            newline newline
            "CHARACTER ONE:   " character1/position newline
            "CHARACTER TWO:   " character2/position newline
            "CHARACTER THREE: " character3/position newline
            "CHARACTER FOUR:  " character4/position newline
            "CHARACTER FIVE:  " character5/position
        ]
        ask "^/Press the [Enter] key to continue."
    ]
]

You could, for example, extend this concept to create a vast world of complex characters in an online multi-player game. All such character definitions could be built from one base character definition containing default configuration values.

9.1.1 Namespace Management

In this example the same words are defined two times in the same program:

var: 1234.56
bank: does [
    print ""
    print rejoin ["Your bank account balance is:  $" var]
    print ""
]

var: "Wabash"
bank: does [
    print ""
    print rejoin [
        "Your favorite place is on the bank of the:  " var]
    print ""
]

bank

There's no way to access the bank account balance after the above code runs, because the "bank" and "var" words have been overwritten. In large coding projects, it's easy for multiple developers to unintentionally use the same variable names to refer to different pieces of code and/or data, which can lead to accidental deletion or alteration of values. That potential problem can be avoided by simply wrapping the above code into separate objects:

money: make object! [
    var: 1234.56
    bank: does [
        print ""
        print rejoin ["Your bank account balance is:  $" var]
        print ""
    ]
]

place: make object! [
    var: "Wabash"
    bank: does [
        print ""
        print rejoin [
            "Your favorite place is on the bank of the:  " var]
        print ""
    ]
]

Now you can access the "bank" and "var" words in their appropriate object contexts:

money/bank
place/bank

money/var
place/var

The objects below make further use of functions and variables contained in the above objects. Because the new objects "deposit" and "travel" are made from the "money" and "place" objects, they inherit all the existing code contained in the above objects:

deposit: make money [
    view layout [
        button "Deposit $10" [
            var: var + 10
            bank
        ]
    ]
]

travel: make place [
    view layout [
        new-favorite: field 300 trim {
            Type a new favorite river here, and press [Enter]} [
            var: value
            bank
        ]
    ]
]

Learning to use objects is important because much of REBOL is built using object structures. As you've seen earlier in the section about built-in help, the REBOL "system" object contains many important interpreter settings. In order to access all the values in the system object, it's essential to understand object notation:

get in system/components/graphics 'date

The same is true for GUI widget properties and many other features of REBOL.

For more information about objects, see:

http://rebol.com/docs/core23/rebolcore-10.html
http://en.wikibooks.org/wiki/REBOL_Programming/Language_Features/Objects
http://en.wikipedia.org/wiki/Prototype-based_programming

9.2 Ports

REBOL "ports" provide a single way to handle many types of data input and output. They enable access to a variety of data sources, and allow you to control them all in a consistent way, using standard REBOL series functions. You can open ports to POP email boxes, FTP directories, local text files, TCP network connections, keyboard input buffers, and more, and also use them to output data such as sounds and console interactions. Once a port is opened to a data source, the data contained in the port can be treated as a sequential list of items which can be traversed, arranged, searched, sorted, and otherwise organized/manipulated, all using series functions such as those covered earlier in this text (foreach, find, select, reverse, length?, head, next, back, last, tail, at, skip, extract, index?, insert, append, remove, change, poke, copy/part, clear, replace, join, intersect, difference, exclude, union, unique, empty?, write, save, etc.).

In some cases, there are other native ways to access data contained in a given port, typically with "read" and "write" functions. In such cases, port access simply provides finer control of the data (dealing with email and file data are examples of such cases). In other cases, ports provide the primary interface for accessing data in the given data source (TCP sockets and other network protocols are examples).

Ports are created using the "open" function, and are typically assigned a word label when created:

my-files: open ftp://user:pass@site.com/public_html/

After opening the above port, the files in the FTP directory can be traversed sequentially or by index, using series functions:

print first my-files
print length? my-files
print pick my-files ((length? my-files) - 7)  ; 7th file from the end

; etc ...

To change the marked index position in a port, re-assign the port label to the new index position:

my-files: head my-files
    print index? my-files
    print first my-files
my-files: next my-files
    print index? my-files
    print first my-files
my-files: at my-files 10
    print index? my-files
    print first my-files

To close the connection to data contained in a port, use the "close" function:

close my-files

It is of course possible to read and write directly to/from the folder in the above examples without manually opening a port:

print read ftp://user:pass@site.com/public_html/
write ftp://user:pass@site.com/public_html/temp.txt (read %temp.txt)

The difference between opening a port to the above files, and simply reading/writing them is that the port connection offers more specific access to and control of individual files. The "get-modes" and "set-modes" functions can be used to set various properties of files:

my-file: open %temp.txt
set-modes port [
    world-read: true
    world-write: true
    world-execute: true
]
close my-file

More benefits of port control are easy to understand when dealing with email accounts. All the email in a given account can be accessed by simply reading it:

print read pop://user:pass/site.com

If, for example, there are 10000 messages in the above email account, the above action could take a very long time to complete. Even to simply read one email from the above account, the entire account needs to be read:

print second read pop://user:pass/site.com

A much better solution is to open a port to the above data source:

my-email: open pop://user:pass/site.com

Once the above port is open, each of the individual emails in the given POP account can be accessed separately, without having to download any other emails in the account:

print second my-email  ; no download of 10000 emails required

And you can jump around between messages in the account:

my-email: head my-email
    print first my-email   ; prints the 1st email in the box
my-email: next my-email
    print first my-email   ; prints the 2nd email in the box
my-email: at my-email 4
    print first my-email   ; prints the 5th email in the box
my-email: head my-email
    print first my-email   ; prints email #1 again

; etc...

You can also remove email messages from the account:

my-email: head my-email
    remove my-email  ; removes the 1st email

Internally, REBOL actually deals with most types of data sources as ports. The following line:

write/append %temp.txt "1234"

Is the same as:

temp: open %temp.txt
append temp "1234"
close temp

REBOL ports are objects. You can see all the properties of an open port, using the "probe" function:

temp: open %temp.txt
probe temp
close temp

From the very important example above, you can see that various useful properties of the port data can be accessed using a consistent syntax:

temp: open %temp.txt

print temp/date
print temp/path
print temp/size

close temp

The state/inBuffer and state/outBuffer are particularly important values in any port. Those items are where changes to data contained in the port are stored, until the port is closed or updated. Take a close look at this example:

; First, create a file:

write %temp.txt ""

; That file is now empty:

print read %temp.txt

; Open the above file as a port:

temp: open %temp.txt

; Append some text to it:

append temp "1234"

; Display the text to be saved to the file:

print temp/state/inBuffer

; The appended changes have NOT yet been saved to the file because the
; port has not yet been closed or updated:

print read %temp.txt

; Either "update" or "close" can be used to save the changes to the file:

update temp

; The "update" function has forced the appended data to be written to
; the file, but has NOT yet closed the port:

print read %temp.txt

; We can still navigate the port contents and add more data to it:

temp: head temp
insert temp "abcd"

; Display the text to be saved to the file:

print temp/state/inBuffer

; Those changes have not yet been saved to the file:

print read %temp.txt

; Closing the port will save changes to the file:

close temp

; Here are the saved changes:

print read %temp.txt

; And additional changes can no longer be made:

append temp "1q2w3e4r"  ; (error)

Ports can be opened with a variety of refinements to help deal with data appropriately. "Help open" displays the following list:

/binary  - Preserves contents exactly.
/string  - Translates all line terminators.
/direct  - Opens the port without buffering.
/seek    - Opens port in seek mode without buffering.
/new     - Creates a file. (Need not already exist.)
/read    - Read only. Disables write operations.
/write   - Write only.  Disables read operations.
/no-wait - Returns immediately without waiting if no data.
/lines   - Handles data as lines.
/with    - Specifies alternate line termination. (Type: char string)
/allow   - Specifies the protection attributes when created. (Type: block)
/mode    - Block of above refinements. (Type: block)
/custom  - Allows special refinements. (Type: block)
/skip    - Skips a number of bytes. (Type: number)

Several of those options will be demonstrated in the following example applications.

9.2.1 Console Email Application

The following email program opens a port to a selected email account and allows the user to navigate through messages, read, send, delete, and reply to emails. It runs entirely at the command line - no VID GUI components or View graphics are required. You can store configuration information for as many email accounts as you'd like in the "accounts" block, and easily switch between them at any point in the program:

REBOL [Title: "Console Email"]

accounts: [
    ["pop.server" "smtp.server" "username" "password" you@site.com]
    ["pop.server2" "smtp.server2" "username" "password" you@site2.com]
    ["pop.server3" "smtp.server3" "username" "password" you@site3.com]
]

empty-lines: "^/"
loop 400 [append empty-lines "^/"]  ; # of lines it takes to clear screen
cls: does [prin {^(1B)[J}]
a-line:{-----------------------------------------------------------------}

select-account: does [
    cls
    print a-line
    forall accounts [
        print rejoin ["^/" index? accounts ":  " last first accounts]
    ]
    print join "^/" a-line
    selected: ask "^/Select an account #:  "
    if selected = "" [selected: 1]
    t: pick accounts (to-integer selected)
    system/schemes/pop/host:  t/1
    system/schemes/default/host: t/2
    system/schemes/default/user: t/3 
    system/schemes/default/pass: t/4 
    system/user/email: t/5
]
send-email: func [/reply] [
    cls
    print rejoin [a-line "^/^/Send Email:^/^/" a-line] 
    either reply [
        print join "^/^/Reply-to:  " addr: form pretty/from
    ] [
        addr: ask "^/^/Recipient Email Address:  "
    ]
    either reply [
        print join "^/Subject:  " subject: join "re: " form pretty/subject
    ] [
        subject: ask "^/Email Subject:  "
    ]
    print {^/Body (when finished, type "end" on a seperate line):^/}
    print join a-line "^/"
    body: copy ""
    get-body: does [
        body-line: ask ""
        if body-line = "end" [return]
        body: rejoin [body "^/" body-line]
        get-body
    ]
    get-body
    if reply [
        rc: ask "^/Quote original email in your reply (Y/n)?  "
        if ((rc = "yes") or (rc = "y") or (rc = "")) [
            body: rejoin [
                body 
                "^/^/^/--- Quoting " form pretty/from ":^/"
                form pretty/content
            ]
        ]
    ]
    print rejoin ["^/" a-line "^/^/Sending..."]
    send/subject to-email addr body subject 
    cls 
    print "Sent^/" 
    wait 1
]
read-email: does [
    pretty: none
    cls
    print "One moment..."
    ; THE FOLLOWING LINE OPENS A PORT TO THE SELECTED EMAIL ACCOUNT:
    mail: open to-url join "pop://" system/user/email
    cls
    while [not tail? mail] [
        print "Reading...^/"
        pretty: import-email (copy first mail)
        either find pretty/subject "***SPAM***" [
            print join "Spam found in message #" length? mail
            mail: next mail
        ][
            print empty-lines
            cls
            prin rejoin [
                a-line
                {^/The following message is #} length? mail { from:  } 
                system/user/email {^/} a-line {^/^/}
                {FROM:     } pretty/from {^/}
                {DATE:     } pretty/date {^/}
                {SUBJECT:  } pretty/subject {^/^/} a-line
            ]
            confirm: ask "^/^/Read Entire Message (Y/n):  "
            if ((confirm = "y") or (confirm = "yes") or (confirm = "")) [
                print join {^/^/} pretty/content
            ]
            print rejoin [
                {^/} a-line {^/}
                {^/[ENTER]:  Go Forward  (next email)^/}
                {^/    "b":  Go Backward (previous email)^/}
                {^/    "r":  Reply to current email^/}
                {^/    "d":  Delete current email^/}
                {^/    "q":  Quit this mail box^/}
                {^/  Any #:  Skip forward or backward this # of messages}
                {^/^/} a-line {^/}
            ]
            switch/default mail-command: ask "Enter Command:  " [
                ""  [mail: next mail]
                "b" [mail: back mail]
                "r" [send-email/reply]
                "d" [
                    remove mail
                    cls 
                    print "Email deleted!^/" 
                    wait 1
                ]
                "q" [
                    close mail
                    cls
                    print"Mail box closed^/"
                    wait 1 
                    break
                ]
            ] [mail: skip mail to-integer mail-command]
            if (tail? mail) [mail: back mail]
        ]
    ]
]

; begin the program:

select-account

forever [
    cls
    print a-line
    print rejoin [
        {^/"r":  Read Email^/}
        {^/"s":  Send Email^/}
        {^/"c":  Choose a different mail account^/}
        {^/"q":  Quit^/}
    ]
    print a-line
    response: ask "^/Select a menu choice:  "
    switch/default response [
        "r" [read-email]
        "s" [send-email]
        "c" [select-account]
        "q" [
            cls
            print "DONE!"
            wait .5 
            quit
        ]
    ] [read-email]
]

9.2.2 Network Ports

One important use of ports is for transferring data via network connections (TCP and UDP "sockets"). When writing a network application, you must choose a specific port number through which data is to be transferred. Potential ports range from 0 to 65535, but many of those numbers are reserved for specific types of applications (email programs use port 110, web servers use port 80 by default, etc.). To avoid conflicting with other established network applications, it's best to choose a port number between 49152 and 65535 for small scripts. A list of reserved port numbers is available here.

Network applications are typically made up of two or more separate programs, each running on different computers. Any computer connected to a network or to the Internet is assigned a specific "IP address", notated in the format xxx.xxx.xxx.xxx. The numbers are different for every machine on a network, but most home and small business machines are normally in the IP range "192.168.xxx.xxx". You can obtain the IP address of your local computer with the following REBOL code:

read join dns:// (read dns://)

"Server" programs open a chosen network port and wait for one or more "client" programs to open the same port and then insert data into it. The port opened by the server program is referred to in a client program by combining the IP address of the computer on which the server runs, along with the chosen port number, each separated by a colon symbol (i.e., 192.168.1.2:55555).

The following simple set of scripts demonstrates how to use REBOL ports to transfer one line of text from a client to a server program. This example is intended to run on a single computer, for demonstration, so the word "localhost" is used to represent the IP address of the server (that's a standard convention used to refer to any computer's own local IP address). If you want to run this on two separate computers connected via a local area network, you'll need to obtain the IP address of the server machine (use the code above), and replace the word "localhost" with that number:

Here's the SERVER program. Be sure to run it before starting the client, or you will receive an error:

; Open network port 55555, in line mode (this mode expects full lines
; of text delineated by newline characters):

server: open/lines tcp://:55555

; Wait for a connection to the above port:

wait server

; Assign a label to the first connection made to the above port:

connection: first server

; Get the data which has been inserted into the above port object:

data: first connection

; Display the inserted data:

alert rejoin ["Text received: " data]

; Close the server

close server

Here's the CLIENT. Run it in a separate instance of the REBOL interpreter, after the above program has been started:

; Open the port created by the server above (replace "localhost" with
; an IP address if running these scripts on separate machines):

server-port: open/lines tcp://localhost:55555

; Insert some text into the port:

insert server-port "Hello Mr. Watson."

; Close the port:

close server-port

Typically, servers will continuously wait for data to appear in a port, and repeatedly do something with that data. The scripts below extend the above example with forever loops to continuously send, receive, and display messages transferred from client(s) to the server. This type of loop forms the basis for most peer-to-peer and client-server network applications. Type "end" in the client program below to quit both the client and server.

Here's the server program (run it first):

server: open/lines tcp://:55555             ; Open a TCP network port.
print "Server started...^/"
connection: first wait server               ; Label the first connection.
forever [
    data: first connection                  ; Get a line of data.
    print rejoin ["Text received: " data]   ; Display it.
    if find data "end" [                    
        close server                        ; End the program if the
        print "Server Closed"               ;   client user typed "end".
        halt
    ]
]

Here's the client program. Run it only after the server program has been started, and in a separate instance of the REBOL interpreter (or on a separate computer):

server-port: open/lines tcp://localhost:55555    ; Open the server port.
forever [
    user-text: ask "Enter some text to send:  "
    insert server-port user-text                 ; Transfer the data.
    if user-text = "end" [
        close server-port                        ; End the program if the
        print "Client Closed"                    ; user typed "end".
        halt
    ]
]

It's important to understand that REBOL servers like the one above can interact independently with more than one simultaneous client connection. The "connection" definition waits until a new client connects, and returns a port representing that first client connection. Once that occurs, "connection" refers to the port used to accept data transferred by the already connected client. If you want to add more simultaneous client connections during the forever loop, simply define another "first wait server". Try running the server below, then run two simultaneous instances of the above client:

server: open/lines tcp://:55555             ; Open a TCP network port.
print "Now start TWO clients..."
connection1: first wait server              ; Label the first connection.
connection2: first wait server              ; Label the second connection.
forever [
    data1: first connection1                ; Get a line of client1 data
    data2: first connection2                ; Get a line of client2 data
    print rejoin ["Client1: " data1]
    print rejoin ["Client2: " data2]
    if find data1 "end" [                    
        close server                        ; End the program if the
        print "Server Closed"               ;   client user typed "end".
        halt
    ]
]

Here's an example that demonstrates how to send data back and forth (both directions), between client and server. Again, run both programs in separate instances of the REBOL interpreter, and be sure to start the server first:

; Server:

print "Server started...^/"
port: first wait open/lines tcp://:55555
forever [
    user-text: ask "Enter some text to send:  "
    insert port user-text
    if user-text = "end" [close port  print "^/Server Closed^/"  halt]
    wait port
    print rejoin ["^/Client user typed:  " first port "^/"]
]

; Client:

port: open/lines tcp://localhost:55555
print "Client started...^/"
forever [
    user-text: ask "Enter some text to send:  "
    insert port user-text
    if user-text = "end" [close port  print "^/Client Closed^/"  halt]
    wait port
    print rejoin ["^/Server user typed:  " first port "^/"]
]

The following short script combines many of the techniques demonstrated so far. It can act as either server or client, and can send messages (one at a time), back and forth between the server and client:

do [
    either find ask "Server or client?  " "s" [
        port: first wait open/lines tcp://:55555 ; server
    ] [
        port: open/lines tcp://localhost:55555   ; client
    ]
    forever [
        insert port ask "Send:  "
        print join "Received: "first wait port
    ] 
]

The following script is a complete GUI network instant message application. Unlike the FTP Chat Room presented earlier, the text in this application is sent directly between two computers, across a network socket connection (users of the FTP chat room never connect directly to one another - they simply connect to a universally available third party FTP server):

view layout [
    btn "Set client/server" [
        ip: request-text/title/default trim {
            Server IP (leave EMPTY to run as SERVER):
            } (to-string read join dns:// read dns://)
        either ip = "" [
            port: first wait open/lines tcp://:55555 z: true ; server
        ] [
            port: open/lines rejoin [tcp:// ip ":55555"] z: true
        ]
    ]
    r: area rate 4 feel [
        engage: func [f a e] [
            if a = 'time and value? 'z [
                if error? try [x: first wait port] [quit]
                r/text: rejoin [form x newline r/text] show r
            ]
        ]
    ]
    f: field "Type message here..."
    btn "Send" [insert port f/text] 
]

Here's an even more compact version (probably the shortest instant messenger program you'll ever see!):

view layout [ across
    q: btn "Serve"[focus g p: first wait open/lines tcp://:8 z: 1]text"OR"
    k: btn "Connect"[focus g p: open/lines rejoin[tcp:// i/text ":8"]z: 1]
    i: field form read join dns:// read dns://  return
    r: area rate 4 feel [engage: func [f a e][if a = 'time and value? 'z [
        if error? try [x: first wait p] [quit]
        r/text: rejoin [x "^/" r/text] show r
    ]]]  return
    g: field "Type message here [ENTER]" [insert p value  focus face]
]

And here's an extended version of the above script that uploads your chosen user name, WAN/LAN IP, and port numbers to an FTP server, so that that info can be shared with others online (which enables them to find and connect with you). Connecting as server uploads the user info and starts the application in server mode. Once that is done, others can click the "Servers" button to retrieve and manually enter your connection info (IP address and port), to connect as client. By using different port numbers and user names, multiple users can connect to other multiple users, anywhere on the Internet:

server-list: ftp://username:password@yoursite.com/public_html/im.txt ;edit
view layout [ across
    q: btn "Serve" [
        parse read http://guitarz.org/ip.cgi[thru<title>copy p to</title>]
        parse p [thru "Your IP Address is: " copy pp to end]
        write/append server-list rejoin [
            b/text " " pp " " read join dns:// read dns://"  " j/text "^/"
        ] 
        focus g p: first wait open/lines join tcp:// j/text z: 1
    ] text "OR"
    k: btn "Connect" [
        focus g p: open/lines rejoin [tcp:// i/text j/text] z: 1
    ]
    b: field 85 "Username"
    i: field 98 form read join dns:// read dns:// 
    j: field 48 ":8080" return
    r: area rate 4 feel [engage: func [f a e][if a = 'time and value? 'z [
        if error? try [x: first wait p] [quit]
        r/text: rejoin [x "^/" r/text] show r
    ]]]  return
    g: field "Type message here [ENTER]" [insert p value  focus face]
    tabs 181 tab btn "Servers" [print read server-list]
]

If you want to run scripts like these between computers connected to the Internet by broadband routers, you'll likely need to learn how to "forward" ports from your router to the IP address of the machine running your server program. In most situations where a router connects a local home/business network to the Internet, only the router device has an IP address which is visible on the Internet. The computers themselves are all assigned IP addresses that are only accessible within the local area network. Port forwarding allows you to send data coming to the IP address of the router (the IP which is visible on the Internet), on a unique port, to a specific computer inside the local area network. A full discussion of port forwarding is beyond the scope of this tutorial, but it's easy to learn how to do - just type "port forwarding" into Google. You'll need to learn how to forward ports on your particular brand and model of router.

With any client-server configuration, only the server machine needs to have an exposed IP address or an open router/firewall port. The client machine can be located behind a router or firewall, without any forwarded incoming ports.

Another option that enables network applications to work through routers is "VPN" software. Applications such as hamachi, comodo, and OpenVPN allow you to connect two separate LAN networks across the Internet, and treat all the machines as if they are connected locally (connect to any computer in the VPN using a local IP address, such as 192.168.1.xxx). VPN software also typically adds a layer of security to the data sent back and forth between the connected machines. The down side of VPN software is that data transmission can be slower than direct connection using port forwarding (the data travels through a third party server).

9.2.3 Peer-to-Peer Instant Messenger

The following text message example contains line by documentation of various useful coding techniques. For instructions, see the help documentation included in the code.

REBOL [Title: "Peer-to-Peer Instant Messenger"]

; The following line sets a flag variable, used to mark whether or not
; the two machines have already connected.  It helps to more gracefully
; handle connection and shutdown actions throughout the script:

connected: false

; The code below traps the close button (just a variation of the routine
; used in the earlier listview example).  It assures that all open ports
; are closed, and sends a message to the remote machine that the
; connection has been terminated.  Notice that the lines in the disconnect
; message are sent in reverse order.  When they're received by the other
; machine, they're printed out one at a time, each line on top of the
; previous - so it appears correctly when viewed on the other side:

insert-event-func closedown: func [face event] [
    either event/type = 'close [
        if connected [
            insert port trim {
                *************************************************
                AND RECONNECT.
                YOU MUST RESTART THE APPLICATION
                TO CONTINUE WITH ANOTHER CHAT,
                THE REMOTE PARTY HAS DISCONNECTED.
                *************************************************
            }
            close port
            if mode/text = "Server Mode" [close listen]
        ]
        quit
    ] [event]
]

view/new center-face gui: layout [
    across
    at 5x2  ; this code positions the following items in the GUI

    ; The text below appears as a menu option in the upper
    ; left hand corner of the GUI.  When it's clicked, the
    ; text contained in the "display" area is saved to a 
    ; user selected file:

     text bold "Save Chat" [
        filename: to-file request-file/title/file/save trim {
            Save file as:} "Save" %/c/chat.txt
        write filename display/text 
    ]

    ; The text below is another menu option.  It displays
    ; the user's IP address when clicked.  It relies on a
    ; public web server to find the external address.
    ; The "parse" command is used to extract the IP address
    ; from the page.  Parse is covered in a separate
    ; dedicated section later in the tutorial.

    text bold "Lookup IP" [
        parse read http://guitarz.org/ip.cgi [
            thru <title> copy my-ip to </title>
        ]
        parse my-ip [
            thru "Your IP Address is: " copy stripped-ip to end
        ]
        alert to-string rejoin [
            "External: " trim/all stripped-ip "  "
            "Internal: " read join dns:// read dns://
        ]
    ]

    ; The text below is a third menu option.  It displays 
    ; the help text when clicked.

     text bold "Help" [
        alert {
        Enter the IP address and port number in the fields
        provided.  If you will listen for others to call you, 
        use the rotary button to select "Server Mode" (you
        must have an exposed IP address and/or an open port
        to accept an incoming chat).  Select "Client Mode" if
        you will connect to another's chat server (you can do
        that even if you're behind an unconfigured firewall, 
        router, etc.).  Click "Connect" to begin the chat. 
        To test the application on one machine, open two
        instances of the chat application, leave the IP set
        to "localhost" on both.  Set one instance to run as 
        server, and the other as client, then click connect.
        You can edit the chat text directly in the display
        area, and you can save the text to a local file.
        }
    ]
    return

    ; Below are the widgets used to enter connection info.
    ; Notice the labels assigned to each item.  Later, the
    ; text contained in these widgets is referred to as
    ; <label>/text.  Take a good look at the action block 
    ; for the rotary button too.  Whenever it's clicked, 
    ; it either hides or shows the other widgets.  When in
    ; server mode, no connection IP address is needed - the
    ; application just waits for a connection on the given
    ; port.  Hiding the IP address field spares the user some
    ; confusion.

    lab1: h3 "IP Address:"  IP: field "localhost" 102
    lab2: h3 "Port:" portspec: field "9083" 50
    mode: rotary 120 "Client Mode" "Server Mode" [
        either value = "Client Mode" [
            show lab1 show IP
        ][
            hide lab1 hide IP
        ]
    ]

    ; Below is the connect button, and the large action block
    ; that does most of the work.  When the button is clicked,
    ; it's first hidden, so that the user isn't tempted to
    ; open the port again (that would cause an error).  Then,
    ; a TCP/IP port is opened - the type (server/client) is
    ; determined using an "either" construct.  If an error
    ; occurs in either of the port opening operations, the
    ; error is trapped and the user is alerted with a message -
    ; that's more graceful and informative than letting the 
    ; program crash with an error.  Notice that the IP
    ; address and port info are gathered from the fields above.
    ; If the server mode is selected (i.e., if the "mode" button
    ; above isn't displaying the text "Client Mode"), then the
    ; the TCP ports are opened in listening mode - waiting
    ; for a client to connect.  If the client mode is selected,
    ; an attempt is made to open a direct connection to the IP
    ; address and port selected. 

    cnnct: button red "Connect" [
        hide cnnct
        either mode/text = "Client Mode" [
            if error? try [
                port: open/direct/lines/no-wait to-url rejoin [
                    "tcp://" IP/text ":" portspec/text]
            ][alert "Server is not responding." return]
        ][
            if error? try [
                listen: open/direct/lines/no-wait to-url rejoin [
                    "tcp://:" portspec/text]
                wait listen
                port: first listen
            ][alert "Server is already running." return]
        ]

        ; After the ports have been opened, the text entry field
        ; is highlighted, and the connection flag is set to true.
        ; Focusing on the text entry field provides a nice visual
        ; cue to the user that the connection has been made, but
        ; it's not required.

        focus entry
        connected: true

        ; The forever loop below continuously waits for data to
        ; appear in the open network connection.  Whenever data
        ; is inserted on the other side, it's copied and
        ; appended to the current text in the display area, and
        ; then the display area is updated to show the new text.

        forever [
            wait port
            foreach msg any [copy port []] [
                display/text: rejoin [
                    ">>>  "msg newline display/text]
            ]
            show display
        ]
    ]

    ; Below are the display area and text entry fields.  Notice
    ; the labels assigned to each.  The "return"s just put each
    ; widget on a new line in the GUI (because the layout mode
    ; is set to "across" above).

    return  display: area "" 537x500
    return  entry: field 428  ; the numbers are pixel sizes

    ; The send button below does some more important work.
    ; First, it checks to see if the connection has been made
    ; (using the flag set above).  If so, it inserts the text
    ; contained in the "entry" field above into the open TCP/IP
    ; port, to be picked up by the remote machine - if the 
    ; connection has been made, the program on the other end
    ; is waiting to read any data inserted into that port.
    ; After sending the data across the network connection,
    ; the text is appended to the local current text display
    ; area, and the display is updated:

    button "Send Text" [
        if connected [
            insert port entry/text focus entry
            display/text: rejoin [
                "<<<  " entry/text newline display/text]
            show display
        ]
    ]
]

show gui do-events  ; these are required because the "/new"
                    ; refinement is used above.

9.2.4 Transferring Binary Files Through TCP Network Sockets:

These 2 scripts based on http://www.rebol.net/cookbook/recipes/0058.html, by Carl Sassenrath (edited and condensed here), demonstrate how to transfer binary files directly between any two networked computers (across a TCP socket connection), using ports. Sending binary files is different from sending text in that the length of the file must be transmitted before sending the file. That must be done so that the receiving code knows when the complete file has been transmitted. The sending script below appends the file length information, and the file name, to the data being sent. The receiving script searches for that information, then receives the specified amount of binary data and saves it to a file when complete:

REBOL [Title: "Server/Receiver"]

p: ":8000"  ; port #
print "receiving"

data: copy wait client: first port: wait open/binary/no-wait join tcp:// p
info: load to-string copy/part data start: find data #""
remove/part data next start
while [info/2 > length? data] [append data copy client]
write/binary (to-file join "transferred-" (second split-path info/1)) data

insert client "done" wait client close client close port print "Done" halt


REBOL [Title: "Client/Sender"]

ip: "localhost" p: ":8000"  ; IP address and port #
print "sending"

data: read/binary file: to-file request-file
server: open/binary/no-wait rejoin [tcp:// ip p]
insert data append remold [file length? data] #""
insert server data

wait server close server print "Done" halt

Here's a more compact example that demonstrates how to create and send an image from one computer to another:

; server/receiver - run first:

if error? try [port: first wait open/binary/no-wait tcp://:8] [quit]
mark: find file: copy wait port #""
length: to-integer to-string copy/part file mark
while [length > length? remove/part file next mark] [append file port]

view layout [image load file]

; client/sender - run after server (change IP address if using on 2 pcs):

save/png %image.png to-image layout [box blue "I traveled through ports!"]

port: open/binary/no-wait tcp://127.0.0.1:8  ; adjust this IP address
insert file: read/binary %image.png join l: length? file #""
insert port file

The following program is a walkie-talkie push-to-talk type of voice over IP application. It's extremely simple - it just records sound from mic to .wav file, then transfers the wave file to another IP (where the same program is running), for playback. Sender and receiver open in separate processes, and both run in forever loops to enable continuous communication back and forth. As it stands, this is a MS Windows only application. The code which handles the sound recording is discussed in more detail in the section of this tutorial about DLLs:

REBOL [Title: "Intercom (VOIP Messenger)"]

write %wt-receiver.r {
    REBOL []
    print join "Receiving at " read join dns:// read dns://
    if error? try[c: first t: wait open/binary/no-wait tcp://:8000][quit]
    s: open sound://
    forever [
        d: copy wait c
        if error? try [i: load to-string copy/part d start: find d #""] [
            print "^lclient closed" close t close c close s wait 1 quit
        ]
        remove/part d next start
        while [i/2 > length? d] [append d copy c]
        write/binary (to-file join "t-" (second split-path i/1))
            decompress to-binary d
        insert s load %t-r.wav wait s
    ]
}
launch %wt-receiver.r

lib: load/library %winmm.dll
mciExecute: make routine! [c [string!] return: [logic!]] lib "mciExecute"

if (ip: ask "Connect to IP (none = localhost):  ") = "" [ip: "localhost"]
if error? try [s: open/binary/no-wait rejoin [tcp:// ip ":8000"]] [quit]

mciExecute "open new type waveaudio alias buffer1 buffer 4"
forever [
    x: ask "^lPress [ENTER] to start sending sound (or 'q' to quit): "
    if find x "q" [close s  free lib  break]
    ; if (ask "^lPress [ENTER] to send sound ('q' to quit): ") = "q"[quit]
    mciExecute "record buffer1"
    ask "^l*** YOU ARE NOW RECORDING SOUND ***   Press [ENTER] to send:  "
    mciExecute join "save buffer1 " to-local-file %r.wav
    mciExecute "delete buffer1 from 0"
    data: compress to-string read/binary %r.wav
    insert data append remold [%r.wav length? data] #""
    insert s data
]

Here's a more compact version of the above application, with hands-free operation enabled (several obfuscated versions of this script can be found at the end of this tutorial - likely the most compact VOIP programs you'll find anywhere):

REBOL [title: "VOIP"] do [write %ireceive.r {REBOL []
if error? try [port: first wait open/binary/no-wait tcp://:8] [quit]
wait 0  speakers: open sound://
forever [
    if error? try [mark: find wav: copy wait port #""] [quit]
    i: to-integer to-string copy/part wav mark
    while [i > length? remove/part wav next mark] [append wav port]
    insert speakers load to-binary decompress wav
]} launch %ireceive.r
lib: load/library %winmm.dll
mci: make routine! [c [string!] return: [logic!]] lib "mciExecute"
if (ip: ask "Connect to IP (none = localhost):  ") = "" [ip: "localhost"]
if error? try [port: open/binary/no-wait rejoin [tcp:// ip ":8"]] [quit]
mci "open new type waveaudio alias wav"
forever [
    mci "record wav"  wait 2  mci "save wav r"  mci "delete wav from 0"
    insert wav: compress to-string read/binary %r join l: length? wav #""
    if l > 4000 [insert port wav]  ; squelch (don't send) if too quiet
]]

For more information on ports, see http://www.rebol.com/docs/core23/rebolcore-14.html, http://stackoverflow.com/questions/1291127/rebol-smallest-http-server-in-the-world-why-first-wait-listen-port, and http://www.rebol.net/docs/async-ports.html.

9.3 Parse (REBOL's Answer to Regular Expressions)

The "parse" function is used to import and convert organized chunks of external data into the block format that REBOL recognizes natively. It also provides a means of dissecting, searching, comparing, extracting, and acting upon organized information within unformatted text data (similar to the pattern matching functionality implemented by regular expressions in other languages).

The basic format for parse is:

parse <data> <matching rules>

Parse has several modes of use. The simplest mode just splits up text at common delimiters and converts those pieces into a REBOL block. To do this, just specify "none" as the matching rule. Common delimiters are spaces, commas, tabs, semicolons, and newlines. Here are some examples:

text1: "apple orange pear"
parsed-block1: parse text1 none

text2: "apple,orange,pear"
parsed-block2: parse text2 none

text3: "apple        orange                    pear"
parsed-block3: parse text3 none

text4: "apple;orange;pear"
parsed-block4: parse text4 none

text5: "apple,orange pear"
parsed-block5: parse text5 none

text6: {"apple","orange","pear"}
parsed-block6: parse text6 none

text7: {
apple
orange  
pear
}
parsed-block7: parse text7 none

To split files based on some character other than the common delimiters, you can specify the delimiter as a rule. Just put the delimiter in quotes:

text: "apple*orange*pear"
parsed-block: parse text "*"

text: "apple&orange&pear"
parsed-block: parse text "&"

text: "apple    &    orange&pear"
parsed-block: parse text "&"

You can also include mixed multiple characters to be used as delimiters:

text: "apple&orange*pear"
parsed-block: parse text "&*"

text: "apple&orange*pear"
parsed-block: parse text "*&" ; the order doesn't matter

Using the "splitting" mode of parse is a great way to get formatted tables of data into your REBOL programs. Splitting the text below by carriage returns, you run into a little problem:

text: {    First Name
           Last Name
           Street Address
           City, State, Zip}

parsed-block: parse text "^/" 

; ^/ is the REBOL symbol for a carriage return

Spaces are included in the parsing rule by default (parse automatically splits at all empty space), so you get a block of data that's more broken up than intended:

["First" "Name" "Last" "Name" "Street" "Address" "City,"
    "State," "Zip"]

You can use the "/all" refinement to eliminate spaces from the delimiter rule. The code below:

text: {    First Name
           Last Name
           Street Address
           City, State, Zip}

parsed-block: parse/all text "^/"

converts the given text to the following block:

[" First Name" "      Last Name" "      Street Address"
    "      City, State, Zip"]

Now you can trim the extra space from each of the strings:

foreach item parsed-block [trim item]

and you get the following parsed-block, as intended:

["First Name" "Last Name" "Street Address" "City, State, Zip"]

Parse is commonly used to convert spreadsheet data into REBOL blocks. In Excel, Open Office, and other spreadsheet programs, you can export all the columns of data in a worksheet by saving it as a CSV formatted ("comma separated value") .csv text file. People often put various bits of descriptive text, labels and column headers into spreadsheets to make them more readable:

;                         TITLE
                       DESCRIPTION

                         Header1 

Category1      data       data      data        Notes...
               data       data      data
               data       data      data
               data       data      data

                         Header2

Category2      data       data      data        Notes...
               data       data      data
               data       data      data
               data       data      data

                         Header3

Category3      data       data      data        Notes...
               data       data      data
               data       data      data
               data       data      data

The following code turns the exported CSV spreadsheet data into a nice useable REBOL block, with group heading data added to each line:

; Read and parse the CSV formatted file:

filename: %filename.csv
data: copy []
lines: read/lines filename
foreach line lines [
    append/only data parse/all line ","
]

; Add headers from sections of the spreadsheet to each line item:

info: copy ""
foreach line data [
    either find "Header" line/1 [
        info: line/1
    ][
        append line info
    ]
]

; Remove the unwanted descriptive header lines:

remove-each line data [find "Header" line/1/1]
remove-each line data [
    (line/3 = "TITLE") or (line/3 = "DESCRIPTION")
]

Pattern Matching Mode:

You can use parse to check whether any specific data exists within a given block. To do that, specify the rule (matching pattern) as the item you're searching for. Here's an example:

parse ["apple"] ["apple"]

parse ["apple" "orange"] ["apple" "orange"]

Both lines above evaluate to true because they match exactly. IMPORTANT: By default, as soon as parse comes across something that doesn't match, the entire expression evaluates to false, EVEN if the given rule IS found one or more times in the data. For example, the following is false:

parse ["apple" "orange"] ["apple"]

But that's just default behavior. You can control how parse responds to items that don't match. Adding the words below to a rule will return true if the given rule matches the data in the specified way:

  1. "any" - the rule matches the data zero or more times
  2. "some" - the rule matches the data one or more times
  3. "opt" - the rule matches the data zero or one time
  4. "one" - the rule matches the data exactly one time
  5. an integer - the rule matches the data the given number of times
  6. two integers - the rule matches the data a number of times included in the range between the two integers

The following examples are all true:

parse ["apple" "orange"] [any string!]
parse ["apple" "orange"] [some string!]
parse ["apple" "orange"] [1 2 string!]

You can create rules that include multiple match options - just separate the choices by a "|" character and enclose them in brackets. The following is true:

parse ["apple" "orange"] [any [string! | url! | number!]]

You can trigger actions to occur whenever a rule is matched. Just enclose the action(s) in parentheses:

parse ["apple" "orange"] [any [string! 
    (alert "The block contains a string.") | url! | number!]]

You can skip through data, ignoring chunks until you get to, or past a given condition. The word "to" ignores data UNTIL the condition is found. The word "thru" ignores data until JUST PAST the condition is found. The following is true:

parse [234.1 $50 http://rebol.com "apple"] [thru string!]

The real value of pattern matching is that you can search for and extract data from unformatted text, in an organized way. The word "copy" is used to assign a variable to matched data. For example, the following code downloads the raw HTML from the REBOL homepage, ignores everything except what's between the HTML title tags, and displays that text:

parse read http://rebol.com [
    thru <title> copy parsed-text to </title> (alert parsed-text)
]

The following code extends the example above to provide the useful feature of displaying the external ip address of the local computer. It reads http://guitarz.org/ip.cgi, parses out the title text, and then parses that text again to return only the IP number. The local network address is also displayed, using the built in dns protocol in REBOL:

parse read http://guitarz.org/ip.cgi [
    thru <title> copy my-ip to </title>
]
parse my-ip [
    thru "Your IP Address is: " copy stripped-ip to end
]
alert to-string rejoin [
    "External: " trim/all stripped-ip "  "
    "Internal: " read join dns:// read dns://
]

Here's a useful example that removes all comments from a given REBOL script (any part of a line that begins with a semicolon ";"):

code: read to-file request-file

parse/all code [any [
    to #";" begin: thru newline ending: (
        remove/part begin ((index? ending) - (index? begin))) :begin
    ]
]

editor code

For more about parse, see the following links:

http://www.codeconscious.com/rebol/parse-tutorial.html
http://www.rebol.com/docs/core23/rebolcore-15.html
http://en.wikibooks.org/wiki/REBOL_Programming/Language_Features/Parse
http://www.rebolforces.com/zine/rzine-1-06.html#sect4.

9.4 2D Drawing, Graphics, and Animation

With REBOL's "view layout" ("VID") dialect you can easily build graphic user interfaces that include buttons, fields, text lists, images and other GUI widgets, but it's not meant to handle general purpose graphics or animation. For that purpose, REBOL includes a built-in "draw" dialect. Various drawing functions allow you to make lines, boxes, circles, arrows, and virtually any other shape. Fill patterns, color gradients, and effects of all sorts can be easily applied to drawings.

Implementing draw functions typically involves creating a 'view layout' GUI, with a box widget that's used as the viewing screen. "Effect" and "draw" functions are then added to the box definition, and a block is passed to the draw function which contains more functions that actually perform the drawing of various shapes and other graphic elements in the box. Each draw function takes an appropriate set of arguments for the type of shape created (coordinate values, size value, etc.). Here's a basic example of the draw format:

view layout [box 400x400 effect [draw [line 10x39 322x211]]]
;  "line" is a draw function

Here's the exact same example indented and broken apart onto several lines:

view layout [
    box 400x400 effect [
        draw [
            line 10x39 322x211
        ]
    ]
]

Any number of shape elements (functions) can be included in the draw block:

view layout [
    box 400x400 black effect [
        draw [
            line 0x400 400x50
            circle 250x250 100
            box 100x20 300x380
            curve 50x50 300x50 50x300 300x300
            spline closed 3 20x20 200x70 150x200
            polygon 20x20 200x70 150x200 50x300
        ]
    ]
]

Color can be added to graphics using the "pen" function. Shapes can be filled with color, with images, and with other graphic elements using the "fill-pen" function. The thickness of drawn lines is set with the "line-width" function:

view layout [
    box 400x400 black effect [
        draw [
            pen red
            line 0x400 400x50
            pen white
            box 100x20 300x380
            fill-pen green
            circle 250x250 100
            pen blue
            fill-pen orange
            line-width 5
            spline closed 3 20x20 200x70 150x200
            polygon 20x20 200x70 150x200 50x300
        ]
    ]
]

Gradients and other effects can be easily applied to the elements:

view layout [
    box 400x220 effect [
        draw [
            fill-pen 200.100.90
            polygon 20x40 200x20 380x40 200x80
            fill-pen 200.130.110
            polygon 20x40 200x80 200x200 20x100
            fill-pen 100.80.50
            polygon 200x80 380x40 380x100 200x200
        ]
        gradmul 180.180.210 60.60.90
    ]
]

Drawn shapes are automatically anti-aliased (lines are smoothed), but that default feature can be disabled:

view layout [
    box 400x400 black effect [
        draw [
            ;  with default smoothing:
            circle 150x150 100
            ;  without smoothing:
            anti-alias off
            circle 250x250 100
        ]
    ]
]

9.4.1 Animation

Animations can be created with draw by changing the coordinates of image elements. The fundamental process is as follows:

  1. Assign a word label to the box in which the drawing takes place (the word "scrn" is used in the following examples).
  2. Create a new draw block in which the characteristics of the graphic elements (position, size, etc.) are changed.
  3. Assign the new block to "{yourlabel}/effect/draw" (i.e., "scrn/label/draw: [changed draw block]" in this case).
  4. Display the changes with a "show {yourlabel}" function (i.e., "show scrn" in this case).

Here's a basic example that moves a circle to a new position when the button is pressed:

view layout [
    scrn: box 400x400 black effect [draw [circle 200x200 20]]
    btn "Move" [
        scrn/effect/draw: [circle 200x300 20]  ; replace the block above
        show scrn
    ]
]

Variables can be assigned to positions, sizes, and/or other characteristics of draw elements, and loops can be used to create smooth animations by adjusting those elements incrementally:

pos: 200x50
view layout [
    scrn: box 400x400 black effect [draw [circle pos 20]]
    btn "Move Smoothly" [
        loop 50 [
            ; increment the "y" value of the coordinate:
            pos/y: pos/y + 1
            scrn/effect/draw: copy [circle pos 20]
            show scrn
        ]
    ]
]

Animation coordinates (and other draw properties) can also be stored in blocks:

pos: 200x200
coords: [70x346 368x99 143x45 80x125 237x298 200x200]

view layout [
    scrn: box 400x400 black effect [draw [circle pos 20]]
    btn "Jump Around" [
        foreach coord coords [
            scrn/effect/draw: copy [circle coord 20]
            show scrn
            wait 1
        ]
    ]
]

Other data sources can also serve to control movement. In the next example, user data input moves the circle around the screen. Notice the use of the "feel" function to update the screen every 10th of a second ("rate 0:0:0.1"). Since feel is used to watch, wait for, and respond to window events, you'll likely need it in many situations where animation is used, such as in games:

pos: 200x200
view layout [
    scrn: box 400x400 black rate 0:0:0.1 feel [
        engage: func [face action event] [
            if action = 'time [
                scrn/effect/draw: copy []
                append scrn/effect/draw [circle pos 20]
                show scrn
            ]    
        ] 
    ] effect [ draw [] ]
    across
    btn "Up" [pos/y: pos/y - 10]
    btn "Down" [pos/y: pos/y + 10]
    btn "Right" [pos/x: pos/x + 10]
    btn "Left" [pos/x: pos/x - 10]
]

Here's a very simple paint program that also uses the feel function. Whenever a mouse-down action is detected, the coordinate of the mouse event ("event/offset") is added to the draw block (i.e., a new dot is added to the screen wherever the mouse is clicked), and then the block is shown:

view layout [
    scrn: box black 400x400 feel [
        engage: func [face action event] [
            if find [down over] action [
                append scrn/effect/draw event/offset
                show scrn
            ]
            if action = 'up [append scrn/effect/draw 'line]
        ]
    ] effect [draw [line]]
]

A useful feature of draw is the ability to easily scale and distort images simply by indicating 4 coordinate points. The image will be altered to fit into the space marked by those four points:

view layout [
    box 400x400 black effect [
        draw [
            image logo.gif 10x10 350x200 250x300 50x300
            ; "logo.gif" is built into the REBOL interpreter
        ]
    ]
]

Here's an example that incorporates the image scaling technique above with some animation. IMPORTANT: In the following example, the coordinate position calculations occur inside the draw block. Whenever such evaluations occur inside a draw block (i.e., when values are added or subtracted to a variable coordinate position, size, etc.), a "reduce" or "compose" function must be used to evaluate those values. Notice the tick mark (') next to the "image" function. Function words inside a reduced block need to be marked with that symbol to evaluate correctly:

pos: 300x300
view layout [
    scrn: box pos black effect [
        draw [image logo.gif 0x0 300x0 300x300 0x300]
    ]
    btn "Animate" [
        for point 1 140 1 [
            scrn/effect/draw: copy reduce [
                'image logo.gif 
                (pos - 300x300)
                (1x1 + (as-pair 300 point))
                (pos - (as-pair 1 point))
                (pos - 300x0)
            ]
            show scrn
        ]
        for point 1 300 1 [
            scrn/effect/draw: copy reduce [
                'image logo.gif 
                (1x1 + (as-pair 1 point))
                (pos - 0x300)
                (pos - 0x0)
                (pos - (as-pair point 1))
            ]
            show scrn
        ]
        ; no "reduce" is required below, because no calculations
        ; occur in the draw block - they're just static coords: 
        scrn/effect/draw: copy [
            image logo.gif 0x0 300x0 300x300 0x300
        ]
        show scrn
    ]
]

Here's another example of a draw block which contains evaluated calculations, and therefore requires "reduce"d evaluation:

view layout [
    scrn: box 400x400 black effect [draw [line 0x0 400x400]]
    btn "Spin" [
        startpoint: 0x0
        endpoint: 400x400
        loop 400 [
            scrn/effect/draw: copy reduce [
                'line 
                startpoint: startpoint + 0x1
                endpoint: endpoint - 0x1
            ]
            show scrn
        ]
    ]
]

The useful little paint program at http://rebol.org/cgi-bin/cgiwrap/rebol/view-script.r?script=paintplus.r consists of only 238 lines of code. Take a look at it to see how efficient REBOL's draw code is:

url: http://rebol.org/cgi-bin/cgiwrap/rebol/download-a-script.r?
script: "script-name=paintplus.r"
do rejoin [url script]
paint none []

For more information about built-in shapes, functions, and capabilities of draw, see http://www.rebol.com/docs/draw-ref.html, http://www.rebol.com/docs/draw.html, http://translate.google.com/translate?hl=en&sl=fr&u=http://www.rebolfrance.info/org/articles/login11/login11.htm (translated by Google), http://www.rebolforces.com/zine/rzine-1-05.html, http://www.rebolforces.com/zine/rzine-1-06.html (updated code for these two tutorials is available at http://mail.rebol.net/maillist/msgs/39100.html). A nice, short tutorial demonstrating how to build multi-player, networked games with draw graphics is available at RebolFrance (translated by Google). Also be sure to see http://www.nwlink.com/~ecotope1/reb/easy-draw.r (a clickable rebsite version is available in the REBOL Desktop -> Docs -> Easy Draw).

9.5 Using Animated GIF Images

Another easy way to work with animations in REBOL is with the "anim" style in GUIs. Anim takes a series of still image frames, and plays them in order as an animation with a given rate. The basic format is:

view layout [
    speed: 10
    anim rate (speed) [%image1.gif %image2.gif etc...]
]

The following script will convert an animated .gif into a folder filled with individual frame images:

REBOL []

gif-anim: load to-file request-file
make-dir %./frames/
count: 1

for count 1 length? gif-anim 1 [
    save/png rejoin [
        %./frames/ "your_file_name-" count ".png"
    ] pick gif-anim count
]

This next script will convert a directory of images (such as above, or any other series of images) into an embeddable block of REBOL code. It looks for all the images named [%your_file_name-1.* your_file_name-2.* etc...]:

REBOL []

system/options/binary-base: 64    
file-list: read  %./frames/
anim-frames-block: copy []
foreach file file-list [
    ; Unique portion of file names for your image frames go here.
    ; Leave out this check if you instead want to convert all
    ; files in the directory:
    if find to-string file "your_file_name-" [ 
        print file
        uncompressed: read/binary file
        compressed: compress to-string uncompressed
        append anim-frames-block compressed
    ]
]

editor anim-frames-block

Here's some sample output:

anim-frames-block: [64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zCHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5uWnp5ukHxqjufmZWdnWxS/OsPJcODoPLtKprUcW9TPLXJT
V7LdFZIZuMx/Ll/rrJCkC3NZ1ztd6SpVCG+L363EsXpCTvhmtovzVCWurr7R6jG7
rzZarKFpd8XTS77Z1/Xu7Qn+vunr6+/v725rqv6nm/Oj4Or2Ll7jvDUOa8+e6FX3
3uYjbPz0fN/RKjbeWcU+Z5do2qfN2lWaelnXfbveKwkz7ytLqu0qBK6Xed1cyfhG
TC58xeujhyuF422FXxQeOPybbR1nzbbP18+khtXvu/H95Ns7Gzdv5ZtfaVX64fjZ
crf/d6xPvV7XmJ7PZ1/x/ueXm/nXrOfVZKyZ+DL8nt85zhWzqu8LPosvPyYZEdW8
QrJjvjdj3TOFJuXQFVEVEl0iC9L49pVJJvZcnR7XLn/w+ux64XUpizrvbF0R1PFx
4QvB3s29OxLylB9tW9Cj9+vEol5NLk+5ia7vLB74GvxbETxZRklSqI+HyWNpR7ri
VbkJtreOp05nF1O/EeGW9C01/RqjmVrF3l7PZxnfPStv12qxsjBYAwBolvDW2AQA
AA==
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5uWnp5ukHxqjufmZWdnW6hqBUwQfnxuvkPltxaJLSsuLOTt
ZWPdIPzSaal3vZUth6nWhZUsq7NsrUqzQ9f47K17qyWmdW1T2txFsreLdW/Pydu6
rXe2mHrsYuf3j86uLn95Z1/Qf6ZnWeUGD2e38V/3WVOh9viYkfzh3Fvmb1Iap+oq
P7OUKH64ocH2tsisGfkvTy7nXi6nG/n11dGZzLv3RQt8On3c19zY7e8stbyDCxtf
h0rLZBZuKjyYFrv6jsLdZ8xr99lGi3wueRLuGN6+zqSq7MW1700y/hHle4o/PhP8
5Xt+397f3z88Pj3ff/++v79/vGdnYbAGAJfEqNM/BAAA
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blri2cIVNC+GU2Hp6elcEX0tnsbLfPpNs++9mTE57fRcyepfJZxfFgUsdNWU
s51l8ihoma+8XatU6cOQVaHCca6zQh+GrYvlrWOVnvbgxrzUo/POzrz2JmpuLuu+
VuntT+9ML316T3VWuf79HXX/t/GuKTJIPBj5UW7bzB0fko75frwVGzP1ffIRa934
tpiQp88O9Zq3q84pL3qwq593uZ621dus61NCJ097K/714b7l3tf1bAv03jfNmv/v
264t3wu2Hn0r9973y6uiy2aql235hJeef35hovexONmK8jc3rzapXLeL03r+6cXl
1fHn9+39/f3D49Pz/ffv+/v7x+fX98/v3////1NWFgZrALxatNdHBAAA
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5uWnp5ukHxKZMWCZWdnSuW+urOSId11nkP+rx6JLS8C2l0n
y6XO2PLyUovvXDtTCdNXV5pCl8YtnRn68tq6qOVNX6tKdW4uT+ud5sv9RTt6Xt79
Vz3a4Stu7Cq7+OitZ/i7i3tza5n4tCo+3JzWdniTz5oI1cfHNOVXt2pWqp87VaPv
LZf1413C3s7pdmKys0rSL88PZGbbe+vzva1rY3+/PV32+sCubRtnnd0rkJdwj/0h
0wyemh2p644UC7fl7H778NGh3vO6fKbGX1/f2Jx9/9ze3d/fPzjczSvvv2/Pz88v
Lq+Oj7dTYLAGANdbpyswBAAA
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5uWnp5ukHxqjufmZWdnWxS/unNy8/Lz8x2auWR/BTVeXOwi
Khe7y2Sl47KAiVamXApZV5b4rnWSXbVVO3RB3OF/PN7X1G9usjnfdXdl2dpz2/IK
D339VZZ3fVfZ2kdnd5uqx++t+/9tqvaMlWfXh3IrT7sZ/jHxaHim0zWtSqOnM6a9
FDtbU26cfkDPvrlNc1dm6kVTb22Lv5alaYfm5C+qu3OrNPfa+tzj13Ijv+XemZzI
zv9n+oq7Kye6f9+js2Fz5IFZx4PK+MR+JSy/sTn7/rm9u7+/f3C4m/m7pACDNQAX
yZ/iJgQAAA==
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5uWendyiezhkdy8zHemsfm9O5LG6m7zHGqjWKRCMo7MY+h4
Z/IrYGXwMp65dq2rAl6FrGJbG3fUKuB12DrPvVqs2gFvwlelHZ/ku3qadvSilMP7
9kqW653fWvay6ezq67rxS6r/P1qjPWPDg4Nu/N+/rvyh9/iYt7zzNs0So6enpi2M
cuuRNLp3qJH/d6hNlEnY+eXS09l6w0qzLq+PPP7s98yy3N2Fp5+dvTtVN78lqf77
u5XTi3wfHpYVj5lTnX3xfsHkeDe98qrS11catc/PK7D+/u74fnNpHv19e35+fnF5
dfz5fXt/f//w+PR8//37/v5mYGJisAYARqapGj4EAAA=
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8loBjYyMaj5ToqJNHjqOV0zsq/l56RnnjPlcq9t6Zy8+Nx8w+okFq8vywK6XDvl
ZGdNeR7Uyb9oUY7X55dH2INX7trCZbr62oIYSa+vv65mRDRHs05rrRR7GLU09+K+
v5LmD++sKuW/d3R2+YO4fbUn//G+MV+bsKpF9JzvnSKDx/vbhJ3DTkbo3j5coB2v
F72z4MzWubrBbLJWL25fWuZv7/d6y4q0bdMNj6udub7mzYnGuVV+v6qK8k/sl/We
l7Nb/+Ojyv5ytX0yFq/2LnRdfW3P79ef515b73/9nFRGSVPJ00c2fXwSf9685y1d
7B9ft/fu53ei/f3/5xnVtie8f33//P79wEKATeNBA4tYxoNGDrUVD5p4zF48aBZw
00h0ZGRksAYAd264o18EAAA=
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5tUvVi5Yia1eG5edqbPtPjSnpWBy/0YDCvDvvwsXh7Q6TL5
kI1UYGbQMv65Wq2nAl6FrApd++vIrA8HmRc4smbxni59cH294d46Vu2tOQc3OzDO
cc2+ujZiZ9zjc6mvr+hFNGV+/rT31bUX9xuTTybFWllsTFzXI5uv6xO2yXe3m669
nrfIxrAzDaLqx9bc2Jx8aVZ90bWcWYZXr6xj39+W++NT4K1VuZ9LeqPfpM2cWHj8
ytmQHx/u79b9zSf3e9un5iOth/QkYnd9fHVy/fSydbWl5e8PBbYHLreJ+1Oyv1d1
cX5tVe2Li+94t/X7y9b9Wf5y4mx3u5919d/Orr1+s8jyovr9ZFYpjol1XGYvHjQL
uGk8bBEJy3jYKpG24mGbTNmLh+0KbRqPOoTYWBisAbfrxM90BAAA
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5uWnp5ukHxqjufmZWdnWxRHhRwIfu46z6Hx1xSJLSsuLOTt
1XLdFfDy0mIfTqu5t4xfOayKWMt04NRVretrAvc3yWqVrTm/LnqlUuusba9Ct6aL
ctQ4mL+9syt3+jHWgO+Nd/fVPXxm88p8Q8y+Gl7/q5Il667sZjp7S0drqm7UHP/T
UrJ7LNc/2zFFOXudlNWyG9uzvs6yO1NgEj29V3RXH2/1tzfTthVv9lt52+zdvcXZ
zPZ/rb99OKfvLF+vu+d50Xaju3b3bSutnj+fsTx4/sra6pK3N9fed2Op/2uR/OZ5
+/pQf7GKiJ37tlb905I3LVw7s//St1W7NgW8f/l1+41qZr6O+MxvjuH3m3jMXjxo
FnDTeNgiEpbxsFUibUViGyMjgzUAhlm/D2kEAAA=
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJGcFnIAgdVr2kGybtEJDernZmpnfsqp9P48bn5tvr/ZKSuPApY4Koo
Fzvry8OgZb6Sdq1Sog9DZjJlh/l6mLz2ZeDfU3c3SuClwzQm+RWsC6bqOC7JOrwo
Vnv72uht1gfbeK0n6MWtKW/8pbrj2/uI7QU/F9Vmf14XMbfnolxpjWlR3GGbyXZb
a3ZufLY619b5H8+vnNRL8z7K6ciWbnG80B7Y3SZrrZF7bVN+ee6q6uKr9/ZFM8/X
qfnx7s6xYPGrs+7oPXrWzex83qes6svaa+v/n9OrtUp9fX9ve7j/ux8fP3x61rjY
vLZ6b+iNdzsPre/9l5a86itjv21cXGXk5p+Wx+fVM3K9CK15v7MtwZlL74RCAp+b
xsMWkbCMh60SaSsetsmUvXjYrtCm8ahDZVrGo06NPFEBBmsAOJHArHoEAAA=
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4a8uKBYvd+6Wd
i/54bFp8YjKf9yqTzk2ph6ZqxZ4S4dj87Mw00+J7IjM3Pz/Xa1v674jElecXJrom
yq3NKFbwWC4/PSiE68FB5llMay/1aJkuClobLhqyV2pa9vUp8SeZBLjL1t7czDM7
S9ViukrMlpCNYj2V5YlB03x/7/uzu3RpQqsjL5tdjYFhyIF8yfehWT82Rmz3VxXf
9rvi0+VJs8zdv8lsLYo/NK2b699pqS93r20wLu/lrTbNvbYt3/rcWmv9x5f2prb7
1VZbvHxwrPO1n94u8+IzB/XV+/VsTEpfXl5pn+9Xbf3l6b2J1cHP+6psKhc/43zk
d99Cs/qrXW17eW3Nl7Jfp1aff17zb2/Rjz8/v8uWMf1aGt/IobbiQROP2YsHzQJu
Gg9bRMIyHrZKpK142CZT9uJhu0KbxqMOlWk7Eh0YrAGyBMCKdgQAAA==
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5uWnp5ukHxqjufmZb79XEWvrlROfnRuvn21F4tXSOOFNptu
JttVBisuzfURtJsrdfXBleWhnHFLZ5VqX18V18lnImW6JmwT/yamD1ofHG9tZbi0
TLV6ytrbOwqeHkrNCtePaiypntX7u+z9rTml7OIxWiZrbhy2kbbm45IsTDrevTDu
GM/PgptrkzWj360qefhi9nLH+b09VUa3Z62zPN+zNkLt7fVt+eK21tHf8w40Jv7S
Oxv148Pxg73y1898t4h4Pnvh9rh5c9S+XjZbH/5+757K7y/22bc716+Lzn168ln4
db/1917kfwvbOH+6/zzLD8ez7p/X9/u1/d+fiEq2+Joe3owHjRxqKx408Zi9eNAs
4KbxsEUkLONhq0SaqACDNQAYMLy/ZgQAAA==
}]

And here's an example of how to write the files in that block back to the hard drive and display them in a GUI:

; Write files:

count: 1
make-dir %./frames/
for count 1 length? anim-frames-block 1 [
    write/binary rejoin [
        %./frames/ "frame-" count ".gif"
    ] to-binary decompress pick anim-frames-block count
]

; Create file list, with frames in numerical order:

file-list: read %./frames/
animation-frames: copy []
for count 1 length? file-list 1 [
    append animation-frames rejoin [
        %./frames/ "frame-" count ".gif"
    ]
]

; Display that file list as an animation:

view layout [
    anim: anim rate 10 frames animation-frames
]

Here's an example that combines the above animated GIF files with normal GUI animation:

view center-face layout [
    size 625x415
    backcolor black
    anim: anim rate 10 frames load animation-frames
    btn "Run Animation" [
        for counter 0 31 1 [
            anim/offset: anim/offset + (as-pair counter 0)
            show anim wait .05
        ]
        for counter 0 24 1 [
            anim/offset: anim/offset + (as-pair 0 counter) 
            show anim wait .05
        ]
        for counter 0 31 1 [
            anim/offset: anim/offset + (as-pair (negate counter) 0)
            show anim wait .05
        ]
        for counter 0 24 1 [
            anim/offset: anim/offset + (as-pair 0 (negate counter))
            show anim wait .05
        ]
    ]
]

9.6 3D Graphics with r3D

The "r3D" modeling engine by Andrew Hoadley is built entirely from native REBOL 2D draw functions. It demonstrates the significantly powerful potential of draw. The examples below show some of what you can accomplish with r3D:

do http://www.rebol.net/demos/BF02D682713522AA/i-rebot.r
do http://www.rebol.net/demos/BF02D682713522AA/histogram.r
do http://www.rebol.net/demos/BF02D682713522AA/objective.r

The r3D engine is small. Here's the entire module in compressed, embeddable format (this is all just standard REBOL code compressed into a more compact format). To enable 3D graphics in your REBOL programs, just include this text in your code (paste it, or "do" it from a file). If you'd like to read and learn from the pure REBOL code that makes up this module, see the examples above (the r3D module is included in those examples as regular text code):

do to-string decompress 64#{
eJzdPGtT28iWn+Nf0cOXsTMYkGXznL1bBMzEtQSnjPMAipqSpTboRpa8kmwwv37P
Od0tdethOzNTu1VLJUTqPu9XP5VR/8Pwmj002NhPA37KdmL7cqfBzhfpcxTD63no
xfyFfYwcL+Ar6PnK48SPwlNm7R3sHTQeG43GGbuI5qvYf3pOWdNtsc7BweEuMzER
6jOPZ36C2MxP2DOP+WTFnmInTLm3y6Yx5yyaMvfZiZ/4Lksj5oQrNgd+gBBNUscP
/fCJOcwFbgiZPgOZJJqmL07MGTBwQo85SRK5vgMkmRe5ixkPUydFllM/4Alrps+c
7dxKpJ0W8fG4EzA/ZNinutiLDyZYpCzmSRr7LtLYBR5+6AYLDwVRAIE/8yUPJEB2
SJDsIgE1UNhdNos8f4r/ctJtvpgEfvK8yzwfiU8WKTQm2OjyELCELvtRzBIeBEjD
B9lJ5VzCXdIX+MzRrqm0FHF+eY5mpjZgqekiDoEpJxwvAsshH+D6b+6m2IYI0ygI
ohdUz41Cz0etklN03hg6nUm05KSRcHYYpSCwEAN9Mc8dLLuSZwfkn3BpNuANZnY0
lWIUIEkhBnzwAfCZRzExLWq7R0J87LPb4dX42/mozwa37PNo+HVw2b+EOL2F951d
9m0w/jj8MmYAMTq/Gd+x4RU7v7lj/zW4udxl/e+fR/3bWzYcscGnz9eDPrQNbi6u
v1wObv5gHwAPmNwMx+x68GkwBrrjIfGU1Ab9W6T3qT+6+Aiv5x8G14Px3S67Goxv
kOwV0D1nn89H48HFl+vzEfv8ZfR5eNsHCS4F5ZvBzdUIePU/9W/Ge8Ab2lj/K7yw
24/n19fE7fwL6DAiKS+Gn+9Ggz8+jtnH4fVlHxo/9EG48w/XfcENVLu4Ph982mWX
55/O/wABgQ90DIHQiCCljN8+9qkJWJ7Dn4vxYHiDylwMb8YjeN0FXUfjDPXb4LYP
OTwa3KJlrkZD4IB2BQw03g0m/fDmpi/ooNVN5wAQvn+57efSXPbPr4HaLcqgA+9R
DWn/1R+qP1BDwpSCJra99syBrHrdi1mbiUeK0SXEOQRc4E9iJ141zpCrG3OoFZCH
0ynUoxDyYDWXqQaFKZlG8YxSO2k0kLDvAYyfrk5ZA2qm9gPVkB3kf40+bNP7S506
QGWnAsByi2Jgqs9BG38JJXu6CF0mpNm5EOo4TINQJsB6BT1LB6sAZvg88kFhjy1C
H0w3jWXNcB2oUs4OEfTYg8ddf+YEvzyyHQ/LCIJg2XJCyG9ZNYo021Bfwl9TqF4p
86m2vPE4Yr/8stN4NO1GLzH3FkDsQTeSVL/WkOq91l5rAZDKPmiH/z7SIIZmJZcH
YMAao6p+LHDCqMJK3w0rZVSgXZjWCSIwDRoKAvHVTwTWXQ3WXQlrpWHd12Ddl7De
JJa0eJWRv5eto7rv6g13vz5IH1WUJq4T1JmS+tYYUfRHYmyCaYLDpg5lL2Tm960M
uo7C3VbG3ZZCjaG/F2xTMtqdBlBp0ftNMW6YO45gFsK/GwY3jKoEhKrnBO6CwiYK
5QCegGKc1ElwiIYWN0ogsYMFTwgLmpG4eGDfqQ1ARKt4gNZSqCmRdfG155JqGll6
brYF51aNKTLRZJuOW4tQJ1CFPe8Me94V7fk3DXqXGfROM+hdZtA706Cyu6R81lKp
rdLMbCtCS0PftUqOuFtjyWqrmmVVGPLeMKSeaf+AHe8zO95rdrzP7HhfZcd7Xe/7
lqmJoW0OnZEuab7RPEU3bIWgno3IhDLE254fc1qVGHa9hAka/jxMgsj9gVXsK816
bIbTAViD4CCdoWIlmwY4lxf1bzFnBewvczVtmjkrMZGnZVlOgr+6fA6rDyeGeT0P
cLAHIQS9fSAD0/pXrI9sRb/f8LeqRPRySlO2EGdasCjiiE2dr1Wd+ObGUZLM4wh8
maLInkRYbYWAFhICNczZh2jdtzJS4lEIKZ+LgSFxOjlOR8PprMOxcxxbw7GrcbSY
2Fi85BwR4hQd1BYDmBEloulPBWL6/IwWaDZ5dMZTmEgyXEHa4HNYTePqDlwqgkIk
oCLzZxr9GUTRjz+dNKdmxJX+oyD0QEHXP2aBLFx5u5jALAccV8WmqMg/FVUQUiD0
/1ZUFbTYt346xooUOj8dcUUK9t+Nv1n3ddY1om5m5WEx61QGQMyTRZDmMyl8OxUb
Pg8CEFZk3HGf2YPDJsxl3iORzYR15nMOQ4TAzJ1gjnQOew8C7Fst9htrTsRLj15c
8XJCL54Es1s5biWhjk7oUCdkHRiUuq1qArZO4MggYBkEejUEujqBY4NAxyBwqBF4
NJR61EyuBhrw4dI2fZi7cLnGg9lcM+WzOTSZPiz6tc6xZb9Kcuscu9T8upSucemZ
rOzV66/mIGKNYu2/sCRiLzgVCXDAW8LiP5+WpPwZFoGghj+HXrE6wDKFVag5dRKo
m9LUfrhsv5wqkZti6SkVAcc1asNWwVggPRHZDNrZHtReAwoyqglTuojDqqhoO3Hs
rIzYWPIYN/2SQvGfddcFCiLx120yXkBqXDYlfSYriiCxH3Vfl7Qj9dxoBlWQt4vV
i9aqUP1TY6hk1ALzKBiNAg5+ZxbrvnYleHVtE1PUWbcdcJi9wa+n9Pk/NQYqEn3a
AoWxF8ffMGI8BAiKfpIb58RqQ0rnB2iSOPsPdqAniVJY28kyY98PfdyIxSGO9rKF
RWnniHaH/RgURDFWar+c5C25LtcFBrOiNjBIRjAnF2QyXRxJuKyCVaWCLDOG9BAp
jEIlFrTn6I5MbhVMRNlnnYyDPn6c5TmNiJApSz9aJIrfRDSH/BWkDTieLmiSCQNk
gx+b++4PPW4EY0lKyo7lpz7VaH+nMhq3GD7PWIYOcVoIykxalS0zqDOz/R78PcFn
SBqoJrP9Q3w5wF+Avo+tR/iCsBYCY+sxviCwdZitUaC0wMzxp+QGf4GfIFmxLGGV
97imS/SDjoywID9Fe3t75SWjQEcPizg9Vi6S01TsNhXGyopS56NA8YcUey+UXQd0
ICltINLdgN+r67fW45+w7fjbG/A38d+Ev0k/+7HstkpnZUGQO6zRbJIL94UsYp4l
WrrUcqS19KjluNVi7QyvU8KzS3iHCq9RQOtpQEclNKmfyc8qIR6XEE/K/OwaNF1w
6Q+TX7dGUAOxkyGWXJUROiwROqkjdGRI0CshSsNUyH5UJYIhhuDQKfnLLvmrW2H3
Tik+7FJ8IF6jgGeVDG+XDNGtiCurZHe7bAeTYbcGsVO2s8nRrpG0UzazybFXQpSC
VWDaBsvDEuZJnaw2sazI9LxAJxyPl7WkZzUlWmh6XFNWZN6rwKkG6mxBoq5uyiCv
6bXX4lpb8K2tt1vwXY+7XqNehXfKPmm6kTjxSFrFoiyn3PlYXVOlrW4pDa1eKQ9h
WVqRUN1S5huohxlqsVZbdimlrIqiVOBqlXGPy7gnFVztWsxuOa0KXLu1EnfLmUW4
m+q2Va5XlbR6FaXbqqhZlUr0NlZvaRSr7DG9ZEhZDqpkMUqLgitFk2UUuJM61JOy
cyyD6VFZXmWAssAnBtOjsnDKyOVR6MQMOqsMZ9cJfGAOV1ZZ4l4JVelgWvikXuKy
w0Diqmru4d7wzA/hmfrg/ZQ1Zf1RdUGMGHmkNGUFyvo7pX7b6LdL/V2jv9vK1664
xqd7Y8vIx+tfeNYhl3OcNnASusmRCStvAfBUrcDqNx9etVL3UL3h0ASg90iNtYob
DIWVXL4EVHsOXpTKnWRzR0XbMV3+zI6pEmlpkTuWarMT3jvivaPebfFut9TKTd/X
/uvSNKrkedAlAM+2dQk6rcqCooNYCsXKhK5FsTI92zpXq5XtdOobCXoFk3YQ+zKm
BX7eBcl/L5yYt+MoSiFCmkshl3KHkEo5Q6iJhsmcce555g0KaMCbKBGbgF4ObUVO
IClDPJvjPm6wiFW+K27XyXMaNlmoq02zBay2AMR9Fmd0jqnTpFJFP+Wz7TbnBaiV
7Wo5bOP2fFNslDgClUwhWiayxdifrtglUUdEpqWyg6OJuNvk/L81WPtnDfZJ7nGZ
BlOtQHiyYmGtuRx1oywz1y6jK73yRL/STKF+4P9/Zaj3LNxkmkva5zcNI9r+slnC
KGzT/be/aR8cwtbZyJ8y2vHdwz3fzzFexFOig+AkgZPi9n/KvR2lvSONAbTVgEjn
wf+o1VHwNXYXls9OV3+65OJG5wprYn5A6wh95d0FdNcqWrAXPwjYE4zQZvfEcX8Q
IdqPz2s/E1JL/zaZ2lFvaRZQjngwLmbJY4YKuGwkxJFgn0hWj2GkPIwPBLMOxJZk
6o82WD6ozTEs1mzKSotmh3FMnsYx8ziOyOi9mntl36PJsnCOuKxliatP7C7womZW
w+bsL1waxsu/hWvD4HQ/5HsxgxyHdzxG8MDxohnvCzdG1GJo8hLFgWcGqbhBa7bB
hOrf8sZMATZcOsmt/wYxgZPuyo1xmjyK2S0+dTz8oIAH4rcQACqPEz4FPFlXITIg
s1nIOxA79iIB8u172Wse0gKIvBcp1W9k7iKZiMiQILTbDLm4pzkF7a4CNLhRgN+d
5Nh2TvkTol+QNPopiym+YPKNpMpRz/Lr3OLCsjpEhBiKGLg6mTsunnfh7d5ldq9Z
qq6oEI2vgHrKCueggiueoOC/BTS8rgUWFyAdXSjs8cNpRJHo+GHCOoyCg0IyVAd+
u8xpEawscIkYf1r4SQeICdOTe4CfJjw1eA6AspD0Qi3arqD5RtLItBECbmEs5wlk
ZIvElxdftZjWTpYyS3W8sp3ySM6Ri6NJFqWELVKu441VazvxZ7igU6lA0mcK6wml
4kkvjGcwCs+htCdRLKxVTAzs2I+5CKY8q4zwV82aqHm9msIgg2wXEMeqHxbmmnln
zg9xvhot0gCvJyZ0Bw/vEdCHNVBz0mcnlEeX9LUMkmNFQxUPvH+FVhimcgFg6LX2
cJX/qHHH8U+nOnES7jHwIQlBn/5AuCQbmSGNNnLU+D02atB+VZao6c8M1dkEYG8C
6NaOg9lyuyoftrnKIEJtzQkmxdYSswsWy7BGXtpsmc7mFv2GV3fOAvy8LclvyFTV
6YxMzR0ZqgVSmIJnjYSlrzHEB18wAyRUH7LpVR3eI4kMf2mdiolbho7dUNNyiE4l
RO4yHOQrAOxGMfzyWpbjop1EwUjUyg0taBkQnSIEGDjnn4O689PyJT3NFWXIfO4I
TcUw3qc7QGo2ZQBIjVQlfgOPtaPYy/qlv09Z2zqAH+1iJ8zXaWvjX1lMPOTQ1PNo
wHZqYTslWLsW1jZgZQ7lcatQdItmuaQu1hSDPZvrlYv1LRVrI7my0YV7X7fOs6y+
bzN7gsJ/gYWfPaQL4F49p8KFkKrjSxGSIjkiKMKQIG60CEWovq3LVRgtIy/CVaHr
LmLcGFV1NB9wxEaprPlEXYhJj6esqanxHtYPPbmnOTWqkzSAnE9pJULNItTcggSH
xVzmvw1lw33mkLK+/KBW5CbnHn1JCstbXB1RI5TMAIqJnmfYLhJHJr4mspAjA1UC
S8A8eEwwKbww/m/MMljdn2octZEAZKdu9jved8pzU+iXDbeKZ91dJoosvPRHt5Hy
Flo2Trg2cwAz0FUgX36JYHDUI8uMlULWGSGohDOlV64TkUluMD+gEyrqlR/gzLpP
YhKBEuZSeqMqJQXLEkqV7NnCX6bOb8xJ2piPtMrFVay5UWtao+AuSqcshwxjYC5C
IRUPwOVgz9po12ZeDd5LxFYFklHhdQIZbGF9rW8aXUeO1x5eXZmX7KdTz0mdTZcf
E5mR4WL2lV7h4Uq19D0oxgQm83IxIxxwFXqV2lTFnr2yGSxE3nAm1MCth8Y7yAsp
Bbjh93+xX0FG6Hj3Tiyedz5QeNMWVQprCobd+J38jrZtQfOnh0z5d0rO04y0GEyh
XS50VLut2jlqkbd3649bloKysfMniGbzoHfZUKbG0mSGX56oFmqi2D1lPclozh1h
7NzKxT0s4X/hkPz+AdBIo7bcmGNNyhXlV+JRcwpa+JmtNtHBUrclrbctaHX0Kb80
huqz9TiGCIG4qZwpzNRtWYRZ1cCsNJi3Gpg3k9nvmcMAKvddgV0dlMmwDopY6s5X
2aNGv8w4KqOuongMGYWZJ2uiYVN9vieLLLVTndRTh+7q0uYirat25R1yHFDpaNLz
6PIrPGJdh1SUAkLSsqaKTk0YqHYdteH4riJihUL5+ZoFHv474QU/9TRQyH+SkhGm
+XV8sEohZpFOhXHKFVnfYNWKa1PFZTuLEjqk/Zm9Q9oyxEovZgaO2mti1IH/P8aI
T6CO+jilCZM0hiUHOJ3OnuhTYg9mUBPczXmOXnB/AUJl4sPIG/vgR7x1HkCcnQmi
iTpcWEAQ7bFbDhx5EL3AwIrRxl+dEPdAoikSw2iSXy1IZNy4RylHtpdvX56xmwi/
bcfGZDHH/2kD5vugkNrP2cuCWPy/B9AYR4sncfebtsVxnANX0jBHWzdnjQYq1Za7
fvKi9lm2AKTXvNY+0BGDZeyS5+3tqo72+p46WupvJY9iR3t9T94hA07oKEILJv4h
qIoj6QuuHl8gZtXExVCc9dihKQ68d4yWDrQcFVqOmG202NByXGg5Zl2jpQstvUJL
D/jpLcdA57DQcljAsoC/XWixM1606vsfGt6vyUFIAAA=
}

Here's a simple example that demonstrates the basic syntax and use of r3D. Be sure to do the code above before running this example:

Transx:  Transy:  Transz: 300.0          ; Set some camera
Lookatx:  Lookaty:  Lookatz: 100.0       ; positions to
                                         ; start with.
do update: does [            ; This "update" function is where
    world: copy []           ; everything is defined.
    append world reduce [    ; Add your 3D objects inside this "append".
        reduce [cube-model (r3d-scale 100.0 150.0 125.0) red]
    ]                        ; A red 'cube' 100x150x125 pixels is added.
    camera: r3d-position-object   
        reduce [Transx Transy Transz]
        reduce [Lookatx Lookaty Lookatz]
        [0.0 0.0 1.0] 
    RenderTriangles: render world camera r3d-perspective 250.0 400x360
    probe RenderTriangles    ; This line demonstrates what's going on
]                            ; under the hood.  You can eliminate it.

view layout [
    scrn: box 400x360 black effect [draw RenderTriangles]  ; basic draw
    across return
    slider 60x16 [Transx: (value * 600 - 300.0) update show scrn]
    slider 60x16 [Transy: (value * 600 - 300.0) update show scrn]
    slider 60x16 [Transz: (value * 600) update show scrn]
    slider 60x16 [Lookatx: (value * 400 - 200.0) update show scrn]
    slider 60x16 [Lookaty: (value * 400 - 200.0) update show scrn]
    slider 60x16 [Lookatz: (value * 200 ) update show scrn]
]

R3D works by rendering 3D images to native REBOL 2D draw functions, which are contained in the "RenderTriangles" block above. R3D provides basic shape structures and a simple language interface to create and view those images in a REBOL application. It automatically adjusts lighting and other characteristics of images as they're viewed from different perspectives. To see how the rendering of images is converted into simple REBOL draw functions, watch the output of the "probe RenderTriangles" line in the REBOL interpreter as you adjust the sliders above. It displays the list of draw commands used to create each image in the moving 3D world.

In the example above, slider widgets are used to adjust values in the animation. Those values could just as easily be controlled by loops or other forms of data input. In the example below, the values are adjusted by keystrokes assigned to empty text widgets (use the "asdfghqwerty" keys to move the cube):

Transx:  Transy:  Transz: 2.0
Lookatx:  Lookaty:  Lookatz: 1.0 
do update: does [
    world: copy []
    append world reduce [ 
        reduce [cube-model (r3d-scale 100.0 150.0 125.0) red]
    ]
    Rendered: render world 
        r3d-position-object   
        reduce [Transx Transy Transz]
        reduce [Lookatx Lookaty Lookatz]
        [0.0 0.0 1.0]
        r3d-perspective 360.0 400x360
]
view layout [
    across
    text "" #"a" [Transx: (Transx + 10) update show scrn]
    text "" #"s" [Transx: (Transx - 10) update show scrn]
    text "" #"d" [Transy: (Transy + 10) update show scrn]
    text "" #"f" [Transy: (Transy - 10) update show scrn]
    text "" #"g" [Transz: (Transz + 10) update show scrn]
    text "" #"h" [Transz: (Transz - 10) update show scrn]
    text "" #"q" [Lookatx: (Lookatx + 10) update show scrn]
    text "" #"w" [Lookatx: (Lookatx - 10) update show scrn]
    text "" #"e" [Lookaty: (Lookaty + 10) update show scrn]
    text "" #"r" [Lookaty: (Lookaty - 10) update show scrn]
    text "" #"t" [Lookatz: (Lookatz + 10) update show scrn]
    text "" #"y" [Lookatz: (Lookatz - 10) update show scrn]
    at 20x20
    scrn: box 400x360 black effect [draw Rendered] 
]

The r3D module can work with models saved in native .R3d format, and the "OFF" format (established by the GeomView program at http://www.geom.uiuc.edu/projects/visualization/. See http://local.wasp.uwa.edu.au/~pbourke/dataformats/oogl/#OFF for a description of the OFF file format). A number of OFF example objects are available at http://www.mpi-sb.mpg.de/~kettner/proj/obj3d/.

To understand how to create/import and manipulate more complex 3D shapes, examine the way objects are designed inside the "update" function in each of Andrew's three examples. Here's a simplified variation of Andrew's objective.r example that loads .off models from the hard drive. Be sure to do the r3D module code above before running this example, and then try downloading and loading some of the example .off files at the web site above:

RenderTriangles: []
view layout [
    scrn: box 400x360 black effect [draw RenderTriangles]
    across return
    slider 60x16 [Transx: (value * 600 - 300.0) update show scrn]
    slider 60x16 [Transy: (value * 600 - 300.0) update show scrn]
    slider 60x16 [Transz: (value * 600) update show scrn]
    slider 60x16 [Lookatx: (value * 400 - 200.0) update show scrn]
    slider 60x16 [Lookaty: (value * 400 - 200.0) update show scrn]
    slider 60x16 [Lookatz: (value * 200 ) update show scrn]
    return btn "Load Model" [
        model: r3d-load-OFF load to-file request-file
        modelsize: 1.0
        if model/3 [modelsize: model/3]
        if modelsize < 1.0 [ modelsize: 1.0 ]
        defaultScale: 200.0 / modelsize
        objectScaleX: objectScaleY: objectScaleZ: defaultscale
        objectRotateX: objectRotateY: objectRotateZ: 0.0
        objectTranslateX: objectTranslateY: objectTranslateZ: 0.0    
        Transx:  Transy:  Transz: 300.0
        Lookatx:  Lookaty:  Lookatz: 200.0
        modelWorld: r3d-compose-m4 reduce [
            r3d-scale objectScaleX objectScaleY objectScaleZ
            r3d-translate 
                objectTranslateX objectTranslateY objectTranslateZ
            r3d-rotatex objectRotateX
            r3d-rotatey objectRotateY 
            r3d-rotatez objectRotateZ
        ]
        r3d-object: reduce [model modelWorld red]
        do update: does [
            world: copy []    
            append world reduce [r3d-object]
            camera: r3d-position-object   
                reduce [Transx Transy Transz]
                reduce [Lookatx Lookaty Lookatz]
                [0.0 0.0 1.0] 
            RenderTriangles: 
                render world camera r3d-perspective 250.0 400x360
        ]
        update show scrn
    ]
]

Like most REBOL solutions, r3D is a brilliantly simple, compact, and powerful design that doesn't require any external toolkits. It's pure REBOL, and it's really amazing!

9.6.1 Several 3D Scripts Using Raw REBOL Draw Dialect

The following short script is a compacted version of Gregory Pecheret's "ebuc-cube" (from http://www.rebol.net/demos/download.html). It demonstrates some simple 3d techniques using only native REBOL draw functions (no 3rd party library required). It's relatively easy to understand, manipulate, and use to create your own basic 3D graphics:

z: 10 h: z * 12 j: negate h c: as-pair z * 5 z * 5 l: z * 4 w: z * 20
img: to-image layout [box effect [draw [pen logo.gif circle c l]]]
q: make object! [x: 0 y: 0 z: 0] 
cube: [[h h j] [h h h] [h j j] [h j h] [j h j] [j h h] [j j j] [j j h]]
view center-face layout [
    f: box 400x400 rate 0 feel [engage: func [f a e] [
        b: copy []   q/x: q/x + 5   q/y: q/y + 8   q/z: q/z + 3
        repeat n 8 [
            p: reduce pick cube n  ; point
            zx: (p/1 * cosine q/z) - (p/2 * sine q/z) - p/1
            zy: (p/1 * sine q/z) + (p/2 * cosine q/z) - p/2
            yx: (p/1 + zx * cosine q/y) - (p/3 * sine q/y) - p/1 - zx
            yz: (p/1 + zx * sine q/y) + (p/3 * cosine q/y) - p/3
            xy: (p/2 + zy * cosine q/x) - (p/3 + yz * sine q/x) - p/2 - zy
            append b as-pair (p/1 + yx + zx + w) (p/2 + zy + xy + w)
        ]
        f/effect: [draw [
            fill-pen 255.0.0.100  polygon b/6 b/2 b/4 b/8
            image img b/6 b/5 b/1 b/2 
            fill-pen 255.159.215.100  polygon b/2 b/1 b/3 b/4 
            fill-pen 54.232.255.100 polygon b/1 b/5 b/7 b/3 
            fill-pen 0.0.255.100 polygon b/5 b/6 b/8 b/7 
            fill-pen 248.255.54.100 polygon b/8 b/4 b/3 b/7
        ]]
        show f
    ]]
]

Here's a version that reshapes and moves the 3D cube in, out and around the screen:

g: 12 i: 5 h: i * g j: negate h w: 0 v2: v1: 1  ; sizes/positions
img: to-image layout [box 200.200.200.50 center logo.gif]
q: make object! [x: 0 y: 0 z: 0]
cube: [[h h j] [h h h] [h j j] [h j h] [j h j] [j h h] [j j j] [j j h]]
view center-face layout/tight [
    f: box 500x450 rate 0 feel [engage: func [f a e] [
        b: copy []  q/x: q/x + 3 q/y: q/y + 3 ; q/z: q/z + 3  ; spinning
        repeat n 8 [
            if w > 500 [v1: 0]   ; w: xy pos        v1: xy direction
            if w < 0 [v1: 1]
            either v1 = 1 [w: w + 1] [w: w - 1]
            if j > (g * i * 2) [v2: 0]  ; j: z pos (size) v2: z direction
            if j < g [v2: 1]
            either v2 = 1 [h: h - 1] [h: h + 1]  j: negate h
            p: reduce pick cube n  ; point
            zx: p/1 * cosine q/z - (p/2 * sine q/z) - p/1
            zy: p/1 * sine q/z + (p/2 * cosine q/z) - p/2
            yx: (p/1 + zx * cosine q/y) - (p/3 * sine q/y) - p/1 - zx
            yz: (p/1 + zx * sine q/y) + (p/3 * cosine q/y) - p/3
            xy: (p/2 + zy * cosine q/x) - (p/3 + yz * sine q/x) - p/2 - zy
            append b as-pair (p/1 + yx + zx + w) (p/2 + zy + xy + w)
        ]
        f/effect: [draw [
            image img b/6 b/5 b/1 b/2 
            fill-pen 255.0.0.50      polygon b/6 b/2 b/4 b/8
            fill-pen 255.159.215.50  polygon b/2 b/1 b/3 b/4 
            fill-pen 54.232.255.50   polygon b/1 b/5 b/7 b/3 
            fill-pen 0.0.255.50      polygon b/5 b/6 b/8 b/7 
            fill-pen 248.255.54.50   polygon b/8 b/4 b/3 b/7
        ]]
        show f
    ]]
]

And here's a little 3D game, with sound, based on the above code:

REBOL [title: "Little 3D Game"]

beep-sound: load to-binary decompress 64#{
eJwBUQKu/VJJRkZJAgAAV0FWRWZtdCAQAAAAAQABABErAAARKwAAAQAIAGRhdGEl
AgAA0d3f1cGadFQ+T2Z9jn1lSjM8T2uNsM/j7Midc05PWGh4eXVrXE5DQEZumsTn
4M2yk3hiVU9fcX+GcFU8KkNmj7rR3+HYroJbPUpfdoqAbldBP0ZWbpW62OvRrohk
WlleaHB2dW9bRzo1WYWy3OHbyrKObVNCVGp/jXpgRC48Vnievtfm6MCUaUVLWW1/
fXNkUkdCRlN7ps3r3cSkgm1fWFhmdH2AaVA6LElwnMja4dzNpHtXPUxje45/aVA5
PUtif6TG3uvMpHtXU1lkcnd2cGVURT0+ZJC84+HUvaGCZ1NIWm6AinVaQCtAX4Wu
yt3k37aJYEBKXXOHf3FdSEJET2KJsdPr1reUcGJbW2FsdXl2YUs5MFF7qdPe3tO+
mHNUP1Bnfo59ZEkyPFFukbTR5OvGm3BMTVlpent1aVpMQ0FJcZ3I6uHMsJB2YlZR
YXJ/hW5UOypEaJK90+Dg1qyBWjxKYHeLgG1WPz9HWXKYvNnr0KyFYVhZX2pydnVu
Wkc7N1yHtN3h2sivjGxTRFZrgI15X0MtPVh7osHZ5ua+kmdES1tvgn5zY1BGQ0hW
fqjO69vBoX9rXllaaHV9fmhPOi1Lcp/K2+DayaF4Vj1NY3uNfmhONjxLZIKnyODr
yqJ4VFFYZHN3dm5iUUM9QGaTv+Th0rqdf2VTSltvgIl0WT4rQGCIssze5N60iF8/
Sl10h39vW0ZBRFFljLPU69W1kG1gWlxiYHkWb1ECAAA=
}
alert {
   Try to click the bouncing REBOLs as many times as possible in
   30 seconds.  The speed increases with each click!
}
do game: [
   speaker: open sound://
   g: 12 i: 5 h: i * g j: negate h  x: y: z: w: sc: 0  v2: v1: 1  o: now
   img1: to-image layout [backcolor brown box red center logo.gif]
   img2: to-image layout [backcolor aqua box yellow center logo.gif]
   img3: to-image layout [backcolor green box tan center logo.gif]
   cube: [[h h j][h h h][h j j][h j h][j h j][j h h][j j j][j j h]]
   view center-face layout/tight [
      f: box white 550x550 rate 15 feel [engage: func [f a e] [
         if a = 'time [
            b: copy []  x: x + 3  y: y + 3  ; z: z + 3
            repeat n 8 [
               if w > 500 [v1: 0]   if w < 50 [v1: 1]
               either v1 = 1 [w: w + 1] [w: w - 1]
               if j > (g * i * 1.4) [v2: 0]   if j < 1 [v2: 1]
               either v2 = 1 [h: h - 1] [h: h + 1]  j: negate h
               p: reduce pick cube n 
               zx: p/1 * cosine z - (p/2 * sine z) - p/1
               zy: p/1 * sine z + (p/2 * cosine z) - p/2
               yx: (p/1 + zx * cosine y) - (p/3 * sine y) - p/1 - zx
               yz: (p/1 + zx * sine y) + (p/3 * cosine y) - p/3
               xy: (p/2 + zy * cosine x) - (p/3 + yz * sine x) - p/2 - zy
               append b as-pair (p/1 + yx + zx + w) (p/2 + zy + xy + w)
            ]
            f/effect: [draw [
               image img1 b/6 b/2 b/4 b/8
               image img2 b/6 b/5 b/1 b/2
               image img3 b/1 b/5 b/7 b/3 
            ]]
            show f
            if now/time - o/time > :00:20 [
               close speaker
               either true = request [
                  join "Time's Up! Final Score: " sc "Again" "Quit"
               ] [do game] [quit]
            ]
         ]
         if a = 'down [
            xblock: copy [] yblock: copy []
            repeat n 8 [
                append xblock first pick b n
                append yblock second pick b n
            ]
            if all [
                e/offset/1 >= first minimum-of xblock
                e/offset/1 <= first maximum-of xblock
                e/offset/2 >= first minimum-of yblock
                e/offset/2 <= first maximum-of yblock
            ][
               insert speaker beep-sound wait speaker
               sc: sc + 1
               t1/text: join "Score: " sc 
               show t1
               if (modulo sc 3) = 0 [f/rate: f/rate + 1]
               show f
            ]
         ]
      ]]
      at 200x0 t1: text brown "Click the bouncing REBOLs!"
   ]
]

9.7 Multitasking

"Threads" are a feature of modern operating systems that allow multiple pieces of code to run concurrently, without waiting for the others to complete. Without threads, individual portions of code must be evaluated in consecutive order. Unfortunately, REBOL does not implement a formal mechanism for threading at the OS level, but does contain built-in support for asynchronous network port and services activity. See http://www.rebol.net/docs/async-ports.html, http://www.rebol.net/docs/async-examples.html, http://www.rebol.net/rebservices/services-start.html, and http://www.rebol.net/rebservices/quick-start.html for more information.

The following technique provides an alternate way to evaluate other types of code in a multitasking manner:

  1. Assign a rate of 0 to a GUI item in a 'view layout' block.
  2. Assign a "feel" detection to that item, and put the actions you want performed simultaneously inside the block that gets evaluated every time a 'time event occurs.
  3. Stop and start the evaluation of concurrently active portions of code by assigning a rate of "none" or 0, respectively, to the associated GUI item.

The following is an example of a webcam viewer which creates a video stream by repeatedly downloading and displaying images from a given webcam URL. To create a moving video effect, the process of downloading each image must run without stopping (i.e., in some sort of unending "forever" loop). But for a user to control the stop/start of the video flow (by clicking a button, for example), the interpreter must be able to check for user events that occur outside the forever loop. By running the repeated download using the technique outlined above, the program can continue to respond to other events while continuously looping the download code:

webcam-url: http://209.165.153.2/axis-cgi/jpg/image.cgi
view layout [
    btn "Start Video" [
        webcam/rate: 0 
        webcam/image: load webcam-url 
        show webcam
    ]
    btn "Stop Video" [webcam/rate: none show webcam]
    return 
    webcam: image load webcam-url 320x240 rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/image: load webcam-url show face
            ] 
        ] 
    ]
]

Here's an example in which two webcam video updates are treated as separate processes. Both can be stopped and started as needed:

webcam-url: http://209.165.153.2/axis-cgi/jpg/image.cgi
view layout [
    across 
    btn "Start Camera 1" [
        webcam/rate: 0 
        webcam/image: load webcam-url 
        show webcam
    ]
    btn "Stop Camera 1" [webcam/rate: none show webcam]
    btn "Start Camera 2" [
        webcam2/rate: 0 
        webcam2/image: load webcam-url 
        show webcam2
    ]
    btn "Stop Camera 2" [webcam2/rate: none show webcam2]
    return 
    webcam: image load webcam-url 320x240 rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/image: load webcam-url show face
            ] 
        ] 
    ]
    webcam2: image load webcam-url 320x240 rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/image: load webcam-url show face
            ] 
        ] 
    ] 
]

Unfortunately, this technique is not asynchronous. Each piece of event code is actually executed consecutively, in an alternating pattern, instead of simultaneously. Although the effect is similar (even indistinguishable) in many cases, the evaluation of code is not concurrent. For example, the following example adds a time display to the webcam viewer. You'll see that the clock is not updated every second. That's because the image download code and the clock code run alternately. The image download must be completed before the clock's 'time action can be evaluated. Try stopping the video to see the difference:

webcam-url: http://209.165.153.2/axis-cgi/jpg/image.cgi
view layout [
    btn "Start Video" [
        webcam/rate: 0 
        webcam/image: load webcam-url 
        show webcam
    ]
    btn "Stop Video" [webcam/rate: none show webcam]
    return 
    webcam: image load webcam-url 320x240 rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/image: load webcam-url show face
            ] 
        ] 
    ]
    clock: field to-string now/time/precise rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/text: to-string now/time/precise show face
            ] 
        ] 
    ]
]

One solution to achieving truly asynchronous activity is to simply write the code for one process into a separate file and run it in a separate REBOL interpreter process using the "launch" function:

write %async.r {
    REBOL []
    view layout [
        clock: field to-string now/time/precise rate 0 feel [
            engage: func [face action event][
                if action = 'time [
                    face/text: to-string now/time/precise show face
                ] 
            ] 
        ]
    ]
}

launch %async.r
; REBOL will NOT wait for the evaluation of code in async.r
; to complete before going on:

webcam-url: http://209.165.153.2/axis-cgi/jpg/image.cgi
view layout [
    btn "Start Video" [
        webcam/rate: 0 
        webcam/image: load webcam-url 
        show webcam
    ]
    btn "Stop Video" [webcam/rate: none show webcam]
    return 
    webcam: image load webcam-url 320x240 rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/image: load webcam-url show face
            ]
        ]
    ]
]

The technique above simply creates two totally separate REBOL programs from within a single code file. If such programs need to interact, share data, or respond to interactive activity states, they can communicate via tcp network port, or by reading/writing data via a shared storage device.

9.8 Using DLLs and Shared Code Files in REBOL

"Dll"s in Windows, "So" files in Linux, and "Dylib" on Macs are libraries of functions that can be shared among different programming languages. Shared code libraries are used to extend the capabilities of a language with new functions. They allow you to accomplish goals which aren't possible (or which are otherwise complicated) using the native functions built into the language. Most of the executable code, and all the potential capabilities, of most operating systems is contained in such files. Third party code libraries are also available to make easy work of complex tasks such as multimedia programming, 3d game programming, specialized hardware control, etc. To use Dlls and shared code files in REBOL, you'll need to download version 2.76 or later of the REBOL interpreter (rebview.exe). If you're using any of the beta versions from http://www.rebol.net/builds/, use either rebview.exe or rebcmdview.exe to run the examples in this section.

Using the format below, you can access and use the functions contained in most DLLs, as if they're native REBOL functions:

lib: load/library %TheNameOfYour.DLL

; "TheFunctionNameInsideTheDll" is loaded from the Dll and converted
; into a new REBOL function called "your-rebol-function-name":

your-rebol-function-name: make routine! [
    return-value: [data-type!]
    first-parameter [data-type!] 
    another-parameter [data-type!] 
    more-parameters [and-their-data-types!]
    ...
] lib "TheFunctionNameInsideTheDll"

; When the new REBOL function is used, it actually runs the function
; inside the Dll:

your-rebol-function-name parameter1 parameter2 ...

free lib

The first line opens access to the functions contained in the specified Dll. The following lines convert the function contained in the Dll to a format that can be used in REBOL. To make the conversion, a REBOL function is labeled and defined (i.e, "your-rebol-function-name" above), and a block containing the labels and types of parameters used and values returned from the function must be provided ("[return: [integer!]]" and "first-parameter [data-type!] another-parameter [data-type!] more-parameters [and-their-data-types!]" above). The name of the function, as labeled in the Dll, must also be provided immediately after the parameter block ("TheFunctionNameInsideTheDll" above). The second to last line above actually executes the new REBOL function, using any appropriate parameters you choose. When you're done using functions from the Dll, the last line is used to free up the Dll so that it's closed by the operating system.

Here are some examples:

REBOL []

; The "kernel32.dll" is a standard dll in all Windows installations:

lib: load/library %kernel32.dll

; The "beep" function is contained in the kernel32.dll library.
; We'll create a new REBOL function called "play-sound" that
; actually executes the "beep" function in kernel32.dll.  The
; "beep" function takes two integer parameters (pitch and 
; duration values), and returns an integer value:

play-sound: make routine! [
    return: [integer!] pitch [integer!] duration [integer!]
] lib "Beep"

; (Beep returns a value of zero if the function does not complete
; successfully.  Otherwise it returns a nonzero number).

; Now we can use the "play-sound" function AS IF IT'S A NATIVE
; REBOL FUNCTION:

for hertz 37 3987 50 [
    print rejoin ["The pitch is now " hertz " hertz."]
    play-sound hertz 50
]

free lib
halt

The following example demonstrates how to record sounds (with the microphone attached to your computer) using the Windows MCI API. When complete, the recorded sound is played back using a native REBOL sound port:

; Various mci functions are included in the winmm.dll library.
; We'll create a new REBOL function called "mciExecute" that
; allows us to run MCI functions in winmm.dll.  This function
; function takes one string parameter (a text string written 
; in MCI function syntax), and returns an integer value (true
; if the function is successful, false if it fails):

lib: load/library %winmm.dll
mciExecute: make routine! [ 
    command [string!]
    return: [logic!] 
] lib "mciExecute"

; Get a file name from the user, which will be used to save the
; recorded sound:

file: to-local-file to-file request-file/save/title/file "Save as:" {
    } %rebol-recording.wav

; Open an MCI buffer and begin the recording:

mciExecute "open new type waveaudio alias buffer1 buffer 6"
mciExecute "record buffer1"

ask "RECORDING STARTED (press [ENTER] when done)...^/"

; Stop recording and save the sound to the wave file selected above:

mciExecute "stop buffer1"
mciExecute join "save buffer1 " file

; Close the DLL:

free lib

print "Recording complete.  Here's how it sounds:^/"

; Play back the sound:

insert port: open sound:// load to-rebol-file file wait port close port
print "DONE.^/"

halt

The next example demonstrates how to play AVI video files, again using the Windows API "mciExecute" from winmm.dll. A demo video is downloaded from the Internet and played two times - once with default settings, and a second time at a given location on screen at twice the original recorded speed. The video codec in the demo video is MS-CRAM (Microsoft Video 1), and the audio format is PCM. For more information about mciExecute commands, Google "multimedia command strings" and see http://msdn.microsoft.com/en-us/library/dd743572(VS.85).aspx:

; These lines open the winmm.dll and define the "mciExecute" function
; in REBOL:

lib: load/library %winmm.dll
mciExecute: make routine! [c [string!] return: [logic!]] lib "mciExecute"

; These lines download a demo video:

if not exists? %test.avi [
    flash "Downloading test video..."
    write/binary %test.avi read/binary http://re-bol.com/test.avi
    unview
]
video: to-local-file %test.avi

; The lines run the mciExecute function with the commands needed to
; play the video:

mciExecute rejoin ["OPEN " video " TYPE AVIVIDEO ALIAS thevideo"]
mciExecute "PLAY thevideo WAIT"
mciExecute "CLOSE thevideo"

mciExecute rejoin ["OPEN " video " TYPE AVIVIDEO ALIAS thevideo"]
mciExecute "PUT thevideo WINDOW AT 200 200 0 0"  ; at 200x200
mciExecute "SET thevideo SPEED 2000"  ; play twice a fast
mciExecute "PLAY thevideo WAIT"
mciExecute "CLOSE thevideo"

; These lines clean up:

free lib
quit

The next example uses the "dictionary.dll" from http://www.reelmedia.org/pureproject/archive411/dll/Dictionary.zip to perform a spell check on text entered at the REBOL command line. There are two functions in the dll that are required to perform a spell check - "Dictionary_Load" and "Dictionary_Check":

REBOL []

check-me: ask "Enter a word to be spell-checked:  "

lib: load/library %Dictionary.dll

; Two new REBOL functions are created, which actually run the
; Dictionary_Load and Dictionary_Check functions in the DLL:

load-dic: make routine! [
    a [string!] 
    return: [none]
] lib "Dictionary_Load"

check-word: make routine! [
    a [string!]
    b [integer!]
    return: [integer!]
] lib "Dictionary_Check"

; This line runs the Dictionary_Load function from the DLL:

load-dic ""

; This line runs the Dictionary_Check function in the DLL, on
; whatever text was entered into the "check-me" variable above:

response: check-word check-me 0

; The Dictionary_Check function returns 0 if there are no errors:

either response = 0 [
    print "No spelling errors found." 
] [
    print "That word is not in the dictionary."
]

free lib
halt

The following example plays an mp3 sound file using the Dll at http://musiclessonz.com/mp3.dll. Of course, that Dll could be compressed and embedded in the code to eliminate the necessity of downloading the file:

REBOL []

write/binary %mp3.dll read/binary http://musiclessonz.com/mp3.dll
lib: load/library %mp3.dll

; the "playfile" function is loaded from the Dll, and converted
; to a new REBOL "play-mp3" function:

play-mp3: make routine! [a [string!] return: [none]] lib "playfile"

; Then an mp3 file name is requested from the user, which is played
; by the "playfile" function in the Dll:

file: to-local-file to-string request-file
play-mp3 file

print "Done playing, Press [Esc] to quit this program: "
free lib

The next example uses the "AU3_MouseMove" function from the Dll version of AutoIt, to move the mouse around the screen. AutoIt contains a wide variety of functions to programatically push buttons, type text, select menu items, choose items from lists, control the mouse, etc. in any existing program window, as if those actions had been performed by a user clicking and typing on screen. Learning the other functions in the AutoIt language can be very helpful in customizing and automating existing Windows applications:

REBOL []

if not exists? %AutoItDLL.dll [
    write/binary %AutoItDLL.dll
    read/binary http://musiclessonz.com/rebol_tutorial/AutoItDLL.dll
]

lib: load/library %AutoItDLL.dll
move-mouse: make routine! [
    return: [integer!] x [integer!] y [integer!] z [integer!]
] lib "AUTOIT_MouseMove"

print "Press the [Enter] key to see your mouse move around the screen."
print "It will move to the top corner, and then down diagonally to"
ask "position 200x200:  " 

for position 0 200 5 [
    move-mouse position position 10 
    ; "10" refers to the speed of the mouse movement
]

free lib
print "^/Done.^/"
halt

This example uses Dll functions from the native Windows API to eliminate the default 'REBOL - ' text at the top of the GUI window:

REBOL []

; First, load the necessary dll:

user32.dll: load/library %user32.dll

; Then define the Windows API functions you'll need:

get-focus: make routine! [return: [int]] user32.dll "GetFocus"

set-caption: make routine! [
    hwnd [int] 
    a [string!]  
    return: [int]
] user32.dll "SetWindowTextA"

; Next, create your GUI - be sure to use 'view/new', so that it doesn't
; appear immediately (start the GUI later with 'do-events', after you've
; changed the title bar below):

view/new center-face layout [
    backcolor white
    text bold "Notice that there's no 'Rebol - ' in the title bar above."
    text "New title text:" 
    across
    f: field "Tada!"
    btn "Change Title" [
        ; These functions change the text in the title bar:
        hwnd: get-focus
        set-caption hwnd f/text
    ]
    btn "Exit" [
        ; Be sure to close the dll when you're done:
        free user32.dll
        quit
    ]
]

; Once you've created your GUI, run the Dll functions to replace the
; default text in the title bar:

hwnd: get-focus
set-caption hwnd "My Title"

; Finally, start your GUI:

do-events

Here's a slightly more versatile version of the above script. You can add it to the top of any existing GUI script, and it will remove the default "REBOL -" text from all GUI title bars, including alerts and requestors:

title-text: "My Program"
if system/version/4 = 3 [
    user32.dll: load/library %user32.dll
    get-tb-focus: make routine! [return: [int]] user32.dll "GetFocus"
    set-caption: make routine! [
        hwnd [int] 
        a [string!]  
        return: [int]
    ] user32.dll "SetWindowTextA"
    show-old: :show
    show: func [face] [
        show-old [face]
        hwnd: get-tb-focus
        set-caption hwnd title-text
    ]
]

The following application demonstrates how to use the Windows API to view video from a local web cam, to save snapshots in BMP format, and to change the REBOL GUI window title:

REBOL []

; First, open the Dlls that contain the Windows API functions we want
; to use (to view webcam video, and to change window titles):

avicap32.dll: load/library %avicap32.dll
user32.dll: load/library %user32.dll

; Create REBOL function prototypes required to change window titles:
; (These functions are found in user32.dll, built in to Windows.)

get-focus: make routine! [return: [int]] user32.dll "GetFocus"
set-caption: make routine! [
    hwnd [int] a [string!]  return: [int]
] user32.dll "SetWindowTextA"

; Create REBOL function prototypes required to view the webcam:
; (also built in to Windows)

find-window-by-class: make routine! [
    ClassName [string!] WindowName [integer!] return: [integer!]
] user32.dll "FindWindowA"
sendmessage: make routine! [
    hWnd [integer!] val1 [integer!] val2 [integer!] val3 [integer!]
    return: [integer!]
] user32.dll "SendMessageA"
sendmessage-file: make routine! [
    hWnd [integer!] val1 [integer!] val2 [integer!] val3 [string!]
    return: [integer!]
] user32.dll  "SendMessageA"
cap: make routine! [
    cap [string!] child-val1 [integer!] val2 [integer!] val3 [integer!]
    width [integer!] height [integer!] handle [integer!] 
    val4 [integer!] return: [integer!]
] avicap32.dll "capCreateCaptureWindowA"

; Create the REBOL GUI window:

view/new center-face layout/tight [
    image 320x240
    across
    btn "Take Snapshot" [
        ; Run the dll functions that take a snapshot:
        sendmessage cap-result 1085 0 0
        sendmessage-file cap-result 1049 0 "scrshot.bmp"
    ]
    btn "Exit" [
        ; Run the dll functions that stop the video:
        sendmessage cap-result 1205 0 0
        sendmessage cap-result 1035 0 0
        free user32.dll
        quit
    ]
]

; Run the Dll functions that reset our REBOL GUI window title:
; (eliminates "REBOL - " in the title bar)

hwnd-set-title: get-focus
set-caption hwnd-set-title "Web Camera"

; Run the Dll functions that show the video:

hwnd: find-window-by-class "REBOLWind" 0
cap-result: cap "cap" 1342177280 0 0 320 240 hwnd 0
sendmessage cap-result 1034 0 0
sendmessage cap-result 1077 1 0
sendmessage cap-result 1075 1 0
sendmessage cap-result 1074 1 0
sendmessage cap-result 1076 1 0

; start the GUI:

do-events

For more information about DLLs and the Windows API, see:

http://rebol.com/docs/library.html
http://en.wikipedia.org/wiki/Dynamic_Link_Library
http://www.math.grin.edu/~shirema1/docs/DLLsinREBOL.html
http://www.borland.com/devsupport/borlandcpp/patches/BC52HLP1.ZIP
http://www.allapi.net/downloads/apiguide/agsetup.exe
http://www.activevb.de/rubriken/apiviewer/index-apiviewereng.html
http://msdn.microsoft.com/library/

Remember, whenever you use any Dll or code created by another programmer, be absolutely sure to check, and follow, the licensing terms by which it's distributed.

9.9 Web Programming and the CGI Interface

In "CGI" web applications, HTML forms on a web site act as the user interface (GUI) for scripts that run on a web server. Users typically type text into fields, select choices from drop down lists, click check boxes, and otherwise enter data into form widgets on a web page, and then click a "submit" button when done. The submitted data is transferred to, and processed by, a script that you've stored at a specified URL (Internet address) on your web server. Data output from the script is then sent back to the user's browser and displayed on screen as a dynamically created web page. CGI programs of that sort, running on web sites, are among the most common types of computer application in contemporary use. PHP, Python, Java, PERL, and ASP are popular languages used to accomplish similar Internet programming tasks, but if you know REBOL, you don't need to learn them. REBOL's CGI interface makes Internet programming very easy.

In order to create REBOL CGI programs, you need an available web server. A web server is a computer attached to the Internet, which constantly runs a program that stores and sends out web page text and data, when requested from an Internet browser running on another computer. The most popular web serving application is Apache. Most small web sites are typically run on shared web server hosting accounts, rented from a data center for a few dollars per month (see http://www.lunarpages.com - they're REBOL friendly). While setting up a web server account, you can register an available domain name (i.e, www.yourwebsitename.com). When web site visitors type your ".com" domain address into their browser, they see files that you've created and saved into a publicly accessible file folder on your web server computer.

In order for REBOL CGI scripts to run, the REBOL interpreter must be installed on your web server. To do that, download from rebol.com the correct version of the REBOL interpreter for the operating system on which your web server runs (most often some type of Linux). Upload it to your user path on your web server, and set the permissions to allow it to be executed (typically "755"). Ask your web site host if you don't understand what that means. http://rebol.com/docs/cgi1.html#section-2.2 has some basic information about how to install REBOL on your server. If you don't have an online web server account, you can download a full featured free Apache web server package that will run on your local Windows PC, from http://www.uniformserver.com.

9.9.1 HTML

In order to create any sort of CGI application, you need to understand a bit about HTML. HTML is the layout language used to format text and GUI elements on all web pages. HTML is not a programming language - it doesn't have facilities to process or manipulate data. It's simply a markup format that allows you to shape the visual appearance of text, images, and other items on pages viewed in a browser.

In HTML, items on a web page are enclosed between starting and ending "tags":

<STARTING TAG>Some item to be included on a web page</ENDING TAG>

There are tags to effect the layout in every possible way. To bold some text, for example, surround it in opening and closing "strong" tags:

<STRONG>some bolded text</STRONG>

The code above appears on a web page as: some bolded text.

To italicize text, surround it in < i > and < / i > tags:

<i>some italicized text</i>

That appears on a web page as: some italicized text.

To create a table with three rows of data, do the following:

<TABLE border=1>
    <TR><TD>First Row</TD></TR>
    <TR><TD>Second Row</TD></TR>
    <TR><TD>Third Row</TD></TR>
</TABLE>

Notice that every

<opening tag>

in HTML code is followed by a corresponding

</closing tag>

Some tags surround all of the page, some tags surround portions of the page, and they're often nested inside one another to create more complex designs.

A minimal format to create a web page is shown below. Notice that the title is nested between "head" tags, and the entire document is nested within "HTML" tags. The page content seen by the user is surrounded by "body" tags:

<HTML>
    <HEAD>
        <TITLE>Page title</TITLE>
    </HEAD>
    <BODY>
        A bunch of text and <i>HTML formatting</i> goes here...
    </BODY>
</HTML>

If you save the above code to a text file called "yourpage.html", upload it to your web server, and surf to http://yourwebserver.com/yourpage.html , you'll see in your browser a page entitled "Page title", with the text "A bunch of text and HTML formatting goes here...". All web pages work that way - this tutorial is in fact just an HTML document stored on the author's web server account. Click View -> Source in your browser, and you'll see the HTML tags that were used to format this document.

9.9.2 HTML Forms and Server Scripts - the Basic CGI Model

The following HTML example contains a "form" tag inside the standard HTML head and body layout. Inside the form tags are a text input field tag, and a submit button tag:

<HTML>
    <HEAD><TITLE>Data Entry Form</TITLE></HEAD>
    <BODY>
        <FORM ACTION="http://yourwebserver.com/your_rebol_script.cgi">
            <INPUT TYPE="TEXT" NAME="username" SIZE="25">
            <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
        </FORM>
    </BODY>
</HTML>

Forms can contain tags for a variety of input types: multi-line text areas, drop down selection boxes, check boxes, etc. See http://www.w3schools.com/html/html_forms.asp for more information about form tags.

You can use the data entered into any form by pointing the action address to the URL at which a specific REBOL script is located. For example, 'FORM ACTION="http://yourwebserver.com/your_rebol_script.cgi"' in the above form could point to the URL of the following CGI script, which is saved as a text file on your web server. When a web site visitor clicks the submit button in the above form, the data is sent to the following program, which in turn does some processing, and prints output directly to the user's web browser. NOTE: Remember that in REBOL curly brackets are the same as quotes. Curly brackets are used in all the following examples, because they allow for multiline content and they help improve readability by clearly showing where strings begin and end:

#!/home/your_user_path/rebol/rebol -cs
REBOL []
print {content-type: text/html^/}  
; the line above is the same as:  print "content-type: text/html^/"
submitted: decode-cgi system/options/cgi/query-string

print {<HTML><HEAD><TITLE>Page title</TITLE></HEAD><BODY>}
print rejoin [{Hello } second submitted {!}]
print {</BODY></HTML>}

In order for the above code to actually run on your web server, a working REBOL interpreter must be installed in the path designated by "/home/your_user_path/rebol/rebol -cs".

The first 4 lines of the above script are basically stock code. Include them at the top of every REBOL CGI script. Notice the "decode-cgi" line - it's the key to retrieving data submitted by HTML forms. In the code above, the decoded data is assigned the variable name "submitted". The submitted form data can be manipulated however desired, and output is then returned to the user via the "print" function. That's important to understand: all data "print"ed by a REBOL CGI program appears directly in the user's web browser (i.e., to the web visitor who entered data into the HTML form). The printed data is typically laid out with HTML formatting, so that it appears as a nicely formed web page in the user's browser.

Any normal REBOL code can be included in a CGI script - you can perform any type of data storage, retrieval, organization, and manipulation that can occur in any other REBOL program. The CGI interface just allows your REBOL code to run online on your web server, and for data to be input/output via web pages which are also stored on the web server, accessible by any visitor's browser.

9.9.3 A Standard CGI Template to Memorize

Most short CGI programs typically print an initial HTML form to obtain data from the user. In the initial printed form, the action address typically points back to the same URL address as the script itself. The script examines the submitted data, and if it's empty (i.e., no data has been submitted), the program prints the initial HTML form. Otherwise, it manipulates the submitted data in way(s) you choose and then prints some output to the user's web browser in the form of a new HTML page. Here's a basic example of that process, using the code above:

#!/home/your_user_path/rebol/rebol -cs
REBOL []
print {content-type: text/html^/}
submitted: decode-cgi system/options/cgi/query-string

; The 4 lines above are the standard REBOL CGI headers.
; The line below prints the standard HTML, head and body
; tags to the visitor's browser:

print {<HTML><HEAD><TITLE>Page title</TITLE></HEAD><BODY>}

; Next, determine if any data has been submitted.
; Print the initial form if empty.  Otherwise, process 
; and print out some HTML using the submitted data.  
; Finally, print the standard closing "body" and "html"
; tags, which were opened above:

either empty? submitted [
    print {
        <FORM ACTION="http://yourwebserver.com/this_rebol_script.cgi">
        <INPUT TYPE="TEXT" NAME="username" SIZE="25">
        <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
        </FORM>
        </BODY></HTML>
    }
] [ 
    print rejoin [{Hello } second submitted {!}]
    print {</BODY></HTML>}
]

Using the above standard outline, you can include any required HTML form(s), along with all executable code and data required to make a complete CGI program, all in one script file. Memorize it.

9.9.4 Examples

Here's a REBOL CGI form-mail program that prints an initial form, then sends an email to a given address containing the user-submitted data:

#!/home/youruserpath/rebol/rebol -cs
REBOL []
print {content-type: text/html^/}
submitted: decode-cgi system/options/cgi/query-string

; the following account info is required to send email:

set-net [from_address@website.com  smtp.website.com]

; print a more complicated HTML header:

print read %template_header.html

; if some form data has been submitted to the script:

if not empty? submitted [
    sent-message: rejoin [
        newline {INFO SUBMITTED BY WEB FORM} newline newline
        {Time Stamp: } (now + 3:00) newline
        {Name: } submitted/2 newline 
        {Email: } submitted/4 newline
        {Message: } submitted/6 newline 
    ]

    send/subject to_address@website.com sent-message "FORM SUBMISSION"

    html: copy {}
    foreach [var value] submitted [
        repend html [<TR><TD> mold var </TD><TD> mold value </TD></TR>]
    ]
    print {<font size=5>Thank You!</font> <br><br>
        The following information has been sent: <BR><BR>}
    print rejoin [{Time Stamp: } now + 3:00]
    print {<BR><BR><table>}
    print html
    print {</table>}
    ;  print a more complicated HTML footer:
    print read %template_footer.html
    quit
]

; if no form data has been submitted, print the initial form:

print {
    <CENTER><TABLE><TR><TD>
    <BR><strong>Please enter your info below:</strong><BR><BR>
    <FORM ACTION="http://yourwebserver.com/this_rebol_script.cgi">
    Name: <BR> <INPUT TYPE="TEXT" NAME="name"><BR><BR>
    Email: <BR> <INPUT TYPE="TEXT" NAME="email"><BR><BR>
    Message: <BR>
    <TEXTAREA cols=75 name=message rows=5></TEXTAREA> <BR><BR>
    <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
    </FORM>
    </TD></TR></TABLE></CENTER>
}
print read %template_footer.html

The template_header.html file used in the above example can include the standard required HTML outline, along with any formatting tags and static content that you'd like, in order to present a nicely designed page. A basic layout may include something similar to the following:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD><TITLE>Page Title</TITLE>    
<META http-equiv=Content-Type content="text/html; 
    charset=windows-1252">
</HEAD>    
<BODY bgColor=#000000>
<TABLE align=center background="" border=0 
    cellPadding=20 cellSpacing=2 height="100%" width="85%">
<TR>
<TD background="" bgColor=white vAlign=top>

The footer closes any tables or tags opened in the header, and may include any static content that appears after the CGI script (copyright info, logos, etc.):

</TD>
</TR>
</TABLE>
<TABLE align=center background="" border=0 
    cellPadding=20 cellSpacing=2 width="95%">
<TR>
<TD background="" cellPadding=2 bgColor=#000000 height=5>
<P align=center><FONT color=white size=1>Copyright © 2009
    Yoursite.com.  All rights reserved.</FONT>
</P>
</TD>
</TR>
</TABLE>
</BODY>
</HTML>

The following example demonstrates how to automatically build lists of days, months, times, and data read from a file, using dynamic loops (foreach, for, etc.). The items are selectable from drop down lists in the printed HTML form:

#!/home/youruserpath/rebol/rebol -cs
REBOL []
print {content-type: text/html^/}
submitted: decode-cgi system/options/cgi/query-string

print {<HTML><HEAD><TITLE>Dropdown Lists</TITLE></HEAD><BODY>}

if not empty? submitted [
    print rejoin [{NAME SELECTED: } submitted/2 {<BR><BR>}]
    selected: rejoin [
        {TIME/DATE SELECTED: }
        submitted/4 { } submitted/6 {, } submitted/8
    ]
    print selected
    quit
]

; If no data has been submitted, print the initial form:

print {<FORM ACTION="http://yourwebserver.com/your_rebol_script.cgi">
   SELECT A NAME:  <BR> <BR>}
names: read/lines %users.txt
print {<select NAME="names">}
foreach name names [prin rejoin [{<option>} name]]
print {</option> </select> <br> <br>}

print { SELECT A DATE AND TIME: }
print rejoin [{(today's date is } now/date {)} <BR><BR>]

print {<select NAME="month">}
foreach m system/locale/months [prin rejoin [{<option>} m]]
print {</option> </select>}

print {<select NAME="date">} 
for daysinmonth 1 31 1 [prin rejoin [{<option>} daysinmonth]]
print {</option> </select>}

print {<select NAME="time">
for time 10:00am 12:30pm :30 [prin rejoin [{<option>} time]]
for time 1:00 10:00 :30 [prin rejoin [{<option>} time]]
print {</option> </select> <br> <br>}

print {<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit"></FORM>}

The "users.txt" file used in the above example may look something like this:

nick
john
jim
bob

Here's a simple CGI program that displays all photos in the current folder on a web site, using a foreach loop:

#! /home/path/public_html/rebol/rebol -cs
REBOL [title: "Photo Viewer"]
print {content-type: text/html^/}
print {<HTML><HEAD><TITLE>Photos</TITLE></HEAD><BODY>}
print read %template_header.html

folder: read %.
count: 0
foreach file folder [
    foreach ext [".jpg" ".gif" ".png" ".bmp"] [
        if find file ext [
            print [<BR> <CENTER>]
            print rejoin [{<img src="} file {">}]
            print [</CENTER>]
            count: count + 1
        ]
    ]
]
print {<BR>}
print rejoin [{Total Images: } count]
print read %template_footer.html

Notice that there's no "submitted: decode-cgi system/options/cgi/query-string" code in the above example. That's because the above script doesn't make use of any data submitted from a form.

Here's a simple but powerful script that allows you to type REBOL code into an HTML text area, and have that code execute directly on your web server. The results of the code are then displayed in your browser. This essentially functions as a remote console for the REBOL interpreter on your server. You can use it to run REBOL code, or to call shell programs directly on your web site - very powerful! DO NOT run this on your web server if you're concerned at all about security!:

#! /home/path/public_html/rebol/rebol276 -cs
REBOL [Title: "CGI Remote Console"]
print {content-type: text/html^/}
print {<HTML><HEAD><TITLE>Console</TITLE></HEAD><BODY>}
submitted: decode-cgi system/options/cgi/query-string

; If no data has been submitted, print form to request user/pass:

if ((submitted/2 = none) or (submitted/4 = none)) [
    print {
        <STRONG>W A R N I N G  -  Private Server, Login Required:</STRONG>
        <BR><BR>
        <FORM ACTION="./console.cgi">
        Username: <INPUT TYPE=text SIZE="50" NAME="name"><BR><BR>
        Password: <INPUT TYPE=text SIZE="50" NAME="pass"><BR><BR>
        <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
        </FORM>
    }
    quit
]

; If code has been submitted, print the output:

entry-form: [
    print {
        <CENTER><FORM METHOD="get" ACTION="./console.cgi">
        <INPUT TYPE=hidden NAME=submit_confirm VALUE="command-submitted">
        <TEXTAREA COLS="100" ROWS="10" NAME="contents"></TEXTAREA><BR><BR>
        <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
        </FORM></CENTER></BODY></HTML>
    }
]

if submitted/2 = "command-submitted" [
    write %commands.txt join "REBOL[]^/" submitted/4
    ; The "call" function requires REBOL version 2.76:
    call/output/error 
        {/home/path/public_html/rebol/rebol276 -qs commands.txt}
        %conso.txt %conse.txt
    do entry-form
    print rejoin [
        {<CENTER>Output: <BR><BR>}
        {<TABLE WIDTH=80% BORDER="1" CELLPADDING="10"><TR><TD><PRE>}
        read %conso.txt
        {</PRE></TD></TR></TABLE><BR><BR>}
        {Errors: <BR><BR>}
        read %conse.txt
        {</CENTER>}
    ]
    quit
]

; Otherwise, check submitted user/pass, then print form for code entry:

username: submitted/2 password: submitted/4 
either (username = "user") and (password = "pass") [
    ; if user/pass is ok, go on
][
    print "Incorrect Username/Password." quit
]

do entry-form

Upload the script to your server, rename it "console.cgi", set it to executable, and change the path to your REBOL interpreter (2 places in the script). Then try running the following example code:

print 352 + 836
? system/locale/months 
call "ls -al"

Here's an example that allows users to check attendance at various weekly events, and add/remove their names from each of the events. It stores all the user information in a flat file (simple text file) named "jams.db":

#! /home/path/public_html/rebol/rebol -cs
REBOL [title: "event.cgi"]
print {content-type: text/html^/}
print {<HTML><HEAD><TITLE>Event Sign-Up</TITLE></HEAD><BODY>}

jams: load %jam.db

a-line: [] loop 65 [append a-line "-"]
a-line: trim to-string a-line

print {
    <hr> <font size=5>" Sign up for an event:"</font> <hr><BR>
    <FORM ACTION="http://yourwebsite.com/cgi-bin/event.cgi">
    Student Name:
    <input type=text size="50" name="student"><BR><BR>
    ADD yourself to this event:             "
    <select NAME="add"><option>""<option>"all"
}
foreach jam jams [prin rejoin [{<option>} jam/1]]
print {
    </option> </select> <BR> <BR>
    REMOVE yourself from this event:
    <select NAME="remove"><option>""<option>"all"
}
foreach jam jams [prin rejoin [{<option>} jam/1]]
print {
    </option> </select> <BR> <BR>
    <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
    </FORM>
}

print-all: does [
    print [<br><hr><font size=5>]
    print " Currently scheduled events, and current attendance:"
    print [</font><br>]
    foreach jam jams [
        print rejoin [a-line {<BR>} jam/1 {BR} a-line {<BR>}]
        for person 2 (length? jam) 1 [
            print jam/:person
            print {<BR>}
        ]
        print {<BR>}
    ]
    print {</BODY></HTML>}
]

submitted: decode-cgi system/options/cgi/query-string

if submitted/2 <> none [
    if ((submitted/4 = "") and (submitted/6 = "")) [
        print {
            <strong> Please try again. You must choose an event.</strong>
        }
        print-all
        quit
    ]
    if ((submitted/4 <> "") and (submitted/6 <> "")) [
        print {
            <strong> Please try again. Choose add OR remove.</strong>
        }
        print-all
        quit
    ]
    if submitted/4 = "all" [
        foreach jam jams [append jam submitted/2]
        save %jam.db jams
        print {
            <strong> Your name has been added to every event:</strong>
        }
        print-all
        quit
    ]
    if submitted/6 = "all" [
        foreach jam jams [
            if find jam submitted/2 [
                remove-each name jam [name = submitted/2]
                save %jam.db jams
            ]
        ]
        print {
            <strong> Your name has been removed from all events:</strong>
        }
        print-all
        quit
    ]
    foreach jam jams [
        if (find jam submitted/4) [
            append jam submitted/2
            save %jam.db jams
            print {
                <strong> Your name has been added to the selected event:
                </strong>
            }
            print-all
            quit    
        ]
    ]
    found: false
    foreach jam jams [
        if (find jam submitted/6) [
            if (find jam submitted/2) [
                remove-each name jam [name = submitted/2]
                save %jam.db jams
                print {
                    <strong>
                    Your name has been removed from the selected event:
                    </strong>
                }
                print-all
                quit
                found: true
            ]
        ]
    ]
    if found <> true [
        print {
            <strong> That name is not found in the specified event!"
            </strong>
        }
        print-all
        quit
    ]
]

print-all

Here is a sample of the "jam.db" data file used in the above example:

["Sunday September 16, 4:00 pm - Jam CLASS"
    "Nick Antonaccio" "Ryan Gaughan" "Mark Carson"]
["Sunday September 23, 4:00 pm - Jam CLASS"
    "Nick Antonaccio" "Ryan Gaughan" "Mark Carson"]
["Sunday September 30, 4:00 pm - Jam CLASS"
    "Nick Antonaccio" "Ryan Gaughan" "Mark Carson"]

Here's a simple web site bulletin board program:

#! /home/path/public_html/rebol/rebol -cs
REBOL [title: "Jam"]
print {content-type: text/html^/}
print read %template_header.html
; print {<HTML><HEAD><TITLE>Bulletin Board</TITLE></HEAD><BODY>}

bbs: load %bb.db

print {
    <center><table border=1 cellpadding=10 width=600><tr><td>
    <center><strong><font size=4>
    Please REFRESH this page to see new messages.
    </font></strong></center>
}

print-all: does [
    print {<br><hr><font size=5> Posted Messages: </font> <br><hr>}
    foreach bb (reverse bbs) [
        print rejoin [
            {<BR>Date/Time: } bb/2 {         }
            {"Name: } bb/1 {<BR><BR>} bb/3 {<BR><BR><HR>}
        ]
    ]
]

submitted: decode-cgi system/options/cgi/query-string

if submitted/2 <> none [
    entry: copy []
    append entry submitted/2
    append entry to-string (now + 3:00)
    append entry submitted/4
    append/only bbs entry
    save %bb.db bbs
    print {<BR><strong>Your message has been added: </strong><BR>}
]

print-all

print {
    <font size=5> Post A New Public Message:</font><hr>
    <FORM ACTION="http://website.com/bb/bb.cgi">
    <br> Your Name:  <br>
    <input type=text size="50" name="student"><BR><BR>
    Your Message: <br>
    <textarea name=message rows=5 cols=50></textarea><BR><BR>
    <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Post Message">
    </FORM>
    </td></tr></table></center>
}
print read %template_footer.html

Here's an example data file for the program above:

[
    [
        "Nick Antonaccio"
        "8-Nov-2006/4:55:59-8:00"
        {
            WELCOME TO OUR PUBLIC BULLETIN BOARD.
             Please keep the posts clean cut and on topic.
            Thanks and have fun!
        }
    ]
]

The default format for REBOL CGI data is "GET". Data submitted by the GET method in an HTML form is displayed in the URL bar of the user's browser. If you don't want users to see that data displayed, or if the amount of submitted data is larger then can be contained in the URL bar of a browser, the "POST" method should be used. To work with the POST method, the action in your HTML form should be:

<FORM METHOD="post" ACTION="./your_script.cgi">

You must also use the "read-cgi" function below to decode the submitted POST data in your REBOL script. This example creates a password protected online text editor, with an automatic backup feature:

#!/home/path/public_html/rebol/rebol -cs
REBOL []
print {content-type: text/html^/}
print {<HTML><HEAD><TITLE>Edit Text Document</TITLE></HEAD><BODY>}

; submitted: decode-cgi system/options/cgi/query-string

; We can't use the normal line above to decode, because
; we're using the POST method to submit data (because data
; from the textarea may get too big for the GET method). 
; Use the following standard function to process data from 
; a POST method instead:

read-cgi: func [/local data buffer][
    switch system/options/cgi/request-method [
        "POST" [
            data: make string! 1020
            buffer: make string! 16380
            while [positive? read-io system/ports/input buffer 16380][
                append data buffer
                clear buffer
            ]
        ]
        "GET" [data: system/options/cgi/query-string]
    ]
    data
]

submitted: decode-cgi read-cgi

; if document.txt has been edited and submitted:

if submitted/2 = "save" [ 
    ; save newly edited document:
    write to-file rejoin ["./" submitted/6 "/document.txt"] submitted/4
    print ["Document Saved."]
    print rejoin [
        {<META HTTP-EQUIV="REFRESH" CONTENT="0; 
            URL=http://website.com/folder/}
        submitted/6 {">}
    ]
    quit
]

; if user is just opening page (i.e., no data has been submitted 
; yet), request user/pass:

if ((submitted/2 = none) or (submitted/4 = none)) [
    print {
        <strong>W A R N I N G  -  Private Server, Login Required:
        </strong><BR><BR>
        <FORM ACTION="./edit.cgi">
        Username: <input type=text size="50" name="name"><BR><BR>
        Password: <input type=text size="50" name="pass"><BR><BR>
        <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
        </FORM>
    }
    quit
]

; check user/pass against those in userlist.txt, 
; end program if incorrect:

userlist: load %userlist.txt
folder: submitted/2 
password: submitted/4
response: false
foreach user userlist [
    if ((first user) = folder) and (password = (second user)) [
        response: true
    ]
]
if response = false [print {Incorrect Username/Password.} quit]

; if user/pass is ok, go on...

; backup (before changes are made):

cur-time: to-string replace/all to-string now/time {:} {-}
document_text: read to-file rejoin [{./} folder {/document.txt}]
write to-file rejoin [
    {./} folder {/} now/date {_} cur-time {.txt}] document_text

; note the POST method in the HTML form:

prin {
    <strong>Be sure to SUBMIT when done:</strong><BR><BR>
    <FORM method="post" ACTION="./edit.cgi">
    <INPUT TYPE=hidden NAME=submit_confirm VALUE="save">
    <textarea cols="100" rows="15" name="contents">
}
;
; The following line is what we want to do, but it won't work for
; HTML documents which contain <textarea>s
;
; print document_text
;
; The following line fixes the problem:
;
prin replace/all document_text {</textarea>} {&lt\/textarea&gt;}
print {</textarea><BR><BR>}
print rejoin [{<INPUT TYPE=hidden NAME=folder VALUE="} folder {">}]
print {<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">}
print {</FORM>}
print {</BODY></HTML>}

The following is a generic form handler that can be used to save GET or POST data to a text file. It's a useful replacement for generic form mailers, and makes the data much more accessible later by other scripts:

#!/home/path/public_html/rebol/rebol -cs
REBOL []
print {content-type: text/html^/}

read-cgi: func [/local data buffer][
    switch system/options/cgi/request-method [
        "POST" [
            data: make string! 1020
            buffer: make string! 16380
            while [positive? read-io system/ports/input buffer 16380][
                append data buffer
                clear buffer
            ]
        ]
        "GET" [data: system/options/cgi/query-string]
    ]
    data
]
submitted: decode-cgi read-cgi

print {
    <HTML><HEAD><TITLE>Your Form Has Been Submitted</TITLE></HEAD>
    <BODY><CENTER><TABLE border=0 cellPadding=10 width="80%"><TR><TD>
}

entry: rejoin [{[^/ "Time Stamp:" } {"} form (now + 3:00) {"^/}]
foreach [title value] submitted [
    entry: rejoin [entry { } {"} mold title {" } mold value {^/}]
]
append entry {]^/}
write/append %submitted_forms.txt entry

html: copy ""
foreach [title value] submitted [
    repend html [
        <TR><TD width=20%> mold title </TD><TD> mold value </TD></TR>
    ]
]

print rejoin [
    {
         <FONT size=5>Thank You! The following information has been
         submitted: </FONT><BR><BR>Time Stamp:
    } 
    now + 3:00  {<BR><BR><TABLE border=1 cellPadding=10 width="100%">}
    html  {</TABLE><BR>}
    {  
        To correct any errors or to submit forms for additional people,
        please click the [BACK] button in your browser, make any 
        changes, and resubmit the form.  You'll hear from us shortly.
        Thank you!<BR><BR><CENTER><FONT size=1>
        Copyright © 2009 This Web Site.  All rights reserved.</FONT>
        </CENTER></TD></TR></CENTER></BODY></HTML>
    }
]

quit

Here's a basic form example that could be processed by the above script. You can add as many text, textareas, and other form items as desired, and the script will save all the submitted data (the action link in the form below assumes that the script above is saved in the text file named "form.cgi"):

<FORM action="form.cgi">
    Name:<BR><INPUT type="TEXT" name="name"><BR><BR>
    Email:<BR><INPUT type="TEXT" name="email"><BR><BR>
    Message:<BR>
        <TEXTAREA cols="75" rows="5" name="message">
        </TEXTAREA><BR><BR>
    <INPUT type="SUBMIT" name="Submit" value="Submit">
</FORM>

The script below can be used on a desktop PC to easily view all the forms submitted at the script above. It provides nice GUI navigation, message count, sort by any data column, etc.:

REBOL [title: "CGI form submission viewer"]

sort-column: 4  ; even numered cols contain data (2nd col is time stamp)
signups: load http://yoursite.com/submitted_forms.txt
do create-list: [
    name-list: copy []
    foreach item signups [append name-list (pick item sort-column)]
]
view center-face layout [
    the-list: text-list 600x150 data name-list [
        foreach item signups [
            if (pick item sort-column) = value [
                dt: copy ""
                foreach [label data] item [
                    dt: rejoin [
                        dt newline label "  " data 
                    ]
                ]
                a/text: dt
                show a
            ]
        ]
    ]
    a: area 600x300 across
    btn "Sort by..." [
        sort-column: to-integer request-text/title/default {
            Data column to list:} "4"
        do create-list
        the-list/data: name-list
        show the-list
    ]
    tab text join (form length? signups) " entries."
]

Here's another script that removes the title columns and reduces the form data into a usable format. Possibilities with managing form data like this are endless:

submissions: load http://yoursite.com/submitted_forms.txt
do create-list: [
    data: copy []
    foreach block submissions [append/only data (extract/index block 2 4)]
    datastring: copy {}
    foreach block data [
        datastring: join datastring "[^/"
        foreach item block [datastring: rejoin [datastring item newline]]
        datastring: join datastring "]^/^/"
    ]
    editor datastring
]

The following example demonstrates how to upload files to your web server using the decode-multipart-form-data function by Andreas Bolka:

#! /home/path/public_html/rebol/rebol -cs
REBOL [Title: "HTTP File Upload"]
print {content-type: text/html^/}
print {<HTML><HEAD><TITLE>File Upload</TITLE></HEAD>}
print {<BODY><br><br><center><table width=95% border=1>}
print {<tr><td width=100%><br><center>}

read-cgi: func [/local data buffer][
    switch system/options/cgi/request-method [
        "POST" [
            data: make string! 1020
            buffer: make string! 16380
            while [positive? read-io system/ports/input buffer 16380][
                append data buffer
                clear buffer
            ]
        ]
        "GET" [data: system/options/cgi/query-string]
    ]
    data
]

submitted: read-cgi

if submitted/2 = none [
    print {
        <FORM ACTION="./upload.cgi" 
        METHOD="post" ENCTYPE="multipart/form-data">
            <strong>Upload File:</strong><br><br> 
            <INPUT TYPE="file" NAME="photo"> <br><br>
            <INPUT TYPE="submit" NAME="Submit" VALUE="Upload">  
        </FORM>
        <br></center></td></tr></table></BODY></HTML>
    }
    quit
]

decode-multipart-form-data: func [
    p-content-type
    p-post-data
    /local list ct bd delim-beg delim-end non-cr non-lf non-crlf mime-part
] [
    list: copy []
    if not found? find p-content-type "multipart/form-data" [return list]
    ct: copy p-content-type
    bd: join "--" copy find/tail ct "boundary="
    delim-beg: join bd crlf
    delim-end: join crlf bd
    non-cr:     complement charset reduce [ cr ]
    non-lf:     complement charset reduce [ newline ]
    non-crlf:   [ non-cr | cr non-lf ]
    mime-part:  [
        ( ct-dispo: content: none ct-type: "text/plain" )
        delim-beg ; mime-part start delimiter
        "content-disposition: " copy ct-dispo any non-crlf crlf
        opt [ "content-type: " copy ct-type any non-crlf crlf ]
        crlf ; content delimiter
        copy content
        to delim-end crlf ; mime-part end delimiter
        ( handle-mime-part ct-dispo ct-type content )
    ]
    handle-mime-part: func [
        p-ct-dispo
        p-ct-type
        p-content
        /local tmp name value val-p
    ] [
        p-ct-dispo: parse p-ct-dispo {;="}
        name: to-set-word (select p-ct-dispo "name")
        either (none? tmp: select p-ct-dispo "filename")
               and (found? find p-ct-type "text/plain") [
            value: content
        ] [
            value: make object! [
                filename: copy tmp
                type: copy p-ct-type
                content: either none? p-content [none][copy p-content]
            ]
        ]
        either val-p: find list name
            [change/only next val-p compose [(first next val-p) (value)]]
            [append list compose [(to-set-word name) (value)]]
    ]
    use [ct-dispo ct-type content] [
        parse/all p-post-data [some mime-part "--" crlf]
    ]
    list
]

; After the following line, "probe cgi-object" will display all parts of
; the submitted multipart object:

cgi-object: construct decode-multipart-form-data 
    system/options/cgi/content-type copy submitted

; Write file to server using the original filename, and notify the user:

the-file: last split-path to-file copy cgi-object/photo/filename
write/binary the-file cgi-object/photo/content
print {
    <strong>UPLOAD COMPLETE</strong><br><br>
    <strong>Files currently in this folder:</strong><br><br>
}
folder: sort read %.
foreach file folder [
    print [rejoin [{<a href="./} file {">} file {</a><br>}]]
]
print {<br></td></tr></table></BODY></HTML>}

; Alternatively, you could forward to a different page when done:
; 
; wait 3
; refresh-me: {
;     <head><title></title>
;     <META HTTP-EQUIV="REFRESH" CONTENT="0; URL=./index.cgi"></head>
; }
; print refresh-me

This variation of the upload script allows you to select the directory to which files are uploaded:

#! /home/path/public_html/rebol/rebol -cs
REBOL [Title: "HTTP File Upload"]
print {content-type: text/html^/}
print {<HTML><HEAD><TITLE>File Upload</TITLE></HEAD>}
print {<BODY><br><br><center><table width=95% border=1>}
print {<tr><td width=100%><br><center>}

read-cgi: func [/local data buffer][
    switch system/options/cgi/request-method [
        "POST" [
            data: make string! 1020
            buffer: make string! 16380
            while [positive? read-io system/ports/input buffer 16380][
                append data buffer
                clear buffer
            ]
        ]
        "GET" [data: system/options/cgi/query-string]
    ]
    data
]

submitted: read-cgi

if submitted/2 = none [
    print {
        <FORM ACTION="./upload.cgi" 
        METHOD="post" ENCTYPE="multipart/form-data">
            <strong>Upload File:</strong><br><br> 
            <INPUT TYPE="file" NAME="photo"> <br><br>
            <INPUT TYPE="text" NAME="path" SIZE="35" 
                VALUE="/home/path/public_html/">
            <INPUT TYPE="submit" NAME="Submit" VALUE="Upload">  
        </FORM>
        <br></center></td></tr></table></BODY></HTML>
    }
    quit
]

decode-multipart-form-data: func [
    p-content-type
    p-post-data
    /local list ct bd delim-beg delim-end non-cr non-lf non-crlf mime-part
] [
    list: copy []
    if not found? find p-content-type "multipart/form-data" [return list]
    ct: copy p-content-type
    bd: join "--" copy find/tail ct "boundary="
    delim-beg: join bd crlf
    delim-end: join crlf bd
    non-cr:     complement charset reduce [ cr ]
    non-lf:     complement charset reduce [ newline ]
    non-crlf:   [ non-cr | cr non-lf ]
    mime-part:  [
        ( ct-dispo: content: none ct-type: "text/plain" )
        delim-beg ; mime-part start delimiter
        "content-disposition: " copy ct-dispo any non-crlf crlf
        opt [ "content-type: " copy ct-type any non-crlf crlf ]
        crlf ; content delimiter
        copy content
        to delim-end crlf ; mime-part end delimiter
        ( handle-mime-part ct-dispo ct-type content )
    ]
    handle-mime-part: func [
        p-ct-dispo
        p-ct-type
        p-content
        /local tmp name value val-p
    ] [
        p-ct-dispo: parse p-ct-dispo {;="}
        name: to-set-word (select p-ct-dispo "name")
        either (none? tmp: select p-ct-dispo "filename")
               and (found? find p-ct-type "text/plain") [
            value: content
        ] [
            value: make object! [
                filename: copy tmp
                type: copy p-ct-type
                content: either none? p-content [none][copy p-content]
            ]
        ]
        either val-p: find list name
            [change/only next val-p compose [(first next val-p) (value)]]
            [append list compose [(to-set-word name) (value)]]
    ]
    use [ct-dispo ct-type content] [
        parse/all p-post-data [some mime-part "--" crlf]
    ]
    list
]

cgi-object: construct decode-multipart-form-data 
    system/options/cgi/content-type copy submitted

the-file: last split-path to-file copy cgi-object/photo/filename
write/binary the-file cgi-object/photo/content
print {
    <strong>UPLOAD COMPLETE</strong><br><br>
    <strong>Files currently in this folder:</strong><br><br>
}
folder: sort read to-file cgi-object/path
current-folder: rejoin  at 
foreach file folder [
    print [rejoin [
        {<a href="http://site.com/"} (at cgi-object/path 28) file {">}
        ; convert path to URL
        file "</a><br>"
    ]]
]
print {<br></td></tr></table></BODY></HTML>}

Here's a script that demonstrates how to push download a file to the user's browser:

#!/home/path/public_html/rebol/rebol -cs
REBOL []
submitted: decode-cgi system/options/cgi/query-string
root-path: "/home/path"

; if no data has been submitted, request file name:

if ((submitted/2 = none) or (submitted/4 = none)) [
    print "content-type: text/html^/"
    print [<STRONG>"W A R N I N G  -  "]
    print ["Private Server, Login Required:"</STRONG><BR><BR>]
    print [<FORM ACTION="./download.cgi">]
    print [" Username: " <INPUT TYPE=text SIZE="50" NAME="name"><BR><BR>]
    print [" Password: " <INPUT TYPE=text SIZE="50" NAME="pass"><BR><BR>]
    print [" File: "<BR><BR>]
    print [<INPUT TYPE=text SIZE="50" NAME="file" VALUE="/public_html/">]
    print [<BR><BR>]
    print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
    print [</FORM>]
    quit
]

; check user/pass, end program if incorrect:

username: submitted/2 password: submitted/4 
either (username = "user") and (password = "pass) [
    ; if user/pass is ok, go on
][
    print "content-type: text/html^/"
    print "Incorrect Username/Password." quit
]

print rejoin [
    "Content-Type: application/x-unknown"
    newline
    "Content-Length: "
    (size? to-file join root-path submitted/6) 
    newline
    "Content-Disposition: attachment; filename=" 
    (second split-path to-file submitted/6)
    newline
]

data: read/binary to-file join root-path submitted/6
data-length: size? to-file join root-path submitted/6
write-io system/ports/output data data-length

9.9.5 A Complete Web Server Management Application

This final script makes use of several previous examples, and some additional code, to form a complete web server management application. It allows you to list directory contents, upload, download, edit, and search for files, execute OS commands (chmod, ls, mv, cp, etc. - any command available on your web server's operating system), and run REBOL commands directly on your server. Edited files are automatically backed up into an "edit_history" folder on the server before being saved. No configuration is required for most web servers. Just save this script as "web-tool.cgi", upload it and the REBOL interpreter into the same folder as your web site's index.html file, set permissions (chmod) to 755, then go to http://yourwebsite/web-tool.cgi.

THIS SCRIPT CAN POSE A MAJOR SECURITY THREAT TO YOUR SERVER. It can potentially enable anyone to gain control of your web server and everything it contains. DO NOT install it on your server if you're at all concerned about security, or if you don't know how to secure your server yourself.

The first line of this script must point to the location of the REBOL interpreter on your web server, and you must use a version of REBOL which supports the "call" function (version 2.76 is recommended). By default, the REBOL interpreter should be uploaded to the same path as this script, that folder should be publicly accessible, and you must upload the correct version of REBOL for the operating system on which your server runs. IN THIS EXAMPLE, THE REBOL INTERPRETER HAS BEEN RENAMED "REBOL276".

#! ./rebol276 -cs
REBOL [Title: "REBOL CGI Web Site Manager"]

;-------------------------------------------------------------------------
; Upload this script to the same path as index.html on your server, then
; upload the REBOL interpreter to the path above (the same path as the
; script, by default).  CHMOD IT AND THIS SCRIPT TO 755.  Then, to run the
; program, go to www.yoursite.com/this-script.cgi .
;-------------------------------------------------------------------------

; YOU CAN EDIT THESE VARIABLES, _IF_ NECESSARY (change the quoted values):

; The user name you want to use to log in:

    set-username:   "username"

; The password you want to use to log in:

    set-password:   "password"

;-------------------------------------------------------------------------

; Do NOT edit these variables, unless you really know what you're doing:

doc-path: to-string what-dir
script-subfolder: find/match what-dir doc-path
if script-subfolder = none [script-subfolder: ""]

;-------------------------------------------------------------------------

; Get submitted data:

selection: decode-cgi system/options/cgi/query-string

read-cgi: func [/local data buffer][
    switch system/options/cgi/request-method [
        "POST" [
            data: make string! 1020
            buffer: make string! 16380
            while [positive? read-io system/ports/input buffer 16380][
                append data buffer
                clear buffer
            ]
        ]
        "GET" [data: system/options/cgi/query-string]
    ]
    the-data: data
    data
]
submitted: read-cgi
submitted-block: decode-cgi the-data

; ------------------------------------------------------------------------

; This section should be first because it prints a different header
; for a push download (not "content-type: text/html^/"):

if selection/2 = "download-confirm" [
    print rejoin [
        "Content-Type: application/x-unknown"
        newline
        "Content-Length: "
        (size? to-file selection/4) 
        newline
        "Content-Disposition: attachment; filename=" 
        (second split-path to-file selection/4)
        newline
    ]

    data: read/binary to-file selection/4
    data-length: size? to-file selection/4
    write-io system/ports/output data data-length
    quit
]

;-------------------------------------------------------------------------

; Print the normal HTML headers, for use by the rest of the script:

print "content-type: text/html^/"
print {<HTML><HEAD><TITLE>Web Site Manager</TITLE></HEAD><BODY>}

;-------------------------------------------------------------------------

; If search has been called (via link on main form):

if selection/2 = "confirm-search" [
    print rejoin [
        {<center><a href="./} 
        (second split-path system/options/script) {?name=} set-username
        {&pass=} set-password {">Back to Web Site Manager</a></center>}
    ]
    print {<center><table border="1" cellpadding="10" width=80%><tr><td>}
    print [<CENTER><TABLE><TR><TD>]
    print rejoin [
        {<FORM ACTION="./} (second split-path system/options/script) 
        {"> Text to search for: <BR> <INPUT TYPE="TEXT" SIZE="50"}
        {NAME="phrase"><BR><BR>Folder to search in: <BR>}
        {<INPUT TYPE="TEXT" SIZE="50" NAME="folder" VALUE="} what-dir
        {" ><BR><BR><INPUT TYPE=hidden NAME=perform-search }
        {VALUE="perform-search"><INPUT TYPE="SUBMIT" NAME="Submit" }
        {VALUE="Submit"></FORM></TD></TR></TABLE></CENTER>}
        {</td></tr></table></center></BODY></HTML>}
    ]
    quit
]

;-------------------------------------------------------------------------

; If edited file text has been submitted:

if submitted-block/2 = "save" [

    ; Save newly edited document:
    write (to-file submitted-block/6) submitted-block/4
    print {<center><strong>Document Saved:</strong>
        <br><br><table border="1" width=80% cellpadding="10"><tr><td>}
    prin [<center><textarea cols="100" rows="15" name="contents">]
    prin replace/all read (
        to-file (replace/all submitted-block/6 "%2F" "/")
    ) "</textarea>" "<\/textarea>"
    print [</textarea></center>]
    print rejoin [
        {</td></tr></table><br><a href="./} 
        (second split-path system/options/script) {?name=} set-username
        {&pass=} set-password {">Back to Web Site Manager</a></center>}
        {</BODY></HTML>}
    ]
    quit
]

;-------------------------------------------------------------------------

; If upload link has been clicked, print file upload form:

if selection/2 = "upload-confirm" [
    print rejoin [
        {<center><a href="./} 
        (second split-path system/options/script) {?name=} set-username
        {&pass=} set-password {">Back to Web Site Manager</a></center>}
    ]
    print {<center><table border="1" cellpadding="10" width=80%><tr><td>}
    print {<center>}

    ; If just the link was clicked - no data submitted yet:

    if selection/4 = none [
        print rejoin [
            {<FORM ACTION="./} (second split-path system/options/script)
            {" METHOD="post" ENCTYPE="multipart/form-data">
                <strong>Upload File:</strong><br><br> 
                <INPUT TYPE=hidden NAME=upload-confirm 
                VALUE="upload-confirm">
                <INPUT TYPE="file" NAME="photo"> <br><br>
                Folder: <INPUT TYPE="text" NAME="path" SIZE="35" 
                    VALUE="} what-dir {"> 
                <INPUT TYPE="submit" NAME="Submit" VALUE="Upload">  
            </FORM>
            <br></center></td></tr></table></center></BODY></HTML>}
        ]
        quit
    ]
]

;-------------------------------------------------------------------------

; If upload data has been submitted:

if (submitted/2 = #"-") and (submitted/4 = #"-") [

    ; This function is by Andreas Bolka:

    decode-multipart-form-data: func [
        p-content-type
        p-post-data
        /local list ct bd delim-beg delim-end non-cr
        non-lf non-crlf mime-part
    ] [
        list: copy []
        if not found? find p-content-type "multipart/form-data" [
            return list
        ]
        ct: copy p-content-type
        bd: join "--" copy find/tail ct "boundary="
        delim-beg: join bd crlf
        delim-end: join crlf bd
        non-cr:     complement charset reduce [ cr ]
        non-lf:     complement charset reduce [ newline ]
        non-crlf:   [ non-cr | cr non-lf ]
        mime-part:  [
            ( ct-dispo: content: none ct-type: "text/plain" )
            delim-beg ; mime-part start delimiter
            "content-disposition: " copy ct-dispo any non-crlf crlf
            opt [ "content-type: " copy ct-type any non-crlf crlf ]
            crlf ; content delimiter
            copy content
            to delim-end crlf ; mime-part end delimiter
            ( handle-mime-part ct-dispo ct-type content )
        ]
        handle-mime-part: func [
            p-ct-dispo
            p-ct-type
            p-content
            /local tmp name value val-p
        ] [
            p-ct-dispo: parse p-ct-dispo {;="}
            name: to-set-word (select p-ct-dispo "name")
            either (none? tmp: select p-ct-dispo "filename")
                   and (found? find p-ct-type "text/plain") [
                value: content
            ] [
                value: make object! [
                    filename: copy tmp
                    type: copy p-ct-type
                    content: either none? p-content [none][copy p-content]
                ]
            ]
            either val-p: find list name
                [
                    change/only next val-p compose [
                        (first next val-p) (value)
                    ]
                ]
                [append list compose [(to-set-word name) (value)]]
        ]
        use [ct-dispo ct-type content] [
            parse/all p-post-data [some mime-part "--" crlf]
        ]
        list
    ]

    ; After the following line, "probe cgi-object" will display all parts
    ; of the submitted multipart object:

    cgi-object: construct decode-multipart-form-data 
        system/options/cgi/content-type copy submitted

    ; Write file to server using the original filename, and notify the
    ; user:

    the-file: last split-path to-file copy cgi-object/photo/filename
    write/binary 
        to-file join cgi-object/path the-file 
        cgi-object/photo/content
    print rejoin [
        {<center><a href="./} 
        (second split-path system/options/script) {?name=} set-username
        {&pass=} set-password {">Back to Web Site Manager</a></center>}
    ]
    print {
        <center><table border="1" width=80% cellpadding="10"><tr><td>
        <strong>UPLOAD COMPLETE</strong><br><br></center>
        <strong>Files currently in this folder:</strong><br><br>
    }
    change-dir to-file cgi-object/path
    folder: sort read what-dir
    foreach file folder [
        print [
            rejoin [
                {<a href="./} (second split-path system/options/script)
                {?editor-confirm=editor-confirm&thefile=} 
                what-dir file {">(edit)</a>   }
                {<a href="./} (second split-path system/options/script)
                {?download-confirm=download-confirm&thefile=}
                what-dir file {">} "(download)</a>   " 
                {<a href="./} (find/match what-dir doc-path) file 
                {">} file {</a><br>}
            ]
        ]
    ]
    print {</td></tr></table></center></BODY></HTML>}
    quit
]

;-------------------------------------------------------------------------

; If no data has been submitted, print form to request user/pass:

if ((selection/2 = none) or (selection/4 = none)) [
    print rejoin [{
        <STRONG>W A R N I N G  -  Private Server, Login Required:</STRONG>
        <BR><BR>
        <FORM ACTION="./} (second split-path system/options/script) {">
        Username: <INPUT TYPE=text SIZE="50" NAME="name"><BR><BR>
        Password: <INPUT TYPE=text SIZE="50" NAME="pass"><BR><BR>
        <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
        </FORM></BODY></HTML>
    }]
    quit
]

;-------------------------------------------------------------------------

; If a folder name has been submitted, print file list:

if ((selection/2 = "command-submitted") and (
    selection/4 = "call {^/^/^/^/}")
) [
    print rejoin [
        {<center><a href="./} 
        (second split-path system/options/script) {?name=} set-username
        {&pass=} set-password {">Back to Web Site Manager</a></center>}
    ]
    print {<center><table border="1" cellpadding="10" width=80%><tr><td>}
    print {<strong>Files currently in this folder:</strong><br><br>}
    change-dir to-file selection/6
    folder: sort read what-dir
    foreach file folder [
        print rejoin [
            {<a href="./} (second split-path system/options/script)
            {?editor-confirm=editor-confirm&thefile=}
            what-dir file {">} "(edit)</a>   " 
            {<a href="./} (second split-path system/options/script)
            {?download-confirm=download-confirm&thefile=}
            what-dir file {">} "(download)</a>   " 
            {<a href="./} (find/match what-dir doc-path) file {">} file
            {</a><br>}
        ]
    ]
    print {</td></tr></table></center></BODY></HTML>}
    quit
]

;-------------------------------------------------------------------------

; If editor has been called (via a constructed link):

if selection/2 = "editor-confirm" [

    ; backup (before changes are made):

    cur-time: to-string replace/all to-string now/time ":" "-"
    document_text: read to-file selection/4
    if not exists? to-file rejoin [
        doc-path script-subfolder "edit_history/"
    ] [
        make-dir to-file rejoin [
            doc-path script-subfolder "edit_history/"
        ]
    ]
    write to-file rejoin [
        doc-path script-subfolder "edit_history/" 
        to-string (second split-path to-file selection/4)
        "--" now/date "_" cur-time ".txt"
    ] document_text

    ; note the POST method in the HTML form:

    print rejoin [
        {<center><strong>Be sure to SUBMIT when done:</strong>}
        {<BR><BR><FORM method="post" ACTION="./} 
        (second split-path system/options/script) {">}
        {<INPUT TYPE=hidden NAME=submit_confirm VALUE="save">}
        {<textarea cols="100" rows="15" name="contents">}
        (replace/all document_text "</textarea>" "<\/textarea>")
        {</textarea><BR><BR><INPUT TYPE=hidden NAME=path VALUE="}
        selection/4
        {"><INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
        </FORM></center></BODY></HTML>}
    ]
    quit
]

;-------------------------------------------------------------------------

; If search criteria has been entered:

if selection/6 = "perform-search" [
    phrase: selection/2
    start-folder: to-file selection/4
    change-dir start-folder
    ; found-list: ""

    recurse: func [current-folder] [ 
        foreach item (read current-folder) [ 
            if not dir? item [  
                if error? try [
                    if find (read to-file item) phrase [
                        print rejoin [
                            {<a href="./}
                            (second split-path system/options/script)
                            {?editor-confirm=editor-confirm&theitem=} 
                            what-dir item {">(edit)</a>   }
                            {<a href="./}
                            (second split-path system/options/script)
                            {?download-confirm=download-confirm&theitem=}
                            what-dir item {">(download)</a>   "}
                            phrase {" found in:  } 
                            {<a href="./} (find/match what-dir doc-path)
                            item {">} item {</a><BR>}
                        ]
                        ; found-list: rejoin [
                        ;     found-list newline what-dir item
                        ; ]
                    ] 
                ] [print rejoin ["error reading " item]]
            ]
        ]
        foreach item (read current-folder) [ 
            if dir? item [
                change-dir item 
                recurse %.\
                change-dir %..\
            ] 
        ]
    ]

    print rejoin [
        {<center><a href="./} 
        (second split-path system/options/script) {?name=} set-username
        {&pass=} set-password {">Back to Web Site Manager</a></center>}
    ]
    print {<center><table border="1" cellpadding="10" width=80%><tr><td>}
    print rejoin [
        {<strong>SEARCHING for "} phrase {" in } start-folder
        {</strong><BR><BR>}
    ]
    recurse %.\
    print {<BR><strong>DONE</strong><BR>}
    print {</td></tr></table></center></BODY></HTML>}
    ; save %found.txt found-list
    quit
]

;-------------------------------------------------------------------------

; This is the main entry form, used below:

entry-form: [
    print rejoin [
        {<CENTER><strong>current path: </strong>} what-dir 
        {<FORM METHOD="get" ACTION="./} 
        (second split-path system/options/script) {">}{<INPUT TYPE=hidden}
        { NAME=submit_confirm VALUE="command-submitted">}
        {<TEXTAREA COLS="100" ROWS="10" NAME="contents">}
        {call {^/^/^/^/}</textarea><BR><BR>}
        {List Files: <INPUT TYPE=text SIZE="35" NAME="name" VALUE="} 
        what-dir {"><INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">}
        {          <A HREF="./} (second split-path system/options/script)
        {?upload-confirm=upload-confirm">upload</A>       } ; leave spaces
        {<A HREF="./} (second split-path system/options/script)
        {?confirm-search=confirm-search">search</A>} 
        {</FORM><BR></CENTER>}
    ]
]

;-------------------------------------------------------------------------

; If code has been submitted, print the output, along with an entry form):

if ((selection/2 = "command-submitted") and (
selection/4 <> "call {^/^/^/^/}") and ((to-file selection/6) = what-dir))[
    write %commands.txt join "REBOL[]^/" selection/4
    ; The "call" function requires REBOL version 2.76:
    call/output/error 
        "./rebol276 -qs commands.txt" 
        %conso.txt %conse.txt
    do entry-form
    print rejoin [
        {<CENTER>Output: <BR><BR>}
        {<TABLE WIDTH=80% BORDER="1" CELLPADDING="10"><TR><TD><PRE>}
        read %conso.txt
        {</PRE></TD></TR></TABLE><BR><BR>}
        {Errors: <BR><BR>}
        read %conse.txt
        {</CENTER></BODY></HTML>}
    ]
    quit
]
;-------------------------------------------------------------------------

if ((selection/2 = "command-submitted") and (
    selection/4 <> "call {^/^/^/^/}") and (
    (to-file selection/6) <> what-dir)
) [
    print rejoin [
        {<center><a href="./} 
        (second split-path system/options/script) {?name=} set-username
        {&pass=} set-password {">Back to Web Site Manager</a></center>}
    ]
    print {
        <center><table border="1" cellpadding="10" width=80%><tr><td>
            <center>
        You must EITHER enter a command, OR enter a file path to list.<BR>
        Please go back and try again (refresh the page if needed).
            </center>
        </td></tr></center></BODY></HTML>
    }
    quit
]

;-------------------------------------------------------------------------

; Otherwise, check submitted user/pass, then print form for code entry:

username: selection/2 password: selection/4 
either (username = set-username) and (password = set-password) [ 
    ; if user/pass is ok, go on
][
    print "Incorrect Username/Password. </BODY></HTML>" quit
]

do entry-form
print {</BODY></HTML>}

Be sure to see the following links for more insight about REBOL CGI programming:

http://rebol.com/docs/cgi1.html
http://rebol.com/docs/cgi2.html
http://rebol.com/docs/cgi-bbs.html
http://www.rebol.net/cookbook/recipes/0045.html

To create web sites using a PHP-like version of REBOL that runs in a web server written entirely in REBOL, see:

http://cheyenne-server.org (binaries are available for Windows, Mac, and Linux).
http://cheyenne-server.org/docs/rsp-api.html - documentation for the "RSP" (REBOL server pages) API.

9.10 WAP - Cell Phone Browser CGI Apps

Most cell phone service providers offer data options which allow users to access information on the Internet. If you use a "smart phone", your data package likely allows you to access normal web pages using a browser program that runs on your phone (with varying degrees of rendering success). Most average cell phones, however, come only with a "WAP" browser that allows you to access only very light mobile versions of web sites, and provides information in a format specifically accessible only by cell phones. Using WAP mobile sites, you can check email in Google, Yahoo, and other accounts, read news, get weather and traffic reports, manage Ebay transactions, etc. WAP versions of sites, accessible on normal cell phones are not renditions of the normal HTML sites created or interpreted by your phone, but are instead entirely separate versions of each site, created and managed on the web server by the web site creators, and simply accessed by WAP phone browsers.

You can create your own WAP CGI applications, to be accessed by any phone with a WAP browser, using REBOL. WAP scripts are just as easy to ceate as normal CGI web scripts. Instead of HTML, however, you simply need to format the output of your scripts using WAP ("Wireless Application Protocal") syntax. Reference information about WAP tags ("WML") can be found at http://www.w3schools.com/WAP/wml_reference.asp

Here's a basic WAP CGI script which demonstrates the stock code required in all your REBOL WAP scripts. The first 5 lines should be memorized. You'll need to copy them verbatim to the beginning of every WAP script you create. The last lines demonstrate some other important WAP tags. Notice that the wml tag basically replaces the html tag from normal HTML. Every WAP page also contains card tags, which basically replace the head and body tags in normal HTML. This script prints the current time in a WAP browser. Remember to set the permissions of (chmod) any WAP CGI script to 755:

#!./rebol276 -cs
REBOL []
prin {Content-type: text/vnd.wap.wml^/^/}
prin {<?xml version="1.0" encoding="iso-8859-1"?>^/}
prin {<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">^/}

; The lines above are standard headers for any WAP CGI script.  The
; lines below print out the standard tags needed to print text on a
; WAP page.  Included inside the wml, card and p (paragraph) tags is
; one line that prints the current time:

print {<wml><card id="1" title="Current Time"><p>}
print now
print {</p></card></wml>}
quit

The following nearly identical script converts text data output by another script on the web site to WAP format, so that it can be read on small cell phone browsers. Because WAP syntax is a bit more strict than HTML, some HTML tags must be removed (replaced) from the source script output. You must be careful to strip out unnecessary tags and characters in text formatted for display in cell phones. Most WAP browsers will simply display an error if they encounter improperly formatted content:

#!./rebol276 -cs
REBOL []
prin {Content-type: text/vnd.wap.wml^/^/}
prin {<?xml version="1.0" encoding="iso-8859-1"?>^/}
prin {<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">^/}

print {<wml><card id="1" title="Wap Page"><p>}
prin replace/all (read http://site.com/page.cgi) {</BODY> </HTML>} {}
print {</p></card></wml>}
quit

Here's a bit more useful version of the above script which allows users to specify the file to be converted, right in the URL of WAP page (i.e., if this script is at site.com/wap.cgi, and the user wants to read page.txt in their WAP browser, then the URL would be "http://site.com/wap.cgi?page.txt"):

#!./rebol276 -cs
REBOL []
submitted: decode-cgi system/options/cgi/query-string
prin {Content-type: text/vnd.wap.wml^/^/}
prin {<?xml version="1.0" encoding="iso-8859-1"?>^/}
prin {<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">^/}
print {<wml><card id="1" title="Wap Page"><p>}

; The line below joins the web site URL with the submitted page,
; reads it, and parses it, up to some indicated marker text, so
; that only the text before the marker text is displayed:

parse read join http://site.com/ submitted/2 [
    thru submitted/2 copy p to "some marker text"
]
prin p

print {</p></card></wml>}
quit

Here's a version of the above script that lets users select a document to be converted and displayed. This code makes use of select and option tags, which work like HTML dropdown boxes in forms. It also demonstrates how to use anchor, go and postfield tags to submit form data. These work like the "action" in HTML forms. Variable labels for information entered by the user are preceded by a dollar symbol ("$"), and the variable name is enclosed in parentheses (i.e., the $(doc) variable below is submitted to the wap.cgi program). The anchor tag creates a clickable link that makes the action occur:

#!./rebol -cs
REBOL []
submitted: decode-cgi system/options/cgi/query-string
prin {Content-type: text/vnd.wap.wml^/^/}
prin {<?xml version="1.0" encoding="iso-8859-1"?>^/}
prin {<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">^/}

; If no data has been submitted, do this by default:

if submitted/2 = none [

    ; Print some standard tags: 

    print {<wml><card id="1" title="Select Doc"><p>}

    ; Get a list of subfolders and display them in a dropdown box:

    folders: copy []
    foreach folder read %./docs/ [
        if find to-string folder {/} [append folders to-string folder]
    ]
    print {Doc: <select name="doc">}
    foreach folder folders [
        folder: replace/all folder {/} {}
        print rejoin [{<option value="} folder {">} folder {</option>}]
    ]
    print {</select>

    ; Create a link to submit the chosen folder, then close the tags
    ; from above:

    <anchor>
       <go method="get" href="wap.cgi">
           <postfield name="doc" value="$(doc)"/>
       </go>
       Submit
    </anchor>}
    print {</p></card></wml>}
    quit
]

; If some data has been submitted, read the selected doc:

print rejoin [{<wml><card id="1" title="} submitted/2 {"><p>}]

parse read join http://site.com/docs/ submitted/2 [
    thru submitted/2 copy p to "end of file"
]
prin p

print {</p></card></wml>}
quit

This script breaks up the selected text document into small (130 byte) chunks so that it can be navigated more quickly. Each separate card is displayed as a different page in the WAP browswer, and anchor links are provided to navigate forward and backword between the cards. For the user, paging through data in this way tends to be much faster than scrolling through long results line by line:

#!./rebol276 -cs
REBOL []
submitted: decode-cgi system/options/cgi/query-string
prin {Content-type: text/vnd.wap.wml^/^/}
prin {<?xml version="1.0" encoding="iso-8859-1"?>^/}
prin {<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">^/}

count: 0
p: read http://site.com/folder.txt 
print {<wml>}
forskip p 130 [

    ; Create a counter, to appear as each card title, then print links
    ; to go forward and backward between the cards:

    count: count + 1
    print rejoin [{<card id="} count {" title="page-} count {"><p>}]
    print rejoin [
        {<anchor>Next<go href="#} (count + 1) {"/></anchor>}
    ]
    print rejoin [{<anchor>Back<prev/></anchor>}]

    ; Print 130 characters in each card:

    print copy/part p 130
    print {</p></card>}
]
print {</wml>}
quit

This next script combines the techniques explained so far, and allows the user to select a file on the web server, using a dropdown box, and displays the selected file in 130 byte pages:

#!./rebol276 -cs
REBOL []
submitted: decode-cgi system/options/cgi/query-string
prin {Content-type: text/vnd.wap.wml^/^/}
prin {<?xml version="1.0" encoding="iso-8859-1"?>^/}
prin {<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">^/}

if submitted/2 = none [   
    print {<wml><card id="1" title="Select Teacher"><p>}
    ; print {Name: <input name="name" size="12"/>}
    folders: copy []
    foreach folder read %./Teachers/ [
        if find to-string folder {/} [append folders to-string folder]
    ]
    print {Teacher: <select name="teacher">}
    foreach folder folders [
        folder: replace/all folder {/} {}
        print rejoin [
            {<option value="} folder {">} folder {</option>}
        ]
    ]
    print {</select>
    <anchor>
       <go method="get" href="wap.cgi">
           <postfield name="teacher" value="$(teacher)"/>
       </go>
       Submit
    </anchor>}
    print {</p></card></wml>}
    quit
]

count: 0
parse read join http://site.com/folder/ submitted/2 [
    thru submitted/2 copy p to "past students"
]
print {<wml>}

forskip p 130 [
    count: count + 1
    print rejoin [
        {<card id="} count {" title="} submitted/2 "-" count {"><p>}
    ]
    print rejoin [
        {<anchor>Next<go href="#} (count + 1) {"/></anchor>}
    ]
    print rejoin [{<anchor>Back<prev/></anchor>}]
    print copy/part p 130
    print {</p></card>}
]
print {</wml>}
quit

Finally, this script allows users to select a file, and enter some text to be saved in that file, using the input tag:

#!./rebol276 -cs
REBOL []
submitted: decode-cgi system/options/cgi/query-string
prin {Content-type: text/vnd.wap.wml^/^/}
prin {<?xml version="1.0" encoding="iso-8859-1"?>^/}
prin {<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">^/}

if submitted/2 = none [   
    print {<wml><card id="1" title="Select Teacher"><p>}
    print {Insert Text: <input name="thetext" size="12"/>}
    folders: copy []
    foreach folder read %./Teachers/ [
        if find to-string folder {/} [
            append folders to-string folder
        ]
    ]
    print {Teacher: <select name="teacher">}
    foreach folder folders [
        folder: replace/all folder {/} {}
        print rejoin [
            {<option value="} folder {">} folder {</option>}
        ]
    ]
    print {</select>
    <anchor>
       <go method="get" href="wapinsert.cgi">
           <postfield name="teacher" value="$(teacher)"/>
           <postfield name="thetext" value="$(thetext)"/>
       </go>
       Submit
    </anchor>}
    print {</p></card></wml>}
    quit
]

chosen-file: rejoin [%./Teachers/ submitted/2 "/schedule.txt"]
adjusted-file: read/lines chosen-file
insert next next next next adjusted-file submitted/4
write/lines chosen-file adjusted-file

count: 0
parse read join http://site.com/folders/ submitted/2 [
    thru submitted/2 copy p to "past students"
]
print {<wml>}

forskip p 130 [
    count: count + 1
    print rejoin [
        {<card id="} count {" title="} submitted/2 "-" count {"><p>}
    ]
    print rejoin [
        {<anchor>Next<go href="#} (count + 1) {"/></anchor>}
    ]
    print rejoin [{<anchor>Back<prev/></anchor>}]
    print copy/part p 130
    print {</p></card>}
]
print {</wml>}
quit

This script allows users to read email messages from any POP server, on their cell phone:

#!./rebol276 -cs
REBOL []
submitted: decode-cgi system/options/cgi/query-string
prin {Content-type: text/vnd.wap.wml^/^/}
prin {<?xml version="1.0" encoding="iso-8859-1"?>^/}
prin {<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">^/}

accounts: [
    ["pop.server" "smtp.server" "username" "password" you@site.com]
    ["pop.server2" "smtp.server2" "username" "password" you@site2.com]
    ["pop.server3" "smtp.server3" "username" "password" you@site3.com]
]

if ((submitted/2 = none) or (submitted/2 = none)) [   
    print {<wml><card id="1" title="Select Account"><p>}
    print {Account: <select name="account">}
    forall accounts [
        print rejoin [
            {<option value="} index? accounts {">}
            last first accounts {</option>}
        ]
    ]
    print {</select>
    <select name="readorsend">
        <option value="readselect">Read</option>
        <option value="sendinput">Send</option>
    </select>
    <anchor>
       <go method="get" href="wapmail.cgi">
           <postfield name="account" value="$(account)"/>
           <postfield name="readorsend" value="$(readorsend)"/>
       </go>
       Submit
    </anchor>}
    print {</p></card></wml>}
    quit
]

if submitted/4 = "readselect" [
    t: pick accounts (to-integer submitted/2)
    system/schemes/pop/host:  t/1
    system/schemes/default/host: t/2
    system/schemes/default/user: t/3 
    system/schemes/default/pass: t/4 
    system/user/email: t/5
    prin {<wml><card id="1" title="Choose Message"><p>}
    prin rejoin [{<setvar name="account" value="} submitted/2 {"/>}]
    prin {<select name="chosenmessage">}
    mail: read to-url join "pop://" system/user/email
    foreach message mail  [
        pretty: import-email message
        if (find pretty/subject "***SPAM***") = none [
            replace/all pretty/subject {"} {}
            replace/all pretty/subject {&} {}
            prin rejoin [
                {<option value="} 
                pretty/subject
                {">}
                pretty/subject
                {</option>}
            ]
        ]
    ]
    print {</select>
    <anchor>
       <go method="get" href="wapmail.cgi">
           <postfield name="subroutine" value="display"/>
           <postfield name="chosenmessage" value="$(chosenmessage)"/>
           <postfield name="account" value="$(account)"/>
       </go>
       Submit
    </anchor>
    </p></card></wml>}
    quit
]

if submitted/2 = "display" [
    t: pick accounts (to-integer submitted/6)
    system/schemes/pop/host:  t/1
    system/schemes/default/host: t/2
    system/schemes/default/user: t/3 
    system/schemes/default/pass: t/4 
    system/user/email: t/5
    prin {<wml><card id="1" title="Display Message"><p>}
    mail: read to-url join "pop://" system/user/email
    foreach message mail  [
        pretty: import-email message
        if pretty/subject = submitted/4 [
            replace/all pretty/content {"} {}
            replace/all pretty/content {&} {}
            replace/all pretty/content {3d} {}
            ; prin pretty/content
            ; The line above often causes errors - we need to strip
            ; out HTML tags:
            strip: copy ""
            foreach item (load/markup pretty/content) [
                if ((type? item) = string!) [strip: join strip item]
            ]
            prin strip
        ]
    ]
    print {</p></card></wml>}
    quit
]

Creating WAP versions of your REBOL CGI scripts is a fantastic way to provide even more universal access to your important data.

9.11 REBOL as a Browser Plugin

REBOL interpreters exist not only for an enormous variety of operating systems, but also as plugins for several popular browsers (Internet Explorer and many Mozilla variations, including Opera). That means that you can embed the REBOL interpreter directly into a web page, and have complete, complex REBOL programs run right inside pages of your web site (in a way similar to Flash and Java, and useful like Javascript code). This provides a nice alternative to CGI programming for any type of application that runs appropriately in the stand-alone view.exe interpreter (i.e., for games, multimedia applications, and rich graphic/GUI applications of all types). Since the browser plugin runs typical REBOL code in the same way as the downloadable view.exe interpreter, you can run code directly on your web pages, without making any changes.

To use the plugin on a web page, just include the necessary object code on your page, as in the following example. Be sure to change the URL of the script you want to run, the size of the window you want to create, and other parameters as needed. You can download the rebolb7.cab file, upload it to your own site, and run it from there, if you prefer (be aware that there are several different versions: "http://www.rebol.com/plugin/rebolb5.cab#Version=0,5,0,0", "http://www.rebol.com/plugin/rebolb6.cab#Version=0,6,0,0", and "http://www.rebol.com/plugin/rebolb7.cab#Version=1,0,0,0" - use version 7 unless you have a specific reason not to). If you use your own copy, be sure to change the URL of the cab file in the following example:

<HTML><HEAD><TITLE>REBOL Plugin Test</TITLE></HEAD><BODY><CENTER>

<OBJECT ID="REBOL" CLASSID="CLSID:9DDFB297-9ED8-421d-B2AC-372A0F36E6C5" 
    CODEBASE="http://www.rebol.com/plugin/rebolb7.cab#Version=1,0,0,0"
    WIDTH="500" HEIGHT="400">
    <PARAM NAME="LaunchURL" VALUE="http://yoursite.com/test_plugin.r">
    <PARAM NAME="BgColor" VALUE="#FFFFFF">
    <PARAM NAME="Version" VALUE="#FFFFFF">
    <PARAM NAME="BgColor" VALUE="#FFFFFF">
</OBJECT>

</CENTER></BODY></HTML>

Here's an example script that could be run at the "http://yoursite.com/test_plugin.r" link referenced in the code above. You can replace this with any valid REBOL code, and have it run directly in your browser:

REBOL []
view layout [
    size 500x400 
    btn "Click me" [alert "Plugin is working!"]
]

You can see the above example running at http://re-bol.com/test_plugin.html. The above script must be run in Internet Explorer on MS Windows. In order to use the plugin in Mozilla based browsers (Firefox, Opera, etc.), the following code must be pasted into your HTML page:

<HTML><HEAD><TITLE>REBOL Plugin Test</TITLE></HEAD><BODY><CENTER>

<OBJECT ID="REBOL_IE" CLASSID="CLSID:9DDFB297-9ED8-421d-B2AC-372A0F36E6C5"
    CODEBASE="http://re-bol.com/rebolb7.cab#Version=1,0,0,0"
    WIDTH="500" HEIGHT="400" BORDER="1" ALT="REBOL/Plugin">
    <PARAM NAME="bgcolor" value="#ffffff">
    <PARAM NAME="version" value="1.2.0">
    <PARAM NAME="LaunchURL" value="http://re-bol.com/test_plugin.r">
    <embed name="REBOL_Moz" type="application/x-rebol-plugin-v1"
        WIDTH="500" HEIGHT="400" BORDER="1" ALT="REBOL/Plugin"
        bgcolor="#ffffff"
        version="1.2.0"
        LaunchURL="http://re-bol.com/test_plugin.r"
    >
    </embed>
</OBJECT>

<script language="javascript" type="text/javascript">
var plugin_name = "REBOL/Plugin for Mozilla";
function install_rebol_plugin_mozilla() {
    if (navigator.userAgent.toLowerCase().indexOf("msie") == -1) {
        if (InstallTrigger) {
           xpi={'REBOL/Plugin for Mozilla':'http://re-bol.com/mozb2.xpi'};
           InstallTrigger.install(xpi, installation_complete);
        }
    }
}
function installation_complete(url, status) {
    if (status == 0) location.reload();
}
function is_mozilla_plugin_installed() {
    if (window.navigator.plugins) {
        for (var i = 0; i < window.navigator.plugins.length; i++) {
            if (window.navigator.plugins[i].name == plugin_name)
            return true;
        }
    }
    return false;
}
if (!is_mozilla_plugin_installed()) install_rebol_plugin_mozilla();
</script>

</CENTER></BODY></HTML>

You can see the above script working at http://re-bol.com/test_plugin_mozilla.html.

For more information about the REBOL plugins, see http://www.rebol.com/plugin/install.html and http://home.comcast.net/~rebolore/plugin-guide.pdf.

9.12 Using Databases

Databases manage all the difficult details of searching, sorting, and otherwise manipulating large amounts of data, quickly and safely in a multiuser environment. MySQL is a free, open source database system used in many web sites and software projects. ODBC is a common interface that allows programmers to connect to many other types of databases. The most recent releases of REBOL, along with all commercial versions, have built-in native access to MySQL, ODBC, and other database formats. You can also download a free MySQL protocol module that runs in every free version of REBOL, from http://softinnov.org/rebol/mysql.shtml. A free module for the postgre database system is also available at that site.

To explore database concepts and techniques in this section, we'll use the open source MySQL module above because it provides access to a powerful and popular database solution which works even in old and unusual versions of REBOL.

Most web hosting accounts come with MySQL already installed. See your web host account instructions to learn how to access it (you need a web address, database name, user name, and password). To get a free, simple-to-install web server package for Windows that includes MySQL, go to http://www.uniformserver.com. That program enables you to easily install a web server with MySQL pre-configured on your local computer. It's useful if you don't have access to a web server on the Internet, or if you want to create multiuser applications which use MySQL on a local network.

To use the REBOL MySQL module above, unpack the compressed "rip" file available at the link above. This step only needs to be done the first time you use the package on a given computer:

do mysql-107.rip

Every time you access a MySQL database, you need to "do" the module that was unpacked in the step above:

do %mysql-r107/mysql-protocol.r

; At the time of this writing, 1.07 was the most current version of
; the mysql module.  Update the numbers in the previous two lines
; of code to reflect the current version number you've downloaded.

Next, enter the database info (location, username, and password), as in the instructions at http://softinnov.org/rebol/mysql-usage.html:

db: open mysql://username:password@yourwebsite.com/yourdatabasename

In MySQL and other databases, data is stored in "tables". Tables are made up of columns of related information. A "Contacts" table, for example, may contain name, address, phone, and birthday columns. Each entry in the database can be thought of as a row containing info in each of those column fields:

name             address                 phone       birthday
----             -------                 --------    --------
John Smith       123 Toleen Lane         555-1234    1972-02-01
Paul Thompson    234 Georgetown Place    555-2345    1972-02-01
Jim Persee       345 Portman Pike        555-3456    1929-07-02
George Jones     456 Topforge Court                  1989-12-23
Tim Paulson                              555-5678    2001-05-16

"SQL" statements let you work with data stored in the table. Some SQL statements are used to create, destroy, and fill columns with data:

CREATE TABLE table_name          ; create a new table of information
DROP TABLE table_name            ; delete a table
INSERT INTO table_name VALUES (value1, value2,....)
INSERT INTO Contacts 
    VALUES ('Billy Powell', '5 Binlow Dr.', '555-6789', '1968-04-19')
INSERT INTO Contacts (name, phone) 
    VALUES ('Robert Ingram', '555-7890')

The SELECT statement is used to retrieve information from columns in a given table:

SELECT column_name(s) FROM table_name
SELECT * FROM Contacts
SELECT name,address FROM Contacts
SELECT DISTINCT birthday FROM Contacts ; returns no duplicate entries
; To perform searches, use WHERE.  Enclose search text in single
; quotes and use the following operators:
;  =, <>, >, <, >=, <=, BETWEEN, LIKE (use "%" for wildcards)
SELECT * FROM Contacts WHERE name='John Smith'
SELECT * FROM Contacts WHERE name LIKE 'J%'
SELECT * FROM Contacts WHERE birthday LIKE '%72%' OR phone LIKE '%34'
SELECT * FROM Contacts
    WHERE birthday NOT BETWEEN '1900-01-01' AND '2010-01-01'
; IN lets you specify a list of data to match within a column:
SELECT * FROM Contacts WHERE phone IN ('555-1234','555-2345')
SELECT * FROM Contacts ORDER BY name  ; sort results alphabetically
SELECT name, birthday FROM Contacts ORDER BY birthday, name DESC

Other SQL statements:

UPDATE Contacts SET address = '643 Pine Valley Rd.' 
    WHERE name = 'Robert Ingram'     ; alter or add to existing data
DELETE FROM Contacts WHERE name = 'John Smith'
DELETE * FROM Contacts
ALTER TABLE  - change the column structure of a table
CREATE INDEX - create a search key
DROP INDEX   - delete a search key

To integrate SQL statements in your REBOL code, enclose them as follows:

insert db {SQL command}

To retrieve the result set created by an inserted command, use:

copy db

You can use the data results of any query just as you would any other data contained in REBOL blocks. To retrieve only the first result of any command, for example, use:

first db

When you're done using the database, close the connection:

close db

Here's a complete example that opens a database connection, creates a new "Contacts" table, inserts data into the table, makes some changes to the table, and then retrieves and prints all the contents of the table, and closes the connection:

REBOL []

do %mysql-protocol.r 
db: open mysql://root:root@localhost/Contacts
; insert db {drop table Contacts} ; erase the old table if it exists
insert db {create table Contacts (
    name            varchar(100),
    address         text,
    phone           varchar(12),
    birthday        date 
)} 
insert db {INSERT into Contacts VALUES 
    ('John Doe', '1 Street Lane', '555-9876', '1967-10-10'),
    ('John Smith', '123 Toleen Lane', '555-1234', '1972-02-01'),
    ('Paul Thompson', '234 Georgetown Pl.', '555-2345', '1972-02-01'),
    ('Jim Persee', '345 Portman Pike', '555-3456', '1929-07-02'),
    ('George Jones', '456 Topforge Court', '', '1989-12-23'),
    ('Tim Paulson', '', '555-5678', '2001-05-16')
}
insert db "DELETE from Contacts WHERE birthday = '1967-10-10'"
insert db "SELECT * from Contacts"
results: copy db
probe results
close db
halt

Here's a shorter coding format that can be used to work with database tables quickly and easily:

read join mysql://user:pass@host/DB? "SELECT * from DB"

For example:

foreach row read rejoin [mysql://root:root@localhost/Contacts? 
    "SELECT * from Contacts"] [print row]

Here's a GUI example:

results: read rejoin [
    mysql://root:root@localhost/Contacts? "SELECT * from Contacts"]
view layout [
    text-list 100x400 data results [
        string: rejoin [
            "NAME:      " value/1 newline
            "ADDRESS:   " value/2 newline
            "PHONE:     " value/3 newline
            "BIRTHDAY:  " value/4
        ]
        view/new layout [
            area string
        ] 
    ] 
]

For a more detailed explanation about how to set up MYSQL, how to us the SQL language syntax, and other related topics, see http://musiclessonz.com/rebol_tutorial.html#section-27.

To use SQLite in REBOL, see http://www.dobeash.com/sqlite.html.

To use ODBC in REBOL, see http://www.rebol.com/docs/database.html.

For a useful open source SQL database system created entirely in native REBOL, see http://www.dobeash.com/rebdb.html.

The following additional database management systems ("DBMS"s) are also available for REBOL:

http://www.tretbase.com/forum/index.php
http://www.fys.ku.dk/~niclasen/nicomdb/
http://www.rebol.net/cookbook/recipes/0012.html
http://www.cs.unm.edu/~whip/
http://www.garret.ru/dybase.html
http://www.rebol.org/view-script.r?script=sql-protocol.r
http://www.rebol.org/view-script.r?script=db.r

Be sure to search rebol.org for more information and code related to databases.

9.13 Menus

One oddity about Rebol's GUI dialect is that it doesn't incorporate a native way to create standard menus. Users typically click buttons or text choices directly in REBOL GUIs to make selections. The "request-list" function and the GUI "choice" widget are short and simple substitutes which provide menu-like functionality. The menu example shown earlier in this tutorial can also be useful, but it doesn't look or operate in the typical way users expect. The popular REBOL GUI tool called RebGUI has a simple facility for creating basic menus, which can be useful.

The example below demonstrates a useful menu widget by Cyphre, which makes it easy to add nice looking menus to your GUIs. This example also includes Cyphre's Tab Panel widget, which is a great way to maximize screen real estate in programs with large GUIs:

REBOL [title: "Cyphre's Menu and Tab Panel Example"]

; Menu Widget:

do load decompress #{
789CB51B6B73DAB8F67B7E85BA3B770237430CE4D12EDD6EC6109A92266D499A
B629E3CE1810C6606C6A9BE074BBFFFD9E23C9B66CCB84A477C384C8B2747474
5E3A0FE5EF5118D516D45DB5C8C29C53E20D6774143E23839D6F357B615A3468
119F8E5723CABABED55EE260329C5B646D875342C71665FD8E678EC9F1E1EF7F
DB9FDAEFAFD6F5B76796A7C3CFBBEB9B69F7C68256BB87CFD71DFD161F3EFFF0
8EBE6047FB6CDCFE78D3D5F58BB3F7930FE1D5CE7C0DBD9DF6ECFAF5F93B787D
7CDED775AB77A9EB1F5C0D5EE8C7F07AFC11BEDE2F11ECB103E35F37F7BE7CFF
EADE20C09DE6DCE9F63F5D1DBA67172F34ED85A6FFB8D1FB7A27B27A33FFD38B
BDABAFE7DF17E1B43B5B4DA7B06AD7BABE3A773AEFDAD7FE7BC07074D5DDB1E7
776BC4D25B2EDB00BBFBEE1656F7ACC08AACAF67572BBDD9A6BDC6E7B6ADEB6F
BAD6FCC5E91B5B3F8F2EBFF6FBD142BFA6D6E71D58EDEAF8C7CDF2EB25ECA0D3
D1DFBEE95E754EEFDF74FA7AB7FBEEA67376DFD1BF9FDDF6FBFA69FBEEF6D68F
AE7A17BADEEB0DDB9DE69DFEFEEDCE17EB6BEFB6F7767D7EEE0125EF69AF1FE8
EDB61E5993FAE5349AF5828B4B86E17A31E97FBC9E863F7AFACDFBFACCBA38EF
996DA0FBCE6D70F5E58D7DE67EE94CFBFDD7A3D3C6C58B8B2BFDB2DB7D7D7C7A
AD03B5FBB7FAC5DC72BF1EDE6B07EDBECE5873F3E9FDD5DBA3CE6DAFF7EA1F89
D9A3291DCD413CFCF9969CD6D9578F73FAB47BFAFCF8E2DFE1F44D3DE6F4F97C
4FDFEBAE7AB0F41A609DBE99CED7FDB6A5B7EDFECAB4AE0F7B20017A74DBB7EC
B707CF3F009C7EFFEDF4F38ED75D74901A97D7AF7FD43B9FDAC8ACB3CBCE9ED5
EDB65FB70F6F75ABDFEF358EEE74EB3540EEEA7BF3E0CB878F079ED6EE3292ED
E46966C06F10DE3BF60FAA2DCC20A4BED01BAE6313131489290EEFFD561B798E
E7B788EBB954F44C3C3714DA88CD64643276E898A379D2E94D26018509F5A89E
F4055373ECAD33500DF117155640C7A6043D009C5BA41135F2EB359E37F71BC7
2FF61B4707DF6A39706BCF1F838948A18CCDD0240397AE356CC18E6D3F08894B
A39098BE15A42D2399623ACBA92981F856C3E9ACB7305F1A04066A42827BA0F1
421BAE6C678C2B52F22769366AE7A65B6BD6EB8719A839C8CDA323522324E9C9
8C34A4A70C62793C8C1C41BC498617410838B5083029E5376C8A711824A236C9
304960164F9E50EAC492004D69333EE5DC9AACDC11190019E069CF90374B41C8
40F896A6ED9F90898603C84034B4A895B4C81E9F0C2D79CFF1D078603C4C1E13
4CBD35991428912388EDDA612B2BC42D62BAF764C0C48B6D3F850A2C75BD90F0
57032180CD83E3FDE6C1C17EB3719C8E5C9A2E6037F2960029ED659445B5497B
3C9F9A2350389B0C0DC2C553DA84ED06D40F4968DA0E419082DCE688E6642704
D6B7889DE9E3CA243130A337857E3835F93BE9503DFC63BF513FDE6FFCF13C47
098EBB1B1666ACA73688393302C5D1B1B8A0DD301DDB725B64D7A193303B7269
FAA618894D80E99BCB13344F4E003E806F5B364C6C46F5EC342E1087D1214883
45C35AC84408D7B271C5CCD8D82C813E64FAB9551812FCC9625F2AEC02E01DF5
5381378967E44730E9311D986A82D4C2C628500F39A9312DCCF571F55300E1B6
5A0315A321CC4184156354C0346F19DA9E1B2858BF6152B85872ABA10629E858
59DBAE202AE873153850A9470DF25FA19F553065F5E870DB353927033AF2DC31
0940EE4FD4AB732563D695D9AB7887822E894CEE7285CDC2803EB28BD231D1D8
41B6CB0D7D7610EB33B6C55B9C29654CE1164931B130B6B8E244136ABBB4C1DB
9968A9B292E2527C47B91992B62AA6E4ACA51A0DEA5AA6456531A70A090DC083
008B661674842998B776952FF2241547139A5BC50B0500159756CB92B5401555
3AA81E0C16C99BE715945963DB1DD3088413FEAA5E8324536722AC066F0F5468
E2671BAD8E1905F82C574B0D740EC8A9996BF35E2956ADD2D342FE240AFECBBA
2C84447DEAC89FB2132823289B7549880C3B53F2C63D4356738426A1852702B0
2B4B27D079F6B67476A981798C7979BC71119C2E5A41C4A7743CC8346C50B89C
77365D935D90135CA9A63CB232847CE078933F78368E1C2FA0357A077B41FF9A
3D96EF84CF015FC61ED393C47C2003180483B75FF1270D1F1E82B5F4BD110D82
9AB70A1128C74405F90138F8B3721D00C59578B080839C84F68212148D3B5471
D198D37BC2AC5730F23DC7A93936F84302E5F07E097230454C986A3E843EFEB0
990F8C7B900CDEB236A6211D852DE0E153362F1C71C11B92CE7E5004721018F9
64B128210DF723C980A3B8C51AC6D698288542DAD2368BFD0243CADF95BD3118
D56ADC8E174EDFF8B336EDB0FCDC509E9ACC53564E508129F6E57BB2CFF293DC
16A74888274623AAC3895129F1C6C12456AB85D0EC5BEDE543364F0C38AED7A3
669A504093A5F1A093B0539687E5B17DE6FD6C621232C47F172B27B485E5636D
49DA26B643333665E8CC334A0516973957D04F1A393165ECE087184B49719460
A4927859B40C96AA81AF8470311AEC7510FAECAF961E7C136F38C3497C804FC3
95EFB22DB3C050E107A003844A9E49E70C672067312A22E152879F887D8B7E1E
69C638705459582D7248299ECC0B49CEEFF8EC640F041E44EF24CE56404BF42D
CD702A3A97A2C55D23DE74BC91E9B0E65D92E0F55760C4590BED329F0381E842
38D7E355DC0A5643DE630727AC319ACE79C35E58ECEFD0F44547B8607FC1F2B3
BF3402591CF377AE07E6920F172C8D43C9241309188340C95CC1E76C5200D8C0
3616B305741942DC847004B605C76BE0C1793440C0102F7B2EA960536BE0C304
1CB49F04BD4614C2219066FE8C543818EC010055783FB79706093D42DD315F98
E076EC30E16192DB40DA275282BB0CB2181B29E15BE4203A48C99BA65114C1F0
D843E72476C2B82E9818976C769D779937B28B001F31CBC41457925254052DBF
860E07F92884A84C3D2E982AF7BCE03C6F4CC0629E2A3172D4013700F52A753B
8DE4D478446E67DBAC4E2E7768A4CA9718F0ACDDFFEDF237C277AE2529B08CD0
81AEC7B2561766881D29A9962BD4834AF95DAE278951455A0C7641D1CDA14349
0535BE45427F45AB06A3D380E5F976E1B4421B205E8136EDD6E21E768E560D31
1CB50C8C0419B0D2D33318892704FE45FC9FC5C376C180900A5A91643536335C
A0D5B45DEB191FF73BEB46B75274C7F3C144910A33300900AEFB0916C21211BE
6134093F992D90314DED81412AD2A1C3625926B43CAB08946F908A8864719DAA
34384E59A1809C24CBB22E84CF90348C814853E26C8E471604235AE674147C67
843C89896A89A3125A598783BD0A3D5EF293B5466887E553EA92547738DC1683
4B2713748F4129C08B4A8A08E09B3051355E368E23396F9BF568004D9040ED1E
629383FC0EA4CC2CB7EC04F896D169F2127A588D0A9F732E15FAC580557C1CC9
9B15B86379536BBC64E2CEAB214390217349FE634D2216E2EF2F5D2B373BDEEE
C85B2CC117073F3B0AC1E813166B8283432A65468347A8CDA323A3CA13DCE835
958E96B2E3D5BCB7581EDE97E606D4E97289090119A439DB20DCCE218599A886
1B3807E4D7F27975C2EB8A63EAD80BB057BE9A710058BD6DA91EB5EDEE13574B
AA6E091396E6A9C18F06A7B774DF19F73B9F9FE1C6281F156448554A146EB213
B2E06306B6A047B19F5724C2450E4975E2DDF73CF42A73DA90398B44A089669C
0C061299659283319226955720BC7C098208F649B3B5E653EA12982B83238599
1730331008E51444442251B31819C11191A90E31A9111A1D47EA92C92C0820D3
796D4CE9920CC6BEB9164715BA4360E2175695D4F741C3F7EB464E81F2A0C462
780A2AC2EF0C6C0CF184B16A9680572C80BE65410FCAB51A04741C0903B14C82
03C1D6C79666088F20C88C848B25FE7AF815E0976D902C150B68C3A0B2F89A1F
6A35FA7D653A2719416AC822AACC673C2ABFFFA40C7F31CA57E6FCE532154FBE
A36A663CDB3809FA0A5E025BD4099AF2792D3EAD300742928A0A12F4CFD2AA73
EE4D5C3F75B96CB08045890FC12F803303ADCB5BD9FCCE2B0E78F8E1F484235A
257FBE2249975B65647193DD6FCCAC7307AE74048421C9415D0107074C638DF9
C2E2C89D79109E0C7621F0406292DD4CEE1A3D6003F1C97B5CD98F4F594ED30C
057166A52319756AA5D4512D414A324A55456745D127EC4CD6A1243CBC050AB3
9D95E4FF4AD3829BC98E32A3F1B43F999448221B97C4C9998D0D54BD6011D5DD
6A188AF594F42A2D5DA1D92AABA115B44E594C2D8CDA504A2D8C55153F846F28
46F3334B7889E8472A0AADEA19B19F5A26CF42602A251B65374614EF44910B5E
436CC7E3F624F89732B0E08F6385AB4AFE22F50D69676081642294B44410F994
60414C3DB55993463063A95C406B8AE23BDBF231D90CC756C249A8B261726C60
6D7ECBA9049B8D3B4DB8868800B2B68CB91DF3ED2FF2105F1E5824DE285BA526
AF524B567900403AA7C5FD8274261279E3EC8DE51625F03DEE76D41E02BCF12D
1792ADF6C7252E4563C3E027D432CAB7AF7205124A3C412CCB5040B5E47BE492
C4BF379A3C38DB376A7A2AFD4F117C99E319A1378781E7AC42CAE4E0D1A4FE05
8A72992B5FF297CE8F47B24B5C87C983528EDED2C9D85CB7FA3F5E6099C49536
F022CA2E9EB03B26E25E49DEE530480A41EDBCB15B1D83CDA3B62FD32519371E
3DE7E6C57724A452D79DE9AC681C28452C32F5E154264A874DE5A2257EADCAB3
D9E0EA32D7B6D4DB85D80FFDDDE28D8D2D02CE38B0F35CE75E89166C92DF81D6
3077A118C0D3CF6C448953C4CA354A09CEE5A6B39F48DC39DDDD63E96783D1BB
126189870B2344A31313738CD047067B301206D57082610CA04FE970E3E72789
5AB9B21082C0B250E90CAC16A9B550949054CAA85415520119C92F14D7DAD574
5CC48A9248E12F887D2E1988B9F6C7E602C14AF9E1681516135F49EA2BAE0FA6
9FF20CE813EFB666925752112549812B2EDC6E4838F2DC5C116F6EEB9B8AD414
922E5FBC493FE2F0B96FF174791EF55C82262DC6AA39086B15B0CE7172B3FD79
98ABA6EFC3E153CE52F67E6BA696D239CEDD0DF87AF5FDE7F00B1FE27BEC5CF8
A3C0E64285E25FA474194DE3320FC6DFAEA1222DB7A18CBE3CA9A04CCCB14A13
D697E25889D79AF68863825F961FAD28A8F0407412B00B0149448A28629A9B9D
1808B25E128C4A858A5D9E5E4DF5261B84A605906DEEB6F0DB9095B20CAEB44C
55B67D80774148784D32D7C9CA5DB93E5E8FCC750A4615FA45ED30D39B629231
F0F11E25C3CEBBC41D92F412C30EBBE4C13204995B1EE9058FF45E867C3323BD
1921DFCDE0D1C84E725067FE312577BBE0A1A23262A03552BB943AED99FFA7B0
852B5B6902F7D81278B735E3D8DAB21F2B7BEF7238920BA3EEF3C3EFE597B082
ACA8316A33B194FC0F4EDCEB9C891B73123B8BC7D62C1358A4898F38807E8040
79F1CE5F8A5253AAD26477BA18373653EE49BB44B5891369E93EE37F084A7659
93B0D222BC8C7CA8DC1378B5CCE66E41B81A99A54D01B8042696F69F80E28B52
24C4202067732BD6487628BE646727D7B20587528E714B85025B2DAAB6505CE9
F69674D5077ACCF13873A72BBDFD1697FB714A72A52D003F4943CBA2C141A2A1
B30D5E5D6C99F9D04301C9244363202209FC77431B040804940CD96F930C307B
8BB782C12732E21B663C8F5D82914872B36B38325AB011E325D6BFBD0989FF97
78E79FFF0120649CE6573C0000
}

; Tab Panel Widget: 

do load decompress #{
789CBD586973E2B816FDCEAF50577FE86428C7989824506F2695349DADB37496
4E4FA09C2A63CBE0C6D8C4368DC9BCF9EFEF5E495E65B2CCAB9AA4005BBAD2DD
8E8EAEF45714AF3CF799AA33338A6948868D47253647CADCF4A9D7238E6951B2
74E309EBE05D6648CD1E999953CABB79CFA312384E44E31E6925ED9668A2F698
0A517CCC441F1577666297179836890365E4FA66B82236B582D93CA4514476F4
8F7F35E8D973D8FF76F8DC69FEB8D56767E387ABBED63D39383B884FC79713EB
E4B8D38F2EEF9ED4A3969BCCFBBF76AE7E9E2E4757ABB079D3D28FADC99F8DDB
6F0FA77BB3FBF1D3D199F7703C0906B3BEDEBCE99EFE6A1F8EBFF6EFC70F27EE
FDF2E2707C39681F5EDF256A337A1ADCEB874FB7CF897DF2E5E2A9D171EFBA4D
4775FC78767D6D1E4D6EA6D6F5D765D4D793E3D9E0AC3B4816E777E7CFA74F17
9FA7AD9B2FC9F27C104C8F0E7FBA17DD4B6B7BFAE3B6D1FC71393BD8BB9AF447
30CF73D3695D7C3B595EDEDE3C58BB5F4EA3CF5DB77564FF79D78CC0BC643038
39F8BAFB5DBB3EBF98B9FDD1FDFC9BAD37BE3E07CBF3D3D5C21B3C75CD8BE6F7
B3B63538BE9C3E9CF8FABDF7BDD554E943AB75A21FDF6996EA6DDF5F7DFEDE3A
38D8D54E0F9AD303F8FBBDF17716F40812DD237AA293ACC90ABC20EC113FF0A9
6833C4AF4311003CCDF058C85DA3F4F3A884D40ECD258065E15B64E8109350A3
200E30000001B61C750ED8F1630561A38E82D0A6E17E4910B4AA0814953A0EB5
004B08872002E0D024A6BECD6CDF7054748428446B6D12E600BE6E94A767ED91
AA6D1A85F98D3A6525EF8B1110225553C6A169BBA00780AE9176A7BDC53F3A69
EBFA565BDFDE6A6FEFBDC72CA31478235D67754B8C27B0038ABB6996023F4E25
E1B1104D9159ADDDDA129F0A0E34AD92F1F5AB5546493A0998F266DC507FCC16
7D012744F502CBF448E43965C844403AD60444CA19B383A55F690259CFE955C0
55789685D594C55464B91E89A8071926A227620F4E602D229E06D7B769B24F1C
F84D65945190B0C1A016463B151DAE039E0D51D6B46237F071587956E335ABD4
BA900BD949B064B3A15CA9CFA805B1F1D2425D9300173B7F279F98AE72BCDF11
6D474DF78499EBE3982012DFCDCA0C76E0C495A12200E868BA684A1282544A61
0593DF902D09409C50D4558FB4DB521777013B5B521F2EB9D4D091675A531908
68433DD5F129EAE84EB5299D9321268B0CE7D4271BC5286C82739EA7D4B4839F
B820BB408C2DD88535A30AB35A3E0B8305C44AEBEC6D693B5DF874C876755CF9
DD581FBF8C97EAE227C7568A1F0063A71AE6F706B166C914FDAEF39720A7831D
8CD2351DD8927F8023E1977F5E89C98B4BAF48ED9C373323855B3D12870BDE52
A01EA4A31EB1CDD864CF0864781D99D31E31C33058463D22A257D9C83826C0D9
8F436C3320313B5B6D0DF6A64E879BB104BD28202C441D64E8D3A5CAB5396E18
C5C4876D17348DA3FC2975C90F84E97C54E686637A00AA5C927FBBBECB20375F
91D94ABCA5AA33C7E4FD2E2F2AF524470F9205EE3C850D2DA6F348CC3FCCC33F
33936C9BCA85EB2386FF7C3B28CF62E4560A0B916786D2BE6B14048B2572FA0A
254C757B2D0DC92320DE7213380ACA66C1A2F08398E4CC372CC0464BA5B84340
FB0B0C6A668AD050988BA79F238B29C2868A2E16373214E15B4EDC98A6120BE4
11C80AF9657A0B1223563C73455C3B210EB32E9ECD59260AD49136959323529B
271BE60087F28255184367F378B52FEC2EAC3DD854C016A9192012CCA8C41DA6
0F21057C71BBE1812D8B0F063E7237A23874FDF107D6802E0D47B05F4E3F90FF
9260F413F8049FC4986C32EEF2279610836C485C042EE0907D3E217CF5C8984F
2FF335C8328D4561F80A166BE4A151CD0F62552A65F3A1AF741FECE3B891444A
0C04E1AF1158F8E8279F41EAAE752255CA025DA733CB0278B05E234ABC4D2182
841D13EA94E5D8038AE0894B9B306A061AC10F194DA2ED25DBDB6FD109168680
1AD3F5186F67CB0E57417D6E11DAD80B4A5E9B2EAF61320E2A170B355E426F8F
540B2B116DDCAF7161B5B014635C8F4D0AC37C8187418F5AA0BAF41578888843
04AB343A6CB1F4C806FE6CCA9E3E2AC5FE9A6E991BDF1F711622B613088F7051
41DDE999515C0EE127B45D9A8E510F9258136F24E6A61B6629D4B228BD3C2791
AA844DE08768EACEDF502CA4A501E7119C2D63CD824CAA34DFDBCA19CE02C1FA
36D83CBF112D81A3729328DA0E948505695828305C4DC81F8443A202A4628899
9B69D9C139282DEEE0A83B86A8BC8848D30A8328AA348A81DB499B4473445C95
B1983EA2ED26DA2ED910956E7B937814F2149A31253B35D067DB51E54099FE3B
FC52EB8DA74511232493093581B2F30305475A9D0AAC364B5250DC626D5B697D
6524AF72CAEF2F42B36A9A844501323CD265B23522AF2FBDEAFB9A24416E27FF
284BFF384788D37D5EABBE3751B5E156FEFF704B50A8B3EEDF499451A60385E8
AD44694B65025FE26B2E205E3EDBD4DFFD08C190724EE2B70FC4618CD324A5D3
A438D463C8F7457FF5CA2E3D6AA64F901026A816CB06E9A28FAB6682EBC89785
5B722F33BE7A7512A45727B083BFE9EA846354A14F0BD34322C16A1B7FE45A75
E2DA9438B51E48875B474D2959EAB20332C26B104715672EDCB91CB0AC7A6355
719D252271E33581023FC4A583949BF42AA2974AD4F5BF78DFCA6A222CCA3476
67E4517F1C4F8AB42BEE71B212B5519C3FBB421348CB2F8AA1469124D3E9544D
45B4AAFC7EB2A24BED6185F6895D3C56EC6681E0D19722F1E2222A0808FD42AE
DCDCCE9B5352AD9F242DF8D39BF1F202AF8C29121C96803E1D2343D7788D3775
6AB266B86033AC6DD8D628F1617D8E44D4AA550C141EFF917995973065A7A088
D1BB50C4D45CE2554CABB36AED18F94E614D8C15A87813FC7A43452753A4D130
FEFE1F50628086731B0000
}

insert-event-func [ 
    either event/type = 'resize [
        mn/size/1: system/view/screen-face/pane/1/size/1 
        my-tabs/size: system/view/screen-face/pane/1/size - 15x30
        show [mn my-tabs]  none
    ] [event]
]

view/options center-face layout  [
    across space 0x0 origin 0x0
    mn: menu with [ 
        size: 470x20 
        data: compose/deep [
            " File " [
                "Open" # "Ctrl+O" [request-file]
                "Save" # "Ctrl+S" [request-file/save]
                bar
                "Exit" [quit]
            ]
            " Options " [
                "Preferences" sub [
                    "Colors" [alert form request-color]
                    "Settings" [request-text/title "Enter new setting:"]
                ]
                "About" [alert "Menu Widget by Cyphre"]
            ]
        ]
    ]
    below
    at 10x25 my-tabs: tab-panel data [
        "Fields"   [
            h1 "Tab Panel by Cyphre" field field area area btn "Ok"
         ]
        "Data List"  [
            t1: text-list 400x430 data system/locale/months [alert value]
        ]
    ]
] [resize]

For full blown menus with all the bells and whistles, animated icons, appropriate look-and-feel for various operating systems, and every possible display option, a module has been created to easily provide that capability: http://www.rebol.org/library/scripts/menu-system.r. Here's a minimal example demonstrating it's use:

do-thru http://www.rebol.org/library/scripts/menu-system.r
menu-data: [edit: item "Menu" menu [item "Item1" item "Item2"]]
simple-style: [item style action [alert item/body/text]]

view center-face layout/size [
    at 2x2 menu-bar menu menu-data menu-style simple-style
] 400x500

Here's a typical example that demonstrates the basic syntax for common menu layouts:

REBOL []

; You can download the menu-system.r script to your hard drive:

if not exists? %menu-system.r [write %menu-system.r (
        read http://www.rebol.org/library/scripts/menu-system.r)]

; If you're packaging your program into an .exe file, be sure to
; include the menu-system.r script in your package:

do %menu-system.r

; Here's how to create a menu layout:
; The "menu-data" block contains all the top level menus.
; Items in each of those menus go into separate "menu" blocks.
; Submenus are simply items with their own additional "menu" blocks.
; Use "---" for separator lines:

menu-data: [
    file: item "File" menu [item "Open" item "Save" item "Quit"]
    edit: item "Edit" menu [ 
        item "Item 1"
        item "Item 2" <ctrl-q>
        ---
        item "Submenu..." menu [
            item "Submenu Item 1" 
            item "Submenu Item 2"
            item "Submenu Item 3" menu [
                item "Sub-Submenu Item 1"
                item "Sub-Submenu Item 2"
            ]
        ]
        ---
        item "Item 3"       
    ]
    icons: item "Icons" menu [
        item "Icon Item 1" icons [help.gif stop.gif]
        item "Icon Item 2" icons [info.gif exclamation.gif]
    ]
]

; Each menu selection can now run any code you want.
; Just use the "switch" structure below:

basic-style: [item style action [
    switch item/body/text [
        ; put any code you want, in each block:
        case "Open" [
            the-file: request-file
            alert rejoin ["You opened: " the-file]
        ] 
        case "Save" [
            the-file: request-file/save
            alert rejoin ["You saved to: " the-file]
        ]
        case "Quit" [
            if (request/confirm "Really Quit?") = true [quit]
        ]   
        case "Item 1" [alert "Item 1 selected"]
        case "Item 2" [alert "Item 2 selected"]
        case "Item 3" [alert "Item 3 selected"]
        case "Submenu Item 1" [alert "Submenu Item 1 selected"]
        case "Submenu Item 2" [alert "Submenu Item 2 selected"]
        case "Submenu Item 3" [alert "Submenu Item 3 selected"]
        case "Sub-Submenu Item 1" [alert "Sub-Submenu Item 1 selected"]
        case "Sub-Submenu Item 2" [alert "Sub-Submenu Item 2 selected"]
        case "Icon Item 1" [alert "Icon Item 1 selected"]
        case "Icon Item 2" [alert "Icon Item 2 selected"]
    ]
]]

; The following lines need to be added to eliminate a potential problem
; closing down:

evt-close: func [face event] [either event/type = 'close [quit] [event]]
insert-event-func :evt-close

; Now put the menu in your GUI, as follows:

view center-face layout [
    size 400x500
    ; use this stock code:
    at 2x2 menu-bar menu menu-data menu-style basic-style
]

The demo at http://www.rebol.org/library/scripts/menu-system-demo.r shows off many more advanced features of the module.

Below is an intermediate example with explanations of the most important features. It also includes some stock code to display menus with a standard MS Windows style (OS specific appearance). The menu module has been compressed and embedded directly into the script, using "compress read http://www.rebol.org/library/scripts/menu-system.r" (so that the module does not need to be downloaded or included as a separate file):

REBOL [Title: "Menu Example"]

; The following line imports the compressed menu module.  

do decompress #{
789CED7DED761B3792E8EFEBA740343F24AD4D5352EC24C3331E1F59A213258E
E548B233191EDE735A64536A9B6433ECA64566BD8F71DFF7A2AAF0D9F8E82645
27D9DD602672B31B28140A8542A150285C745F9CBF62BD078CA78BECE6B62C3A
8CFD27FE847492CF567378CDF606FBECE8E0E0293BB99D6745992553D69D16E9
58653D1E8F19662DD83C2DD2F9C774F8F881FA7A910E79A97976BD28B37CCA92
E9902D8A94655356E48BF920C537D7D93499AFD8289F4F8A47EC2E2B6F593EC7
7FF345C914AC493ECC46D92001488F58324FD92C9D4FB2B24C876C36CF3F6643
FE50DE2625FF937268E3717E974D6FD8209F0E3328546858507A92961D8DE9FF
AB225BB07C24B11CE4439E7F5194BC8D65C2B1871A92EBFC237C12A45290789A
E66536481FF16C59C1C61C28C032F08056DB48F29A07E3249BA4F3C7319478D5
06B5244ABCF5C30547D38315DB165A8C5A6D821BE683C5249D9689ECDB36EFB6
9C679AB34952A6F32C1917BA67B05F01BAD922ABB1AFD30C0B43A6693249013B
78D68DB9CDC7439E619AEB4C9C266561B5923788E0E7F38223B262D729301D6F
5ACED2E990BF4D81BF386293BC4C19118FC3E09033CEBD16AC11CF44E42AF251
79076C23F9B298A503E0470E2003769D03274E89278B02DBA6205D7D7776C92E
CF5F5EFD7C7CD165FCF9CDC5F9BBB3D3EE297BF10BFFD86527E76F7EB938FBF6
BB2BF6DDF9ABD3EEC5253B7E7DCADFBEBEBA387BF1F6EAFCE252C1DA39BEE410
7630C3F1EB5F58F75F6F2EBA9797ECFC829DFDF8E6D51907CA6BB9387E7D75D6
BD7CC4CE5E9FBC7A7B7AF6FADB478C0362AFCFAF74035F9DFD7876C5F35F9D3F
422CDCF2ECFC25FBB17B71F21DFF79FCE2ECD5D9D52F58F1CBB3ABD750E94B5E
AB1605ECCDF1C5D5D9C9DB57C717ECCDDB8B37E7975D060D3E3DBB3C79757CF6
63F7F431C787E3C0BAEFBAAFAFD8E577C7AF5E55DAAFA09DFFFCBA7B01AD32A9
C05E7439D6C72F5E75B16A68FFE9D945F7E40A1AAA9F4E386D39C2AF1E697097
6FBA2767F0A6FBAF2E6FE6F1C52F8F04F0CBEE4F6F796EFE919D1EFF78FC2D6F
F55E53AAF17E3C797BD1FD115AC34975F9F6C5E5D5D9D5DBAB2EFBF6FCFC143B
E5B27BF1EEEC840315E9F4F8EA18ABE6653919D57B4E86F38B5F000A341049FD
88FDFC5D97BFBF00AA21198EA17D979C1C275766360E8D53C7C0513780BDEE7E
FBEAECDBEEEB932E643B07703F9F5D76F779CF9C5D42060E1BE8F8F331AFFC2D
36037AE42DEF3BDD172F6D167E841DC8CE5EB2E3D3771CD8A92CC5BBFCF24CF0
09D2E3E43B41D4C73BAC2E6175FFF5A0FFE0C1A05CB6B86459F059094673BA2C
F95CF5C0CA57DC26C3FCAE954D929BB483934A0FE69F92E19BBE98DB20E1EB0E
2B5645994EDAF90C655D9BC468EB3A29B44C0B67E9B0AF9EA86CA2CE719E0CF9
EBBFFDE783ECDD8BF38BBB831FBEBDC98F797A7DF9F6B6FBF6863FBD809FC73F
9D1CFF02FF8EBE69FFFD161E5E4CBE7F7571F0D371FBEEB47DFCE6E1CD838F49
7A051F4EFEF5E2ECE77FFDC89F0AF8FDAA7BD73D9ECCEEB0F48B2F2FBEBF7AFB
F6BBC39F7E7A7BF2DB2FDFAEDE24C9F827FEE1ECF5F70F2EBA2FDFA6AFE7C383
5FBBE777EF8FDFBE181E9F9E9E7D7F7AF64BF2EF9FDFBCFF7E74F9EFC5717E74
FD53B6BAFEE1877FBF9AFCFAF6B78B41F7F0EADFE70FDF7ED91D3D78F3F4EFA7
776F57DF9DDCFDF0EDAF372FF2C1CB7CF1F0EED5CDEDEDACFCE1DDE9FBF7DFFE
38FB707EFDD5ABF4F8CDCDC9AFBF7DDF3DBC7C77F9E4E3FBEF6F2E0E8BE3F70F
7EFEF7F7B7C7CB9BF7DF1497072F3ECE9E941F3EBE990C96CBB47BD25EFDB67A
F26A79F8F1F8F8E555FBFCB7BF1F5FBE6FBFBC3DCDAEFFF5DD3552A83B7EF9E0
EAC3E5E2A7C9C9C983FF6AD41FD8AB7697E0AF3EF1C92029789F8F16D301EB7D
4CC68B14DEA405E78B820BF0019F8956B3F479FB2E9F0F59C7CC40C581E55A45
F65BFA5C02D9B948CBC51C2649364A06E96E817918E481D9E46079C0391CBEB0
5E7EFD3E1D945FF4597B9C0F9231E5E1F82EC625AF5F8F2A78DFC1326D7854EF
D59B0E3B3CE069897F6B878F7F3C8911C0EBEE60852D441BAAF0D567E141C504
55154024758536AFCF5F775B45324AE92B02E323349F978345B9432F590FFFF9
82CFDED3F48BBE394293E98AF512AE48521603481F08DB7750981665321DA4AD
7C2451D0D3A3ECA7AB8BB75D968DF8945F66E58A65A8CFCDD35F17D99C4FF25C
AB290AB6078893CA729715E9FEE31D054714939D29B016E57AC0375F08BC8C76
40134CF58151E9E7029AF5691799E519BB01993595F5ED025F5AF946F9623A7C
CE461957CE9050950202A3BEC04D9575A8E6658DA6090B4CD3BBD61014E0B13B
2AA66C31CDA65C911CF36E1B72A52C9D30CAFB78C7ECEB49F2219503FB6396DE
F13F43F8AF4563675DBC3061C33B04FA9A8FB90F5F704EE2150864FB9BC04C87
20E307F9389F77583A1AF16E94FFF2AA801B9AC1B1D96134A2B908071B676DBB
A7D3742C5A8143127E57F8091267DF7972277B809A28204B89038DEF7B8A4282
6F1DD135ED19D768A765CB120756E691CC299A1E000AC9CAD861B36CF0A15A18
B9E21FFFC47FDB30C1B7930157D38953ABFFD9F37D90B6D8D135A4530F1C1F44
8E6887E8A41F3905AAC4E2ED063D3F9B3EA7EF6D415FBE286AD1E3736A0CB604
C59D4B18DD4A408733799195FA253EF9CB60765E2BACBA7C991025EB6DDF18F6
86FC3619C5CABF73CABFE0A8850A1AE866350969A1273E41ADDE2CC9E65F68DC
883B2D44B8202F531A66BA093D374B47FD44BEEA1DB1C33ED5FACCCB4D95C254
CD4355F8A02F7A0EDFB30A8FD9AC00F9841CA8BEE66B47BEDE2BB3B468231D31
5BD1EE2058174AC695D9B6D01E235020DB63CC56034A0EB5FB81BACE87ABF688
ABD91D16C30AB23D866C11088A4E7108ED1A3A213C2E9A920618413617C287B4
D2243F049E2DD0240940F77C14405D8B009CD5A030387F83683A725E57014036
7F619B858385EB1A8279EAF9CEC81681658289C00A83A9CAEC695EB274322B57
CF231C43C32124AF91F72466837C32CB8BB43D4CD35960E603111B991449A1DD
4EDA4B8A1648547660A00A734F7BC55A6CAF48C19467EAE7B5441034DD676D76
B41FD509F79A82F242E93B6FFB81E90B92F543B02D6ABE9E91E915405C094C06
1FFC6A9FCB8638DB4237B626C9FC8398CB08CEBAA9022F99CFF3BB4DE11913B9
42CE525CF4744BC07BE56236AEACAC1452505AB1B5BB4EB1F3B9030D12AC8F43
3AA55E9069306C779E0CB33C32381A0C2F93045C9F48A76C0F5BBBCF1744E371
CB7C31C8E68371CABE5E7EC3BE74190E49CA7CAC1869C0E036057D617B0D8866
311B33CEA669EB2E1B96B7EC887E0C92192B7E5D80091A7FBFCF613380AB82E3
2850C8CBBEE434F96A7978C80E8F964FB59EE5FBCF5E2F6A36D